]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
FS-11874: [Verto-Communicator] Rebuild verto-min.js to reflect the latest changes...
authorAndrey Volk <andywolk@gmail.com>
Mon, 3 Jun 2019 17:00:33 +0000 (21:00 +0400)
committerAndrey Volk <andywolk@gmail.com>
Mon, 3 Jun 2019 17:00:33 +0000 (21:00 +0400)
html5/verto/demo/js/verto-min.js
html5/verto/video_demo-live_canvas/js/verto-min.js
html5/verto/video_demo/js/verto-min.js

index 2ac8e89e7ed7c5777e3265cf4f52814ae155b29b..7a6a42b8a0c26694ecf3dc65c8ecbd01ce5a4906 100644 (file)
-/*
- * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
- *
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- *
- * The Initial Developer of the Original Code is
- * Anthony Minessale II <anthm@freeswitch.org>
- * Portions created by the Initial Developer are Copyright (C)
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Anthony Minessale II <anthm@freeswitch.org>
- *
- * jquery.FSRTC.js - WebRTC Glue code
- *
- */
 
-(function($) {
-
-    // Find the line in sdpLines that starts with |prefix|, and, if specified,
-    // contains |substr| (case-insensitive search).
-    function findLine(sdpLines, prefix, substr) {
-        return findLineInRange(sdpLines, 0, -1, prefix, substr);
-    }
-
-    // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
-    // and, if specified, contains |substr| (case-insensitive search).
-    function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
-        var realEndLine = (endLine != -1) ? endLine : sdpLines.length;
-        for (var i = startLine; i < realEndLine; ++i) {
-            if (sdpLines[i].indexOf(prefix) === 0) {
-                if (!substr || sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
-                    return i;
-                }
-            }
-        }
-        return null;
-    }
-
-    // Gets the codec payload type from an a=rtpmap:X line.
-    function getCodecPayloadType(sdpLine) {
-        var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
-        var result = sdpLine.match(pattern);
-        return (result && result.length == 2) ? result[1] : null;
-    }
-    
-    // Returns a new m= line with the specified codec as the first one.
-    function setDefaultCodec(mLine, payload) {
-        var elements = mLine.split(' ');
-        var newLine = [];
-        var index = 0;
-        for (var i = 0; i < elements.length; i++) {
-            if (index === 3) { // Format of media starts from the fourth.
-                newLine[index++] = payload; // Put target payload to the first.
-            }
-            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() {}
-            },
-      useStream: null,
-        }, 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;
-    };
-
-    // Sets Opus in stereo if stereo is enabled, by adding the stereo=1 fmtp param.
-    $.FSRTC.prototype.stereoHack = function(sdp) {
-        var self = this;
-
-        if (!self.options.useStereo) {
-            return sdp;
-        }
-
-        var sdpLines = sdp.split('\r\n');
-
-        // Find opus payload.
-        var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'), opusPayload;
-
-        if (!opusIndex) {
-           return sdp;
-       } else {
-            opusPayload = getCodecPayloadType(sdpLines[opusIndex]);
-        }
-
-        // Find the payload in fmtp line.
-        var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString());
-
-        if (fmtpLineIndex === null) {
-           // create an fmtp line
-           sdpLines[opusIndex] = sdpLines[opusIndex] + '\r\na=fmtp:' + opusPayload.toString() + " stereo=1; sprop-stereo=1"
-       } else {
-            // Append stereo=1 to fmtp line.
-            sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1');
-       }
-
-        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 onRemoteStreamSuccess(self, stream) {
-      console.log("Remote Stream Success");
-      doCallback(self, "onRemoteStream", stream);
-    }
-
-    function onICE(self, candidate) {
-        self.mediaData.candidate = candidate;
-        self.mediaData.candidateList.push(self.mediaData.candidate);
-
-        doCallback(self, "onICE");
-    }
-
-    function doCallback(self, func, arg) {
-        if (func in self.options.callbacks) {
-            self.options.callbacks[func](self, arg);
-        }
-    }
-
-    function onICEComplete(self, candidate) {
-        console.log("ICE Complete");
-        doCallback(self, "onICEComplete");
-    }
-
-    function onChannelError(self, e) {
-        console.error("Channel Error", e);
-        doCallback(self, "onError", e);
-    }
-
-    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 (typeof element.srcObject !== 'undefined') {
-           element.srcObject = stream;
-       } else {
-           console.error('Error attaching stream to element.');
-       }
-    }
-    
-    function onRemoteStream(self, stream) {
-        if (self.options.useVideo) {
-            self.options.useVideo.style.display = 'block';
-
-           // Hacks for Mobile Safari
-           var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
-
-           if (iOS) {
-               self.options.useVideo.setAttribute("playsinline", true);
-           }
-        }
-
-        var element = self.options.useAudio;
-        console.log("REMOTE STREAM", stream, element);
-
-       FSRTCattachMediaStream(element, stream);
-       
-
-       
-        //self.options.useAudio.play();
-        self.remoteStream = stream;
-        onRemoteStreamSuccess(self, 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';
-            self.options.useVideo['src'] = '';
-        }
-
-        if (self.localStream && !self.options.useStream) {
-            if(typeof self.localStream.stop == 'function') {
-                self.localStream.stop();
-            } else {
-               if (self.localStream.active){
-                    var tracks = self.localStream.getTracks();
-                    console.log(tracks);
-                   tracks.forEach(function(track, index){
-                       console.log(track);
-                       track.stop();
-                   })
-                }
-            }
-            self.localStream = null;
-        }
-
-        if (self.options.localVideo) {
-      deactivateLocalVideo(self.options.localVideo);
-        }
-
-       if (self.options.localVideoStream && !self.options.useStream) {
-            if(typeof self.options.localVideoStream.stop == 'function') {
-               self.options.localVideoStream.stop();
-            } else {
-               if (self.options.localVideoStream.active){
-                    var tracks = self.options.localVideoStream.getTracks();
-                    console.log(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;
-    if (!self.localStream) {
-        return false;
-    }
-       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;
-           }
-
-           self.audioEnabled = audioTracks[i].enabled;
-       }
-
-       return !self.audioEnabled;
-    }
-
-    $.FSRTC.prototype.getVideoMute = function() {
-       var self = this;
-       return self.videoEnabled;
-    }
-
-    $.FSRTC.prototype.setVideoMute = function(what) {
-       var self = this;
-    if (!self.localStream) {
-        return false;
-    }
-       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 = 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
-                },
-                turnServer: self.options.turnServer
-            });
-
-            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 (self.options.useVideo && self.options.localVideo && !self.options.useStream) {
-            getUserMedia({
-               constraints: {
-                    audio: false,
-                    video: { deviceId: params.useCamera },
-               },
-               localVideo: self.options.localVideo,
-               onsuccess: function(e) {self.options.localVideoStream = e; console.log("local video ready");},
-               onerror: function(e) {console.error("local video error!");}
-            });
-       }
-
-    if (self.options.useStream) {
-      if (self.options.useVideo) {
-        self.options.localVideoStream = self.options.useStream;
-        if (self.options.localVideo) {
-          activateLocalVideo(self.options.localVideo, self.options.useStream);
-        }
-      }
-      onSuccess(self.options.useStream);
-    }
-    else {
-      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) {//obj.options.videoParams.chromeMediaSource == 'desktop') {
-           console.error("SCREEN SHARE", obj.options.videoParams);
-           audio = false;
-       } else {
-           audio = {
-           };
-
-           if (obj.options.audioParams) {
-               audio = obj.options.audioParams;
-            }
-
-           if (obj.options.useMic !== "any") {
-               //audio.optional = [{sourceId: obj.options.useMic}];
-               audio.deviceId = {exact: obj.options.useMic};
-           }
-       }
-
-    if (obj.options.useVideo && obj.options.localVideo && !obj.options.useStream) {
-            getUserMedia({
-               constraints: {
-                    audio: false,
-                    video: { deviceId: obj.options.useCamera },
-               },
-               localVideo: obj.options.localVideo,
-               onsuccess: function(e) {obj.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) {
-           if (!obj.options.useCamera && !!navigator.mozGetUserMedia) {
-               //This is an issue, only FireFox needs to ask this additional question if its screen or window we need a better way
-               var dowin = window.confirm("Do you want to share an application window?  If not you can share an entire screen.");
-
-               video = {
-                   width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth},
-                   height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight},
-                   mediaSource: dowin ? "window" : "screen"
-               }
-           } else {
-               var opt = [];
-               if (obj.options.useCamera) {
-                   opt.push({sourceId: obj.options.useCamera});
-               }
-               
-               if (bestFrameRate) {
-                   opt.push({minFrameRate: bestFrameRate});
-                   opt.push({maxFrameRate: bestFrameRate});
-               }
-               
-               video = {
-                   mandatory: obj.options.videoParams,
-                   optional: opt               
-               };
-          // NOTE: This is a workaround for
-          // https://bugs.chromium.org/p/chromium/issues/detail?id=862325
-          if (!!navigator.userAgent.match(/Android/i)) {
-            delete video.frameRate.min;
-          }
-           }
-       } else {
-
-           video = {
-               //mandatory: obj.options.videoParams,
-               width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth},
-               height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight}
-           };
-           
-            
-           
-           var useVideo = obj.options.useVideo;
-
-           if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") {
-               //if (!video.optional) {
-               //video.optional = [];
-               //}
-
-               
-               if (obj.options.useCamera !== "any") {
-                   //video.optional.push({sourceId: obj.options.useCamera});
-        video.deviceId = {
-          exact: obj.options.useCamera,
-        };
-               }
-
-               if (bestFrameRate) {
-                   //video.optional.push({minFrameRate: bestFrameRate});
-                   //video.optional.push({maxFrameRate: bestFrameRate});
-                   video.frameRate = {ideal: bestFrameRate, min: minFrameRate, max: 30};
-               }
-
-           } else {
-               console.log("Camera Disabled");
-               video = false;
-               useVideo = false;
-           }
-       }
-
-       return {audio: audio, video: video, useVideo: useVideo};
-    }
-    
-    $.FSRTC.prototype.call = function(profile) {
-        checkCompat();
-       
-        var self = this;
-       var screen = false;
-
-        self.type = "offer";
-
-       if (self.options.videoParams && self.options.screenShare) { //self.options.videoParams.chromeMediaSource == 'desktop') {
-           screen = true;
-       }
-
-        function onSuccess(stream) {
-           self.localStream = stream;
-           
-           if (screen) {
-               self.constraints.offerToReceiveVideo = false;
-               self.constraints.offerToReceiveAudio = false;
-               self.constraints.offerToSendAudio = 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,
-                turnServer: self.options.turnServer
-            });
-
-            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 (self.options.useStream) {
-      if (self.options.useVideo) {
-        self.options.localVideoStream = self.options.useStream;
-        if (self.options.localVideo) {
-          activateLocalVideo(self.options.localVideo, self.options.useStream);
-        }
-      }
-      onSuccess(self.options.useStream);
-    }
-    else if (mediaParams.audio || mediaParams.video) {
-
-            getUserMedia({
-               constraints: {
-                    audio: mediaParams.audio,
-                    video: mediaParams.video
-               },
-               video: mediaParams.useVideo,
-               onsuccess: onSuccess,
-               onerror: onError
-            });
-
-       } else {
-           onSuccess(null);
-       }
-
-
-
-        /*
-        navigator.getUserMedia({
-            video: self.options.useVideo,
-            audio: true
-        }, onSuccess, onError);
-        */
-
-    };
-
-    // DERIVED from RTCPeerConnection-v1.5
-    // 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
-    
-    function FSRTCPeerConnection(options) {
-       var gathering = false, done = false;
-       var config = {};
-        var default_ice = [{ urls: ['stun:stun.l.google.com:19302'] }];
-
-        if (self.options.turnServer) {
-          default_ice.push(self.options.turnServer)
-        }
-
-        if (options.iceServers) {
-            if (typeof(options.iceServers) === "boolean") {
-               config.iceServers = default_ice;
-            } else {
-               config.iceServers = options.iceServers;
-           }
-        }
-
-       config.bundlePolicy = "max-compat";
-
-        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();
-           }
-        };
-
-        // attachStream = MediaStream;
-        if (options.attachStream) peer.addStream(options.attachStream);
-
-        // attachStreams[0] = audio-stream;
-        // attachStreams[1] = video-stream;
-        // attachStreams[2] = screen-capturing-stream;
-        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;
-
-            // onRemoteStreamEnded(MediaStream)
-            remoteMediaStream.oninactive = function () {
-                if (options.onRemoteStreamEnded) options.onRemoteStreamEnded(remoteMediaStream);
-            };
-
-            // onRemoteStream(MediaStream)
-            if (options.onRemoteStream) options.onRemoteStream(remoteMediaStream);
-
-            //console.debug('on:add:stream', remoteMediaStream);
-        };
-
-        //var constraints = options.constraints || {
-         //  offerToReceiveAudio: true,
-           //offerToReceiveVideo: true   
-        //};
-
-        // onOfferSDP(RTCSessionDescription)
-        function createOffer() {
-            if (!options.onOfferSDP) return;
-
-            peer.createOffer(function(sessionDescription) {
-                sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
-                peer.setLocalDescription(sessionDescription);
-                options.onOfferSDP(sessionDescription);
-            },
-                               onSdpError, options.constraints);
-        }
-
-        // onAnswerSDP(RTCSessionDescription)
-        function createAnswer() {
-            if (options.type != "answer") return;
-
-            //options.offerSDP.sdp = addStereo(options.offerSDP.sdp);
-            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();
-        }
-
-        // DataChannel Bandwidth
-        function setBandwidth(sdp) {
-            // remove existing bandwidth lines
-            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;
-        }
-
-        // old: FF<>Chrome interoperability management
-        function getInteropSDP(sdp) {
-            var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
-            extractedChars = '';
-
-            function getChars() {
-                extractedChars += chars[parseInt(Math.random() * 40)] || '';
-                if (extractedChars.length < 40) getChars();
-
-                return extractedChars;
-            }
-
-            // usually audio-only streaming failure occurs out of audio-specific crypto line
-            // a=crypto:1 AES_CM_128_HMAC_SHA1_32 --------- kAttributeCryptoVoice
-            if (options.onAnswerSDP) sdp = sdp.replace(/(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g, '');
-
-            // video-specific crypto line i.e. SHA1_80
-            // a=crypto:1 AES_CM_128_HMAC_SHA1_80 --------- kAttributeCryptoVideo
-            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;
-        }
-
-        // DataChannel management
-        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);
-            };
-        }
-
-        function openAnswererChannel() {
-            peer.ondatachannel = function(event) {
-                channel = event.channel;
-                channel.binaryType = 'blob';
-                setChannelEvents();
-            };
-
-            return;
-        }
-
-        // fake:true is also available on chrome under a flag!
-        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 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;
-                  }
-                }
-            }
-
-        };
-    }
-
-    // getUserMedia
-    var video_constraints = {
-        //mandatory: {},
-        //optional: []
-    };
-
-  function activateLocalVideo(el, stream) {
-    el.srcObject = stream;
-    el.style.display = 'block';
-  }
-
-  function deactivateLocalVideo(el) {
-    el.srcObject = null;
-    el.style.display = 'none';
-  }
-
-    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) {
-        activateLocalVideo(options.localVideo, stream);
-            }
-
-            if (options.onsuccess) {
-                options.onsuccess(stream);
-            }
-
-            media = stream;
-        }
-
-        return media;
-    }
-
-    $.FSRTC.resSupported = function(w, h) {
-       for (var i in $.FSRTC.validRes) {
-           if ($.FSRTC.validRes[i][0] == w && $.FSRTC.validRes[i][1] == h) {
-               return true;
-           }
-       }
-
-       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 = [[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;
-       }
-
-       w = resList[resI][0];
-       h = resList[resI][1];
-       resI++;
-
-       var video = {
-           width: {exact: w},
-           height: {exact: h}
-       };
-
-  if (cam !== "any") {
-    video.deviceId = {
-      exact: cam,
-    };
-  }
-
-       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.warn( 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);
-    }
-
-    $.FSRTC.checkPerms = function (runtime, check_audio, check_video) {
-       getUserMedia({
-           constraints: {
-               audio: check_audio,
-               video: check_video,
-           },
-           onsuccess: function(e) {
-
-               e.getTracks().forEach(function(track) {track.stop();});
-               
-               console.info("media perm init complete"); 
-               if (runtime) {
-                    setTimeout(runtime, 100, true);
-               }
-            },
-           onerror: function(e) {
-               if (check_video && check_audio) {
-                   console.error("error, retesting with audio params only");
-                   return $.FSRTC.checkPerms(runtime, check_audio, false);
-               }
-
-               console.error("media perm init error");
-
-               if (runtime) {
-                   runtime(false)
-               }
-           }
-       });
-    }
-
-})(jQuery);
-/*
- * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
- *
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is jquery.jsonrpclient.js modified for Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- *
- * The Initial Developer of the Original Code is
- * Textalk AB http://textalk.se/
- * Portions created by the Initial Developer are Copyright (C)
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Anthony Minessale II <anthm@freeswitch.org>
- *
- * jquery.jsonrpclient.js - JSON RPC client code
- *
- */
-/**
- * This plugin requires jquery.json.js to be available, or at least the methods $.toJSON and
- * $.parseJSON.
- *
- * The plan is to make use of websockets if they are available, but work just as well with only
- * http if not.
- *
- * Usage example:
- *
- *   var foo = new $.JsonRpcClient({ ajaxUrl: '/backend/jsonrpc' });
- *   foo.call(
- *     'bar', [ 'A parameter', 'B parameter' ],
- *     function(result) { alert('Foo bar answered: ' + result.my_answer); },
- *     function(error)  { console.log('There was an error', error); }
- *   );
- *
- * More examples are available in README.md
- */
-(function($) {
-  /**
-   * @fn new
-   * @memberof $.JsonRpcClient
-   *
-   * @param options An object stating the backends:
-   *                ajaxUrl    A url (relative or absolute) to a http(s) backend.
-   *                socketUrl  A url (relative of absolute) to a ws(s) backend.
-   *                onmessage  A socket message handler for other messages (non-responses).
-   *                getSocket  A function returning a WebSocket or null.
-   *                           It must take an onmessage_cb and bind it to the onmessage event
-   *                           (or chain it before/after some other onmessage handler).
-   *                           Or, it could return null if no socket is available.
-   *                           The returned instance must have readyState <= 1, and if less than 1,
-   *                           react to onopen binding.
-   */
-    $.JsonRpcClient = function(options) {
-        var self = this;
-        this.options = $.extend({
-            ajaxUrl       : null,
-            socketUrl     : null, ///< The ws-url for default getSocket.
-            onmessage     : null, ///< Other onmessage-handler.
-            login         : null, /// auth login
-            passwd        : null, /// auth passwd
-            sessid        : null,
-           loginParams   : null,
-           userVariables : null,
-            getSocket     : function(onmessage_cb) { return self._getSocket(onmessage_cb); }
-        }, options);
-
-        self.ws_cnt = 0;
-
-        // Declare an instance version of the onmessage callback to wrap 'this'.
-        this.wsOnMessage = function(event) { self._wsOnMessage(event); };
-    };
-
-    /// Holding the WebSocket on default getsocket.
-    $.JsonRpcClient.prototype._ws_socket = null;
-
-    /// Object <id>: { success_cb: cb, error_cb: cb }
-    $.JsonRpcClient.prototype._ws_callbacks = {};
-
-    /// The next JSON-RPC request id.
-    $.JsonRpcClient.prototype._current_id = 1;
-
-
-    $.JsonRpcClient.prototype.speedTest = function (bytes, cb) {
-        var socket = this.options.getSocket(this.wsOnMessage);
-       if (socket !== null) {
-           this.speedCB = cb;
-           this.speedBytes = bytes;
-           socket.send("#SPU " + bytes);
-
-           var loops = bytes / 1024;
-           var rem = bytes % 1024;
-           var i;
-           var data = new Array(1024).join(".");
-           for (i = 0; i < loops; i++) {
-               socket.send("#SPB " + data);
-           }
-           
-           if (rem) {
-               socket.send("#SPB " + data);
-           }
-
-           socket.send("#SPE");
-       }
-    };
-
-
-
-    /**
-     * @fn call
-     * @memberof $.JsonRpcClient
-     *
-     * @param method     The method to run on JSON-RPC server.
-     * @param params     The params; an array or object.
-     * @param success_cb A callback for successful request.
-     * @param error_cb   A callback for error.
-     */
-    $.JsonRpcClient.prototype.call = function(method, params, success_cb, error_cb) {
-    // Construct the JSON-RPC 2.0 request.
-
-        if (!params) {
-            params = {};
-        }
-
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        var request = {
-            jsonrpc : '2.0',
-            method  : method,
-            params  : params,
-            id      : this._current_id++  // Increase the id counter to match request/response
-        };
-
-        if (!success_cb) {
-            success_cb = function(e){console.log("Success: ", e);};
-        }
-
-        if (!error_cb) {
-            error_cb = function(e){console.log("Error: ", e);};
-        }
-
-        // Try making a WebSocket call.
-        var socket = this.options.getSocket(this.wsOnMessage);
-        if (socket !== null) {
-            this._wsCall(socket, request, success_cb, error_cb);
-            return;
-        }
-
-        // No WebSocket, and no HTTP backend?  This won't work.
-        if (this.options.ajaxUrl === null) {
-            throw "$.JsonRpcClient.call used with no websocket and no http endpoint.";
-        }
-
-        $.ajax({
-            type     : 'POST',
-            url      : this.options.ajaxUrl,
-            data     : $.toJSON(request),
-            dataType : 'json',
-            cache    : false,
-
-            success  : function(data) {
-                if ('error' in data) error_cb(data.error, this);
-                success_cb(data.result, this);
-            },
-
-            // JSON-RPC Server could return non-200 on error
-            error    : function(jqXHR, textStatus, errorThrown) {
-                try {
-                    var response = $.parseJSON(jqXHR.responseText);
-
-                    if ('console' in window) console.log(response);
-
-                    error_cb(response.error, this);
-                } catch (err) {
-                    // Perhaps the responseText wasn't really a jsonrpc-error.
-                    error_cb({ error: jqXHR.responseText }, this);
-                }
-            }
-        });
-    };
-
-    /**
-     * Notify sends a command to the server that won't need a response.  In http, there is probably
-     * an empty response - that will be dropped, but in ws there should be no response at all.
-     *
-     * This is very similar to call, but has no id and no handling of callbacks.
-     *
-     * @fn notify
-     * @memberof $.JsonRpcClient
-     *
-     * @param method     The method to run on JSON-RPC server.
-     * @param params     The params; an array or object.
-     */
-    $.JsonRpcClient.prototype.notify = function(method, params) {
-        // Construct the JSON-RPC 2.0 request.
-
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        var request = {
-            jsonrpc: '2.0',
-            method:  method,
-            params:  params
-        };
-
-        // Try making a WebSocket call.
-        var socket = this.options.getSocket(this.wsOnMessage);
-        if (socket !== null) {
-            this._wsCall(socket, request);
-            return;
-        }
-
-        // No WebSocket, and no HTTP backend?  This won't work.
-        if (this.options.ajaxUrl === null) {
-            throw "$.JsonRpcClient.notify used with no websocket and no http endpoint.";
-        }
-
-        $.ajax({
-            type     : 'POST',
-            url      : this.options.ajaxUrl,
-            data     : $.toJSON(request),
-            dataType : 'json',
-            cache    : false
-        });
-    };
-
-    /**
-     * Make a batch-call by using a callback.
-     *
-     * The callback will get an object "batch" as only argument.  On batch, you can call the methods
-     * "call" and "notify" just as if it was a normal $.JsonRpcClient object, and all calls will be
-     * sent as a batch call then the callback is done.
-     *
-     * @fn batch
-     * @memberof $.JsonRpcClient
-     *
-     * @param callback    The main function which will get a batch handler to run call and notify on.
-     * @param all_done_cb A callback function to call after all results have been handled.
-     * @param error_cb    A callback function to call if there is an error from the server.
-     *                    Note, that batch calls should always get an overall success, and the
-     *                    only error
-     */
-    $.JsonRpcClient.prototype.batch = function(callback, all_done_cb, error_cb) {
-        var batch = new $.JsonRpcClient._batchObject(this, all_done_cb, error_cb);
-        callback(batch);
-        batch._execute();
-    };
-
-    /**
-     * The default getSocket handler.
-     *
-     * @param onmessage_cb The callback to be bound to onmessage events on the socket.
-     *
-     * @fn _getSocket
-     * @memberof $.JsonRpcClient
-     */
-
-    $.JsonRpcClient.prototype.socketReady = function() {
-        if (this._ws_socket === null || this._ws_socket.readyState > 1) {
-            return false;
-        }
-
-        return true;
-    };
-
-    $.JsonRpcClient.prototype.closeSocket = function() {
-       var self = this;
-        if (self.socketReady()) {
-            self._ws_socket.onclose = function (w) {console.log("Closing Socket");};
-            self._ws_socket.close();
-        }
-    };
-
-    $.JsonRpcClient.prototype.loginData = function(params) {
-       var self = this;
-        self.options.login = params.login;
-        self.options.passwd = params.passwd;
-       self.options.loginParams = params.loginParams;
-       self.options.userVariables = params.userVariables;
-    };
-
-    $.JsonRpcClient.prototype.connectSocket = function(onmessage_cb) {
-        var self = this;
-
-        if (self.to) {
-            clearTimeout(self.to);
-        }
-
-        if (!self.socketReady()) {
-            self.authing = false;
-
-            if (self._ws_socket) {
-                delete self._ws_socket;
-            }
-
-            // No socket, or dying socket, let's get a new one.
-            self._ws_socket = new WebSocket(self.options.socketUrl);
-
-            if (self._ws_socket) {
-                // Set up onmessage handler.
-                self._ws_socket.onmessage = onmessage_cb;
-                self._ws_socket.onclose = function (w) {
-                    if (!self.ws_sleep) {
-                        self.ws_sleep = 1000;
-                    }
-
-                    if (self.options.onWSClose) {
-                        self.options.onWSClose(self);
-                    }
-
-                    if (self.ws_cnt > 10 && self.options.wsFallbackURL) {
-                      self.options.socketUrl = self.options.wsFallbackURL;
-                    }
-
-                    console.error("Websocket Lost " + self.ws_cnt + " sleep: " + self.ws_sleep + "msec");
-
-                    self.to = setTimeout(function() {
-                        console.log("Attempting Reconnection....");
-                        self.connectSocket(onmessage_cb);
-                    }, self.ws_sleep);
-
-                    self.ws_cnt++;
-
-                    if (self.ws_sleep < 3000 && (self.ws_cnt % 10) === 0) {
-                        self.ws_sleep += 1000;
-                    }
-                };
-
-                // Set up sending of message for when the socket is open.
-                self._ws_socket.onopen = function() {
-                    if (self.to) {
-                        clearTimeout(self.to);
-                    }
-                    self.ws_sleep = 1000;
-                    self.ws_cnt = 0;
-                    if (self.options.onWSConnect) {
-                        self.options.onWSConnect(self);
-                    }
-
-                    var req;
-                    // Send the requests.
-                    while ((req = $.JsonRpcClient.q.pop())) {
-                        self._ws_socket.send(req);
-                    }
-                };
-            }
-        }
-
-        return self._ws_socket ? true : false;
-    };
-
-    $.JsonRpcClient.prototype.stopRetrying = function() {
-      if (self.to)
-        clearTimeout(self.to);
-    }
-
-    $.JsonRpcClient.prototype._getSocket = function(onmessage_cb) {
-        // If there is no ws url set, we don't have a socket.
-        // Likewise, if there is no window.WebSocket.
-        if (this.options.socketUrl === null || !("WebSocket" in window)) return null;
-
-        this.connectSocket(onmessage_cb);
-
-        return this._ws_socket;
-    };
-
-    /**
-     * Queue to save messages delivered when websocket is not ready
-     */
-    $.JsonRpcClient.q = [];
-
-    /**
-     * Internal handler to dispatch a JRON-RPC request through a websocket.
-     *
-     * @fn _wsCall
-     * @memberof $.JsonRpcClient
-     */
-    $.JsonRpcClient.prototype._wsCall = function(socket, request, success_cb, error_cb) {
-        var request_json = $.toJSON(request);
-
-        if (socket.readyState < 1) {
-            // The websocket is not open yet; we have to set sending of the message in onopen.
-            self = this; // In closure below, this is set to the WebSocket.  Use self instead.
-            $.JsonRpcClient.q.push(request_json);
-        } else {
-            // We have a socket and it should be ready to send on.
-            socket.send(request_json);
-        }
-
-        // Setup callbacks.  If there is an id, this is a call and not a notify.
-        if ('id' in request && typeof success_cb !== 'undefined') {
-            this._ws_callbacks[request.id] = { request: request_json, request_obj: request, success_cb: success_cb, error_cb: error_cb };
-        }
-    };
-
-    /**
-     * Internal handler for the websocket messages.  It determines if the message is a JSON-RPC
-     * response, and if so, tries to couple it with a given callback.  Otherwise, it falls back to
-     * given external onmessage-handler, if any.
-     *
-     * @param event The websocket onmessage-event.
-     */
-    $.JsonRpcClient.prototype._wsOnMessage = function(event) {
-        // Check if this could be a JSON RPC message.
-        var response;
-
-       // Special sub proto
-       if (event.data[0] == "#" && event.data[1] == "S" && event.data[2] == "P") {
-           if (event.data[3] == "U") {
-               this.up_dur = parseInt(event.data.substring(4));
-           } else if (this.speedCB && event.data[3] == "D") {
-               this.down_dur = parseInt(event.data.substring(4));
-
-               var up_kps = (((this.speedBytes * 8) / (this.up_dur / 1000)) / 1024).toFixed(0);
-               var down_kps = (((this.speedBytes * 8) / (this.down_dur / 1000)) / 1024).toFixed(0);
-               
-               console.info("Speed Test: Up: " + up_kps + " Down: " + down_kps);
-               var cb = this.speedCB;
-               this.speedCB = null;
-               cb(event, {
-                       upDur: this.up_dur,
-                       downDur: this.down_dur,
-                       upKPS: up_kps,
-                       downKPS: down_kps
-               });
-           }
-           
-           return;
-       }
-
-
-        try {
-            response = $.parseJSON(event.data);
-
-            /// @todo Make using the jsonrcp 2.0 check optional, to use this on JSON-RPC 1 backends.
-
-            if (typeof response === 'object' &&
-                'jsonrpc' in response &&
-                response.jsonrpc === '2.0') {
-
-                /// @todo Handle bad response (without id).
-
-                // If this is an object with result, it is a response.
-                if ('result' in response && this._ws_callbacks[response.id]) {
-                    // Get the success callback.
-                    var success_cb = this._ws_callbacks[response.id].success_cb;
-
-    /*
-                    // set the sessid if present
-                    if ('sessid' in response.result && !this.options.sessid || (this.options.sessid != response.result.sessid)) {
-                        this.options.sessid = response.result.sessid;
-                        if (this.options.sessid) {
-                            console.log("setting session UUID to: " + this.options.sessid);
-                        }
-                    }
-    */
-                    // Delete the callback from the storage.
-                    delete this._ws_callbacks[response.id];
-
-                    // Run callback with result as parameter.
-                    success_cb(response.result, this);
-                    return;
-                } else if ('error' in response && this._ws_callbacks[response.id]) {
-                // If this is an object with error, it is an error response.
-
-                // Get the error callback.
-                    var error_cb = this._ws_callbacks[response.id].error_cb;
-                    var orig_req = this._ws_callbacks[response.id].request;
-
-                    // if this is an auth request, send the credentials and resend the failed request
-                    if (!self.authing && response.error.code == -32000 && self.options.login && self.options.passwd) {
-                        self.authing = true;
-
-                        this.call("login", { login: self.options.login, passwd: self.options.passwd, loginParams: self.options.loginParams,
-                                            userVariables: self.options.userVariables},
-                            this._ws_callbacks[response.id].request_obj.method == "login" ?
-                            function(e) {
-                                self.authing = false;
-                                console.log("logged in");
-                                delete self._ws_callbacks[response.id];
-
-                                if (self.options.onWSLogin) {
-                                    self.options.onWSLogin(true, self);
-                                }
-                            }
-
-                            :
-
-                            function(e) {
-                                self.authing = false;
-                                console.log("logged in, resending request id: " + response.id);
-                                var socket = self.options.getSocket(self.wsOnMessage);
-                                if (socket !== null) {
-                                    socket.send(orig_req);
-                                }
-                                if (self.options.onWSLogin) {
-                                    self.options.onWSLogin(true, self);
-                                }
-                            },
-
-                            function(e) {
-                                console.log("error logging in, request id:", response.id);
-                                delete self._ws_callbacks[response.id];
-                                error_cb(response.error, this);
-                                if (self.options.onWSLogin) {
-                                self.options.onWSLogin(false, self);
-                                }
-                            });
-                            return;
-                        }
-
-                        // Delete the callback from the storage.
-                        delete this._ws_callbacks[response.id];
-
-                        // Run callback with the error object as parameter.
-                        error_cb(response.error, this);
-                        return;
-                    }
-                }
-            } catch (err) {
-            // Probably an error while parsing a non json-string as json.  All real JSON-RPC cases are
-            // handled above, and the fallback method is called below.
-            console.log("ERROR: "+ err);
-            return;
-        }
-
-        // This is not a JSON-RPC response.  Call the fallback message handler, if given.
-        if (typeof this.options.onmessage === 'function') {
-            event.eventData = response;
-            if (!event.eventData) {
-                event.eventData = {};
-            }
-
-            var reply = this.options.onmessage(event);
-
-            if (reply && typeof reply === "object" && event.eventData.id) {
-                var msg = {
-                    jsonrpc: "2.0",
-                    id: event.eventData.id,
-                    result: reply
-                };
-
-                var socket = self.options.getSocket(self.wsOnMessage);
-                if (socket !== null) {
-                    socket.send($.toJSON(msg));
-                }
-            }
-        }
-    };
-
-
-    /************************************************************************************************
-     * Batch object with methods
-     ************************************************************************************************/
-
-    /**
-     * Handling object for batch calls.
-     */
-    $.JsonRpcClient._batchObject = function(jsonrpcclient, all_done_cb, error_cb) {
-        // Array of objects to hold the call and notify requests.  Each objects will have the request
-        // object, and unless it is a notify, success_cb and error_cb.
-        this._requests   = [];
-
-        this.jsonrpcclient = jsonrpcclient;
-        this.all_done_cb = all_done_cb;
-        this.error_cb    = typeof error_cb === 'function' ? error_cb : function() {};
-
-    };
-
-    /**
-     * @sa $.JsonRpcClient.prototype.call
-     */
-    $.JsonRpcClient._batchObject.prototype.call = function(method, params, success_cb, error_cb) {
-
-        if (!params) {
-            params = {};
-        }
-
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        if (!success_cb) {
-            success_cb = function(e){console.log("Success: ", e);};
-        }
-
-        if (!error_cb) {
-        error_cb = function(e){console.log("Error: ", e);};
-        }
-
-        this._requests.push({
-            request    : {
-            jsonrpc : '2.0',
-            method  : method,
-            params  : params,
-            id      : this.jsonrpcclient._current_id++  // Use the client's id series.
-        },
-            success_cb : success_cb,
-            error_cb   : error_cb
-        });
-    };
-
-    /**
-     * @sa $.JsonRpcClient.prototype.notify
-     */
-    $.JsonRpcClient._batchObject.prototype.notify = function(method, params) {
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        this._requests.push({
-            request    : {
-                jsonrpc : '2.0',
-                method  : method,
-                params  : params
-            }
-        });
-    };
-
-    /**
-     * Executes the batched up calls.
-     */
-    $.JsonRpcClient._batchObject.prototype._execute = function() {
-        var self = this;
-
-        if (this._requests.length === 0) return; // All done :P
-
-        // Collect all request data and sort handlers by request id.
-        var batch_request = [];
-        var handlers = {};
-        var i = 0;
-        var call;
-        var success_cb;
-        var error_cb;
-
-        // If we have a WebSocket, just send the requests individually like normal calls.
-        var socket = self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);
-        if (socket !== null) {
-            for (i = 0; i < this._requests.length; i++) {
-                call = this._requests[i];
-                success_cb = ('success_cb' in call) ? call.success_cb : undefined;
-                error_cb   = ('error_cb'   in call) ? call.error_cb   : undefined;
-                self.jsonrpcclient._wsCall(socket, call.request, success_cb, error_cb);
-            }
-
-            if (typeof all_done_cb === 'function') all_done_cb(result);
-            return;
-        }
-
-        for (i = 0; i < this._requests.length; i++) {
-            call = this._requests[i];
-            batch_request.push(call.request);
-
-            // If the request has an id, it should handle returns (otherwise it's a notify).
-            if ('id' in call.request) {
-                handlers[call.request.id] = {
-                    success_cb : call.success_cb,
-                    error_cb   : call.error_cb
-                };
-            }
-        }
-
-        success_cb = function(data) { self._batchCb(data, handlers, self.all_done_cb); };
-
-        // No WebSocket, and no HTTP backend?  This won't work.
-        if (self.jsonrpcclient.options.ajaxUrl === null) {
-            throw "$.JsonRpcClient.batch used with no websocket and no http endpoint.";
-        }
-
-        // Send request
-        $.ajax({
-            url      : self.jsonrpcclient.options.ajaxUrl,
-            data     : $.toJSON(batch_request),
-            dataType : 'json',
-            cache    : false,
-            type     : 'POST',
-
-            // Batch-requests should always return 200
-            error    : function(jqXHR, textStatus, errorThrown) {
-                self.error_cb(jqXHR, textStatus, errorThrown);
-            },
-            success  : success_cb
-        });
-    };
-
-    /**
-     * Internal helper to match the result array from a batch call to their respective callbacks.
-     *
-     * @fn _batchCb
-     * @memberof $.JsonRpcClient
-     */
-    $.JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, all_done_cb) {
-        for (var i = 0; i < result.length; i++) {
-            var response = result[i];
-
-            // Handle error
-            if ('error' in response) {
-                if (response.id === null || !(response.id in handlers)) {
-                    // An error on a notify?  Just log it to the console.
-                    if ('console' in window) console.log(response);
-                } else {
-                    handlers[response.id].error_cb(response.error, this);
-                }
-            } else {
-                // Here we should always have a correct id and no error.
-                if (!(response.id in handlers) && 'console' in window) {
-                    console.log(response);
-                } else {
-                    handlers[response.id].success_cb(response.result, this);
-                }
-            }
-        }
-
-        if (typeof all_done_cb === 'function') all_done_cb(result);
-    };
-
-})(jQuery);
-
-/*
- * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
- *
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- *
- * The Initial Developer of the Original Code is
- * Anthony Minessale II <anthm@freeswitch.org>
- * Portions created by the Initial Developer are Copyright (C)
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Anthony Minessale II <anthm@freeswitch.org>
- *
- * jquery.verto.js - Main interface
- *
- */
-
-(function($) {
-    var sources = [];
-
-    var generateGUID = (typeof(window.crypto) !== 'undefined' && typeof(window.crypto.getRandomValues) !== 'undefined') ?
-    function() {
-        // If we have a cryptographically secure PRNG, use that
-        // http://stackoverflow.com/questions/6906916/collisions-when-generating-uuids-in-javascript
-        var buf = new Uint16Array(8);
-        window.crypto.getRandomValues(buf);
-        var S4 = function(num) {
-            var ret = num.toString(16);
-            while (ret.length < 4) {
-                ret = "0" + ret;
-            }
-            return ret;
-        };
-        return (S4(buf[0]) + S4(buf[1]) + "-" + S4(buf[2]) + "-" + S4(buf[3]) + "-" + S4(buf[4]) + "-" + S4(buf[5]) + S4(buf[6]) + S4(buf[7]));
-    }
-
-    :
-
-    function() {
-        // Otherwise, just use Math.random
-        // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
-        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
-            var r = Math.random() * 16 | 0,
-            v = c == 'x' ? r : (r & 0x3 | 0x8);
-            return v.toString(16);
-        });
-    };
-
-    /// MASTER OBJ
-    $.verto = function(options, callbacks) {
-        var verto = this;
-
-        $.verto.saved.push(verto);
-
-        verto.options = $.extend({
-            login: null,
-            passwd: null,
-            socketUrl: null,
-            tag: null,
-           localTag: null,
-            videoParams: {},
-            audioParams: {},
-           loginParams: {},
-           deviceParams: {onResCheck: null},
-           userVariables: {},
-            iceServers: false,
-            ringSleep: 6000,
-      sessid: null,
-      useStream: null
-        }, options);
-
-       if (verto.options.deviceParams.useCamera) {
-           $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, verto.options.deviceParams.onResCheck);
-       }
-
-       if (!verto.options.deviceParams.useMic) {
-           verto.options.deviceParams.useMic = "any";
-       }
-
-       if (!verto.options.deviceParams.useSpeak) {
-           verto.options.deviceParams.useSpeak = "any";
-       }
-
-       if (verto.options.sessid) {
-           verto.sessid = verto.options.sessid;
-       } else {
-            verto.sessid = localStorage.getItem("verto_session_uuid") || generateGUID();
-           localStorage.setItem("verto_session_uuid", verto.sessid);
-       }
-
-        verto.dialogs = {};
-        verto.callbacks = callbacks || {};
-        verto.eventSUBS = {};
-
-        verto.rpcClient = new $.JsonRpcClient({
-            login: verto.options.login,
-            passwd: verto.options.passwd,
-            socketUrl: verto.options.socketUrl,
-            wsFallbackURL: verto.options.wsFallbackURL,
-            turnServer: verto.options.turnServer,
-           loginParams: verto.options.loginParams,
-           userVariables: verto.options.userVariables,
-            sessid: verto.sessid,
-            onmessage: function(e) {
-                return verto.handleMessage(e.eventData);
-            },
-            onWSConnect: function(o) {
-                o.call('login', {});
-            },
-            onWSLogin: function(success) {
-                if (verto.callbacks.onWSLogin) {
-                    verto.callbacks.onWSLogin(verto, success);
-                }
-            },
-            onWSClose: function(success) {
-                if (verto.callbacks.onWSClose) {
-                    verto.callbacks.onWSClose(verto, success);
-                }
-                verto.purge();
-            }
-        });
-
-        var tag = verto.options.tag;
-        if (typeof(tag) === "function") {
-          tag = tag();
-        }
-
-        if (verto.options.ringFile && verto.options.tag) {
-            verto.ringer = $("#" + tag);
-        }
-
-        verto.rpcClient.call('login', {});
-
-    };
-
-
-    $.verto.prototype.deviceParams = function(obj) {
-        var verto = this;
-
-       for (var i in obj) {
-            verto.options.deviceParams[i] = obj[i];
-       }
-
-       if (obj.useCamera) {
-           $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, obj ? obj.onResCheck : undefined);
-       }
-    };
-
-    $.verto.prototype.videoParams = function(obj) {
-        var verto = this;
-
-       for (var i in obj) {
-            verto.options.videoParams[i] = obj[i];
-       }
-    };
-
-    $.verto.prototype.iceServers = function(obj) {
-        var verto = this;
-        verto.options.iceServers = obj;
-    };
-
-    $.verto.prototype.loginData = function(params) {
-       var verto = this;
-        verto.options.login = params.login;
-        verto.options.passwd = params.passwd;
-        verto.rpcClient.loginData(params);
-    };
-
-    $.verto.prototype.logout = function(msg) {
-        var verto = this;
-        verto.rpcClient.closeSocket();
-        if (verto.callbacks.onWSClose) {
-            verto.callbacks.onWSClose(verto, false);
-        }
-        verto.purge();
-    };
-
-    $.verto.prototype.login = function(msg) {
-        var verto = this;
-        verto.logout();
-        verto.rpcClient.call('login', {});
-    };
-
-    $.verto.prototype.message = function(msg) {
-        var verto = this;
-        var err = 0;
-
-        if (!msg.to) {
-            console.error("Missing To");
-            err++;
-        }
-
-        if (!msg.body) {
-            console.error("Missing Body");
-            err++;
-        }
-
-        if (err) {
-            return false;
-        }
-
-        verto.sendMethod("verto.info", {
-            msg: msg
-        });
-
-        return true;
-    };
-
-    $.verto.prototype.processReply = function(method, success, e) {
-        var verto = this;
-        var i;
-
-        //console.log("Response: " + method, success, e);
-
-        switch (method) {
-        case "verto.subscribe":
-            for (i in e.unauthorizedChannels) {
-                drop_bad(verto, e.unauthorizedChannels[i]);
-            }
-            for (i in e.subscribedChannels) {
-                mark_ready(verto, e.subscribedChannels[i]);
-            }
-
-            break;
-        case "verto.unsubscribe":
-            //console.error(e);
-            break;
-        }
-    };
-
-    $.verto.prototype.sendMethod = function(method, params) {
-        var verto = this;
-
-        verto.rpcClient.call(method, params,
-
-        function(e) {
-            /* Success */
-            verto.processReply(method, true, e);
-        },
-
-        function(e) {
-            /* Error */
-            verto.processReply(method, false, e);
-        });
-    };
-
-    function do_sub(verto, channel, obj) {
-
-    }
-
-    function drop_bad(verto, channel) {
-        console.error("drop unauthorized channel: " + channel);
-        delete verto.eventSUBS[channel];
-    }
-
-    function mark_ready(verto, channel) {
-        for (var j in verto.eventSUBS[channel]) {
-            verto.eventSUBS[channel][j].ready = true;
-            console.log("subscribed to channel: " + channel);
-            if (verto.eventSUBS[channel][j].readyHandler) {
-                verto.eventSUBS[channel][j].readyHandler(verto, channel);
-            }
-        }
-    }
-
-    var SERNO = 1;
-
-    function do_subscribe(verto, channel, subChannels, sparams) {
-        var params = sparams || {};
-
-        var local = params.local;
-
-        var obj = {
-            eventChannel: channel,
-            userData: params.userData,
-            handler: params.handler,
-            ready: false,
-            readyHandler: params.readyHandler,
-            serno: SERNO++
-        };
-
-        var isnew = false;
-
-        if (!verto.eventSUBS[channel]) {
-            verto.eventSUBS[channel] = [];
-            subChannels.push(channel);
-            isnew = true;
-        }
-
-        verto.eventSUBS[channel].push(obj);
-
-        if (local) {
-            obj.ready = true;
-            obj.local = true;
-        }
-
-        if (!isnew && verto.eventSUBS[channel][0].ready) {
-            obj.ready = true;
-            if (obj.readyHandler) {
-                obj.readyHandler(verto, channel);
-            }
-        }
-
-        return {
-            serno: obj.serno,
-            eventChannel: channel
-        };
-
-    }
-
-    $.verto.prototype.subscribe = function(channel, sparams) {
-        var verto = this;
-        var r = [];
-        var subChannels = [];
-        var params = sparams || {};
-
-        if (typeof(channel) === "string") {
-            r.push(do_subscribe(verto, channel, subChannels, params));
-        } else {
-            for (var i in channel) {
-                r.push(do_subscribe(verto, channel, subChannels, params));
-            }
-        }
-
-        if (subChannels.length) {
-            verto.sendMethod("verto.subscribe", {
-                eventChannel: subChannels.length == 1 ? subChannels[0] : subChannels,
-                subParams: params.subParams
-            });
-        }
-
-        return r;
-    };
-
-    $.verto.prototype.unsubscribe = function(handle) {
-        var verto = this;
-        var i;
-
-        if (!handle) {
-            for (i in verto.eventSUBS) {
-                if (verto.eventSUBS[i]) {
-                    verto.unsubscribe(verto.eventSUBS[i]);
-                }
-            }
-        } else {
-            var unsubChannels = {};
-            var sendChannels = [];
-            var channel;
-
-            if (typeof(handle) == "string") {
-                delete verto.eventSUBS[handle];
-                unsubChannels[handle]++;
-            } else {
-                for (i in handle) {
-                    if (typeof(handle[i]) == "string") {
-                        channel = handle[i];
-                        delete verto.eventSUBS[channel];
-                        unsubChannels[channel]++;
-                    } else {
-                        var repl = [];
-                        channel = handle[i].eventChannel;
-
-                        for (var j in verto.eventSUBS[channel]) {
-                            if (verto.eventSUBS[channel][j].serno == handle[i].serno) {} else {
-                                repl.push(verto.eventSUBS[channel][j]);
-                            }
-                        }
-
-                        verto.eventSUBS[channel] = repl;
-
-                        if (verto.eventSUBS[channel].length === 0) {
-                            delete verto.eventSUBS[channel];
-                            unsubChannels[channel]++;
-                        }
-                    }
-                }
-            }
-
-            for (var u in unsubChannels) {
-                console.log("Sending Unsubscribe for: ", u);
-                sendChannels.push(u);
-            }
-
-            if (sendChannels.length) {
-                verto.sendMethod("verto.unsubscribe", {
-                    eventChannel: sendChannels.length == 1 ? sendChannels[0] : sendChannels
-                });
-            }
-        }
-    };
-
-    $.verto.prototype.broadcast = function(channel, params) {
-        var verto = this;
-        var msg = {
-            eventChannel: channel,
-            data: {}
-        };
-        for (var i in params) {
-            msg.data[i] = params[i];
-        }
-        verto.sendMethod("verto.broadcast", msg);
-    };
-
-    $.verto.prototype.purge = function(callID) {
-        var verto = this;
-        var x = 0;
-        var i;
-
-        for (i in verto.dialogs) {
-            if (!x) {
-                console.log("purging dialogs");
-            }
-            x++;
-            verto.dialogs[i].setState($.verto.enum.state.purge);
-        }
-
-        for (i in verto.eventSUBS) {
-            if (verto.eventSUBS[i]) {
-                console.log("purging subscription: " + i);
-                delete verto.eventSUBS[i];
-            }
-        }
-    };
-
-    $.verto.prototype.hangup = function(callID) {
-        var verto = this;
-        if (callID) {
-            var dialog = verto.dialogs[callID];
-
-            if (dialog) {
-                dialog.hangup();
-            }
-        } else {
-            for (var i in verto.dialogs) {
-                verto.dialogs[i].hangup();
-            }
-        }
-    };
-
-    $.verto.prototype.newCall = function(args, callbacks) {
-        var verto = this;
-
-        if (!verto.rpcClient.socketReady()) {
-            console.error("Not Connected...");
-            return;
-        }
-
-        if (args["useCamera"]) {
-            verto.options.deviceParams["useCamera"] = args["useCamera"];
-        }
-
-        var dialog = new $.verto.dialog($.verto.enum.direction.outbound, this, args);
-
-        if (callbacks) {
-            dialog.callbacks = callbacks;
-        }
-
-        dialog.invite();
-
-        return dialog;
-    };
-
-    $.verto.prototype.handleMessage = function(data) {
-        var verto = this;
-
-        if (!(data && data.method)) {
-            console.error("Invalid Data", data);
-            return;
-        }
-
-        if (data.params.callID) {
-            var dialog = verto.dialogs[data.params.callID];
-           
-           if (data.method === "verto.attach" && dialog) {
-               delete dialog.verto.dialogs[dialog.callID];
-               dialog.rtc.stop();
-               dialog = null;
-           }
-
-            if (dialog) {
-
-                switch (data.method) {
-                case 'verto.bye':
-                    dialog.hangup(data.params);
-                    break;
-                case 'verto.answer':
-                    dialog.handleAnswer(data.params);
-                    break;
-                case 'verto.media':
-                    dialog.handleMedia(data.params);
-                    break;
-                case 'verto.display':
-                    dialog.handleDisplay(data.params);
-                    break;
-                case 'verto.info':
-                    dialog.handleInfo(data.params);
-                    break;
-                default:
-                    console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED", dialog, data.method);
-                    break;
-                }
-            } else {
-
-                switch (data.method) {
-                case 'verto.attach':
-                    data.params.attach = true;
-
-                    if (data.params.sdp && data.params.sdp.indexOf("m=video") > 0) {
-                        data.params.useVideo = true;
-                    }
-
-                    if (data.params.sdp && data.params.sdp.indexOf("stereo=1") > 0) {
-                        data.params.useStereo = true;
-                    }
-
-                    dialog = new $.verto.dialog($.verto.enum.direction.inbound, verto, data.params);
-                    dialog.setState($.verto.enum.state.recovering);
-
-                    break;
-                case 'verto.invite':
-
-                    if (data.params.sdp && data.params.sdp.indexOf("m=video") > 0) {
-                        data.params.wantVideo = true;
-                    }
-
-                    if (data.params.sdp && data.params.sdp.indexOf("stereo=1") > 0) {
-                        data.params.useStereo = true;
-                    }
-
-                    dialog = new $.verto.dialog($.verto.enum.direction.inbound, verto, data.params);
-                    break;
-                default:
-                    console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED");
-                    break;
-                }
-            }
-
-            return {
-                method: data.method
-            };
-        } else {
-            switch (data.method) {
-            case 'verto.punt':
-                verto.purge();
-               verto.logout();
-               break;
-            case 'verto.event':
-                var list = null;
-                var key = null;
-
-                if (data.params) {
-                    key = data.params.eventChannel;
-                }
-
-                if (key) {
-                    list = verto.eventSUBS[key];
-
-                    if (!list) {
-                        list = verto.eventSUBS[key.split(".")[0]];
-                    }
-                }
-
-                if (!list && key && key === verto.sessid) {
-                    if (verto.callbacks.onMessage) {
-                        verto.callbacks.onMessage(verto, null, $.verto.enum.message.pvtEvent, data.params);
-                    }
-                } else if (!list && key && verto.dialogs[key]) {
-                    verto.dialogs[key].sendMessage($.verto.enum.message.pvtEvent, data.params);
-                } else if (!list) {
-                    if (!key) {
-                        key = "UNDEFINED";
-                    }
-                    console.error("UNSUBBED or invalid EVENT " + key + " IGNORED");
-                } else {
-                    for (var i in list) {
-                        var sub = list[i];
-
-                        if (!sub || !sub.ready) {
-                            console.error("invalid EVENT for " + key + " IGNORED");
-                        } else if (sub.handler) {
-                            sub.handler(verto, data.params, sub.userData);
-                        } else if (verto.callbacks.onEvent) {
-                            verto.callbacks.onEvent(verto, data.params, sub.userData);
-                        } else {
-                            console.log("EVENT:", data.params);
-                        }
-                    }
-                }
-
-                break;
-
-            case "verto.info":
-                if (verto.callbacks.onMessage) {
-                    verto.callbacks.onMessage(verto, null, $.verto.enum.message.info, data.params.msg);
-                }
-                //console.error(data);
-                console.debug("MESSAGE from: " + data.params.msg.from, data.params.msg.body);
-
-                break;
-
-            case 'verto.clientReady':
-                if (verto.callbacks.onMessage) {
-                    verto.callbacks.onMessage(verto, null, $.verto.enum.message.clientReady, data.params);
-                }
-                console.debug("CLIENT READY", data.params);
-                break;
-
-            default:
-                console.error("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED", data.method);
-                break;
-            }
-        }
-    };
-
-    var del_array = function(array, name) {
-        var r = [];
-        var len = array.length;
-
-        for (var i = 0; i < len; i++) {
-            if (array[i] != name) {
-                r.push(array[i]);
-            }
-        }
-
-        return r;
-    };
-
-    var hashArray = function() {
-        var vha = this;
-
-        var hash = {};
-        var array = [];
-
-        vha.reorder = function(a) {
-            array = a;
-            var h = hash;
-            hash = {};
-
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                var key = array[i];
-                if (h[key]) {
-                    hash[key] = h[key];
-                    delete h[key];
-                }
-            }
-            h = undefined;
-        };
-
-        vha.clear = function() {
-            hash = undefined;
-            array = undefined;
-            hash = {};
-            array = [];
-        };
-
-        vha.add = function(name, val, insertAt) {
-            var redraw = false;
-
-            if (!hash[name]) {
-                if (insertAt === undefined || insertAt < 0 || insertAt >= array.length) {
-                    array.push(name);
-                } else {
-                    var x = 0;
-                    var n = [];
-                    var len = array.length;
-
-                    for (var i = 0; i < len; i++) {
-                        if (x++==insertAt) {
-                            n.push(name);
-                        }
-                        n.push(array[i]);
-                    }
-
-                    array = undefined;
-                    array = n;
-                    n = undefined;
-                    redraw = true;
-                }
-            }
-
-            hash[name] = val;
-
-            return redraw;
-        };
-
-        vha.del = function(name) {
-            var r = false;
-
-            if (hash[name]) {
-                array = del_array(array, name);
-                delete hash[name];
-                r = true;
-            } else {
-                console.error("can't del nonexistant key " + name);
-            }
-
-            return r;
-        };
-
-        vha.get = function(name) {
-            return hash[name];
-        };
-
-        vha.order = function() {
-            return array;
-        };
-
-        vha.hash = function() {
-            return hash;
-        };
-
-        vha.indexOf = function(name) {
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                if (array[i] == name) {
-                    return i;
-                }
-            }
-        };
-
-        vha.arrayLen = function() {
-            return array.length;
-        };
-
-        vha.asArray = function() {
-            var r = [];
-
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                var key = array[i];
-                r.push(hash[key]);
-            }
-
-            return r;
-        };
-
-        vha.each = function(cb) {
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                cb(array[i], hash[array[i]]);
-            }
-        };
-
-        vha.dump = function(html) {
-            var str = "";
-
-            vha.each(function(name, val) {
-                str += "name: " + name + " val: " + JSON.stringify(val) + (html ? "<br>" : "\n");
-            });
-
-            return str;
-        };
-
-    };
-
-    $.verto.liveArray = function(verto, context, name, config) {
-        var la = this;
-        var lastSerno = 0;
-        var binding = null;
-        var user_obj = config.userObj;
-        var local = false;
-
-        // Inherit methods of hashArray
-        hashArray.call(la);
-
-        // Save the hashArray add, del, reorder, clear methods so we can make our own.
-        la._add = la.add;
-        la._del = la.del;
-        la._reorder = la.reorder;
-        la._clear = la.clear;
-
-        la.context = context;
-        la.name = name;
-        la.user_obj = user_obj;
-
-        la.verto = verto;
-        la.broadcast = function(channel, obj) {
-            verto.broadcast(channel, obj);
-        };
-        la.errs = 0;
-
-        la.clear = function() {
-            la._clear();
-            lastSerno = 0;
-
-            if (la.onChange) {
-                la.onChange(la, {
-                    action: "clear"
-                });
-            }
-        };
-
-        la.checkSerno = function(serno) {
-            if (serno < 0) {
-                return true;
-            }
-
-            if (lastSerno > 0 && serno != (lastSerno + 1)) {
-                if (la.onErr) {
-                    la.onErr(la, {
-                        lastSerno: lastSerno,
-                        serno: serno
-                    });
-                }
-                la.errs++;
-                console.debug(la.errs);
-                if (la.errs < 3) {
-                    la.bootstrap(la.user_obj);
-                }
-                return false;
-            } else {
-                lastSerno = serno;
-                return true;
-            }
-        };
-
-        la.reorder = function(serno, a) {
-            if (la.checkSerno(serno)) {
-                la._reorder(a);
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "reorder"
-                    });
-                }
-            }
-        };
-
-        la.init = function(serno, val, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "init",
-                        index: index,
-                        key: key,
-                        data: val
-                    });
-                }
-            }
-        };
-
-        la.bootObj = function(serno, val) {
-            if (la.checkSerno(serno)) {
-
-                //la.clear();
-                for (var i in val) {
-                    la._add(val[i][0], val[i][1]);
-                }
-
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "bootObj",
-                        data: val,
-                        redraw: true
-                    });
-                }
-            }
-        };
-
-        // @param serno  La is the serial number for la particular request.
-        // @param key    If looking at it as a hash table, la represents the key in the hashArray object where you want to store the val object.
-        // @param index  If looking at it as an array, la represents the position in the array where you want to store the val object.
-        // @param val    La is the object you want to store at the key or index location in the hash table / array.
-        la.add = function(serno, val, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                var redraw = la._add(key, val, index);
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "add",
-                        index: index,
-                        key: key,
-                        data: val,
-                        redraw: redraw
-                    });
-                }
-            }
-        };
-
-        la.modify = function(serno, val, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                la._add(key, val, index);
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "modify",
-                        key: key,
-                        data: val,
-                        index: index
-                    });
-                }
-            }
-        };
-
-        la.del = function(serno, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                if (index === null || index < 0 || index === undefined) {
-                    index = la.indexOf(key);
-                }
-                var ok = la._del(key);
-
-                if (ok && la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "del",
-                        key: key,
-                        index: index
-                    });
-                }
-            }
-        };
-
-        var eventHandler = function(v, e, la) {
-            var packet = e.data;
-
-            //console.error("READ:", packet);
-
-            if (packet.name != la.name) {
-                return;
-            }
-
-            switch (packet.action) {
-
-            case "init":
-                la.init(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex);
-                break;
-
-            case "bootObj":
-                la.bootObj(packet.wireSerno, packet.data);
-                break;
-            case "add":
-                la.add(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex);
-                break;
-
-            case "modify":
-                if (! (packet.arrIndex || packet.hashKey)) {
-                    console.error("Invalid Packet", packet);
-                } else {
-                    la.modify(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex);
-                }
-                break;
-            case "del":
-                if (! (packet.arrIndex || packet.hashKey)) {
-                    console.error("Invalid Packet", packet);
-                } else {
-                    la.del(packet.wireSerno, packet.hashKey, packet.arrIndex);
-                }
-                break;
-
-            case "clear":
-                la.clear();
-                break;
-
-            case "reorder":
-                la.reorder(packet.wireSerno, packet.order);
-                break;
-
-            default:
-                if (la.checkSerno(packet.wireSerno)) {
-                    if (la.onChange) {
-                        la.onChange(la, {
-                            serno: packet.wireSerno,
-                            action: packet.action,
-                            data: packet.data
-                        });
-                    }
-                }
-                break;
-            }
-        };
-
-        if (la.context) {
-            binding = la.verto.subscribe(la.context, {
-                handler: eventHandler,
-                userData: la,
-                subParams: config.subParams
-            });
-        }
-
-        la.destroy = function() {
-            la._clear();
-            la.verto.unsubscribe(binding);
-        };
-
-        la.sendCommand = function(cmd, obj) {
-            var self = la;
-            self.broadcast(self.context, {
-                liveArray: {
-                    command: cmd,
-                    context: self.context,
-                    name: self.name,
-                    obj: obj
-                }
-            });
-        };
-
-        la.bootstrap = function(obj) {
-            var self = la;
-            la.sendCommand("bootstrap", obj);
-            //self.heartbeat();
-        };
-
-        la.changepage = function(obj) {
-            var self = la;
-            self.clear();
-            self.broadcast(self.context, {
-                liveArray: {
-                    command: "changepage",
-                    context: la.context,
-                    name: la.name,
-                    obj: obj
-                }
-            });
-        };
-
-        la.heartbeat = function(obj) {
-            var self = la;
-
-            var callback = function() {
-                self.heartbeat.call(self, obj);
-            };
-            self.broadcast(self.context, {
-                liveArray: {
-                    command: "heartbeat",
-                    context: self.context,
-                    name: self.name,
-                    obj: obj
-                }
-            });
-            self.hb_pid = setTimeout(callback, 30000);
-        };
-
-        la.bootstrap(la.user_obj);
-    };
-
-    $.verto.liveTable = function(verto, context, name, jq, config) {
-        var dt;
-        var la = new $.verto.liveArray(verto, context, name, {
-            subParams: config.subParams
-        });
-        var lt = this;
-
-        lt.liveArray = la;
-        lt.dataTable = dt;
-        lt.verto = verto;
-
-        lt.destroy = function() {
-            if (dt) {
-                dt.fnDestroy();
-            }
-            if (la) {
-                la.destroy();
-            }
-
-            dt = null;
-            la = null;
-        };
-
-        la.onErr = function(obj, args) {
-            console.error("Error: ", obj, args);
-        };
-
-       /* back compat so jsonstatus can always be enabled */
-       function genRow(data) {
-           if (typeof(data[4]) === "string" && data[4].indexOf("{") > -1) {
-               var tmp = $.parseJSON(data[4]);
-               data[4] = tmp.oldStatus;
-               data[5] = null;
-           }
-           return data;
-       }
-
-       function genArray(obj) {
-           var data = obj.asArray();
-
-            for (var i in data) {
-               data[i] = genRow(data[i]);
-           }
-
-           return data;
-       }
-
-
-        la.onChange = function(obj, args) {
-            var index = 0;
-            var iserr = 0;
-
-            if (!dt) {
-                if (!config.aoColumns) {
-                    if (args.action != "init") {
-                        return;
-                    }
-
-                    config.aoColumns = [];
-
-                    for (var i in args.data) {
-                        config.aoColumns.push({
-                            "sTitle": args.data[i]
-                        });
-                    }
-                }
-
-                dt = jq.dataTable(config);
-            }
-
-            if (dt && (args.action == "del" || args.action == "modify")) {
-                index = args.index;
-
-                if (index === undefined && args.key) {
-                    index = la.indexOf(args.key);
-                }
-
-                if (index === undefined) {
-                    console.error("INVALID PACKET Missing INDEX\n", args);
-                    return;
-                }
-            }
-
-            if (config.onChange) {
-                config.onChange(obj, args);
-            }
-
-            try {
-                switch (args.action) {
-                case "bootObj":
-                    if (!args.data) {
-                        console.error("missing data");
-                        return;
-                    }
-                    dt.fnClearTable();
-                    dt.fnAddData(genArray(obj));
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "add":
-                    if (!args.data) {
-                        console.error("missing data");
-                        return;
-                    }
-                    if (args.redraw > -1) {
-                        // specific position, more costly
-                        dt.fnClearTable();
-                        dt.fnAddData(genArray(obj));
-                    } else {
-                        dt.fnAddData(genRow(args.data));
-                    }
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "modify":
-                    if (!args.data) {
-                        return;
-                    }
-                    //console.debug(args, index);
-                    dt.fnUpdate(genRow(args.data), index);
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "del":
-                    dt.fnDeleteRow(index);
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "clear":
-                    dt.fnClearTable();
-                    break;
-                case "reorder":
-                    // specific position, more costly
-                    dt.fnClearTable();
-                    dt.fnAddData(genArray(obj));
-                    break;
-                case "hide":
-                    jq.hide();
-                    break;
-
-                case "show":
-                    jq.show();
-                    break;
-
-                }
-            } catch(err) {
-                console.error("ERROR: " + err);
-                iserr++;
-            }
-
-            if (iserr) {
-                obj.errs++;
-                if (obj.errs < 3) {
-                    obj.bootstrap(obj.user_obj);
-                }
-            } else {
-                obj.errs = 0;
-            }
-
-        };
-
-        la.onChange(la, {
-            action: "init"
-        });
-
-    };
-
-    var CONFMAN_SERNO = 1;
-
-    /*
-        Conference Manager without jQuery table.
-     */
-
-    $.verto.conf = function(verto, params) {
-        var conf = this;
-
-        conf.params = $.extend({
-            dialog: null,
-            hasVid: false,
-            laData: null,
-            onBroadcast: null,
-            onLaChange: null,
-            onLaRow: null
-        }, params);
-
-        conf.verto = verto;
-        conf.serno = CONFMAN_SERNO++;
-
-        createMainModeratorMethods();
-
-        verto.subscribe(conf.params.laData.modChannel, {
-            handler: function(v, e) {
-                if (conf.params.onBroadcast) {
-                    conf.params.onBroadcast(verto, conf, e.data);
-                }
-            }
-        });
-
-        verto.subscribe(conf.params.laData.infoChannel, {
-            handler: function(v, e) {
-                if (typeof(conf.params.infoCallback) === "function") {
-                    conf.params.infoCallback(v,e);
-                }
-            }
-        });
-
-        verto.subscribe(conf.params.laData.chatChannel, {
-            handler: function(v, e) {
-                if (typeof(conf.params.chatCallback) === "function") {
-                    conf.params.chatCallback(v,e);
-                }
-            }
-        });
-    };
-
-    $.verto.conf.prototype.modCommand = function(cmd, id, value) {
-        var conf = this;
-
-        conf.verto.rpcClient.call("verto.broadcast", {
-            "eventChannel": conf.params.laData.modChannel,
-            "data": {
-                "application": "conf-control",
-                "command": cmd,
-                "id": id,
-                "value": value
-            }
-        });
-    };
-
-    $.verto.conf.prototype.destroy = function() {
-        var conf = this;
-
-        conf.destroyed = true;
-        conf.params.onBroadcast(conf.verto, conf, 'destroy');
-
-        if (conf.params.laData.modChannel) {
-            conf.verto.unsubscribe(conf.params.laData.modChannel);
-        }
-
-        if (conf.params.laData.chatChannel) {
-            conf.verto.unsubscribe(conf.params.laData.chatChannel);
-        }
-
-        if (conf.params.laData.infoChannel) {
-            conf.verto.unsubscribe(conf.params.laData.infoChannel);
-        }
-    };
-
-    function createMainModeratorMethods() {
-        $.verto.conf.prototype.listVideoLayouts = function() {
-            this.modCommand("list-videoLayouts", null, null);
-        };
-
-        $.verto.conf.prototype.play = function(file) {
-            this.modCommand("play", null, file);
-        };
-
-        $.verto.conf.prototype.stop = function() {
-            this.modCommand("stop", null, "all");
-        };
-
-        $.verto.conf.prototype.deaf = function(memberID) {
-            this.modCommand("deaf", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.undeaf = function(memberID) {
-            this.modCommand("undeaf", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.record = function(file) {
-            this.modCommand("recording", null, ["start", file]);
-        };
-
-        $.verto.conf.prototype.stopRecord = function() {
-            this.modCommand("recording", null, ["stop", "all"]);
-        };
-
-        $.verto.conf.prototype.snapshot = function(file) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-write-png", null, file);
-        };
-
-        $.verto.conf.prototype.setVideoLayout = function(layout, canvasID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-           if (canvasID) {
-               this.modCommand("vid-layout", null, [layout, canvasID]);
-           } else {
-               this.modCommand("vid-layout", null, layout);
-           }
-        };
-
-        $.verto.conf.prototype.kick = function(memberID) {
-            this.modCommand("kick", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.muteMic = function(memberID) {
-            this.modCommand("tmute", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.muteVideo = function(memberID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("tvmute", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.presenter = function(memberID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-res-id", parseInt(memberID), "presenter");
-        };
-
-        $.verto.conf.prototype.videoFloor = function(memberID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-floor", parseInt(memberID), "force");
-        };
-
-        $.verto.conf.prototype.banner = function(memberID, text) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-banner", parseInt(memberID), escape(text));
-        };
-
-        $.verto.conf.prototype.volumeDown = function(memberID) {
-            this.modCommand("volume_out", parseInt(memberID), "down");
-        };
-
-        $.verto.conf.prototype.volumeUp = function(memberID) {
-            this.modCommand("volume_out", parseInt(memberID), "up");
-        };
-
-        $.verto.conf.prototype.gainDown = function(memberID) {
-            this.modCommand("volume_in", parseInt(memberID), "down");
-        };
-
-        $.verto.conf.prototype.gainUp = function(memberID) {
-            this.modCommand("volume_in", parseInt(memberID), "up");
-        };
-
-        $.verto.conf.prototype.transfer = function(memberID, exten) {
-            this.modCommand("transfer", parseInt(memberID), exten);
-        };
-
-        $.verto.conf.prototype.sendChat = function(message, type) {
-            var conf = this;
-            conf.verto.rpcClient.call("verto.broadcast", {
-                "eventChannel": conf.params.laData.chatChannel,
-                "data": {
-                    "action": "send",
-                    "message": message,
-                    "type": type
-                }
-            });
-        };
-            
-    }
-
-    $.verto.modfuncs = {};
-
-    $.verto.confMan = function(verto, params) {
-        var confMan = this;
-
-        confMan.params = $.extend({
-            tableID: null,
-            statusID: null,
-            mainModID: null,
-            dialog: null,
-            hasVid: false,
-            laData: null,
-            onBroadcast: null,
-            onLaChange: null,
-            onLaRow: null
-        }, params);
-
-        confMan.verto = verto;
-        confMan.serno = CONFMAN_SERNO++;
-       confMan.canvasCount = confMan.params.laData.canvasCount;
-       
-        function genMainMod(jq) {
-            var play_id = "play_" + confMan.serno;
-            var stop_id = "stop_" + confMan.serno;
-            var recording_id = "recording_" + confMan.serno;
-            var snapshot_id = "snapshot_" + confMan.serno;
-            var rec_stop_id = "recording_stop" + confMan.serno;
-            var div_id = "confman_" + confMan.serno;
-
-            var html =  "<div id='" + div_id + "'><br>" +
-               "<button class='ctlbtn' id='" + play_id + "'>Play</button>" +
-               "<button class='ctlbtn' id='" + stop_id + "'>Stop</button>" +
-               "<button class='ctlbtn' id='" + recording_id + "'>Record</button>" +
-               "<button class='ctlbtn' id='" + rec_stop_id + "'>Record Stop</button>" +
-               (confMan.params.hasVid ? "<button class='ctlbtn' id='" + snapshot_id + "'>PNG Snapshot</button>" : "") +
-               "<br><br></div>";
-
-            jq.html(html);
-
-           $.verto.modfuncs.change_video_layout = function(id, canvas_id) {        
-               var val = $("#" + id + " option:selected").text();
-               if (val !== "none") {
-                    confMan.modCommand("vid-layout", null, [val, canvas_id]);
-               }
-           };
-
-           if (confMan.params.hasVid) {
-               for (var j = 0; j < confMan.canvasCount; j++) {
-                   var vlayout_id = "confman_vid_layout_" + j + "_" + confMan.serno;
-                   var vlselect_id = "confman_vl_select_" + j + "_" + confMan.serno;
-               
-
-                   var vlhtml =  "<div id='" + vlayout_id + "'><br>" +
-                       "<b>Video Layout Canvas " + (j+1) + 
-                       "</b> <select onChange='$.verto.modfuncs.change_video_layout(\"" + vlayout_id + "\", \"" + (j+1) + "\")' id='" + vlselect_id + "'></select> " +
-                       "<br><br></div>";
-                   jq.append(vlhtml);
-               }
-
-               $("#" + snapshot_id).click(function() {
-                    var file = prompt("Please enter file name", "");
-                   if (file) {
-                       confMan.modCommand("vid-write-png", null, file);
-                   }
-               });
-           }
-
-            $("#" + play_id).click(function() {
-                var file = prompt("Please enter file name", "");
-               if (file) {
-                    confMan.modCommand("play", null, file);
-               }
-            });
-
-            $("#" + stop_id).click(function() {
-                confMan.modCommand("stop", null, "all");
-            });
-
-            $("#" + recording_id).click(function() {
-                var file = prompt("Please enter file name", "");
-               if (file) {
-                    confMan.modCommand("recording", null, ["start", file]);
-               }
-            });
-
-            $("#" + rec_stop_id).click(function() {
-                confMan.modCommand("recording", null, ["stop", "all"]);
-            });
-
-        }
-
-        function genControls(jq, rowid) {
-            var x = parseInt(rowid);
-            var kick_id = "kick_" + x;
-            var canvas_in_next_id = "canvas_in_next_" + x;
-            var canvas_in_prev_id = "canvas_in_prev_" + x;
-            var canvas_out_next_id = "canvas_out_next_" + x;
-            var canvas_out_prev_id = "canvas_out_prev_" + x;
-
-            var canvas_in_set_id = "canvas_in_set_" + x;
-            var canvas_out_set_id = "canvas_out_set_" + x;
-
-            var layer_set_id = "layer_set_" + x;
-            var layer_next_id = "layer_next_" + x;
-            var layer_prev_id = "layer_prev_" + x;
-           
-            var tmute_id = "tmute_" + x;
-            var tvmute_id = "tvmute_" + x;
-            var vbanner_id = "vbanner_" + x;
-            var tvpresenter_id = "tvpresenter_" + x;
-            var tvfloor_id = "tvfloor_" + x;
-            var box_id = "box_" + x;
-            var gainup_id = "gain_in_up" + x;
-            var gaindn_id = "gain_in_dn" + x;
-            var volup_id = "vol_in_up" + x;
-            var voldn_id = "vol_in_dn" + x;
-            var transfer_id = "transfer" + x;
-           
-
-            var html = "<div id='" + box_id + "'>";
-
-           html += "<b>General Controls</b><hr noshade>";
-
-            html += "<button class='ctlbtn' id='" + kick_id + "'>Kick</button>" +
-                "<button class='ctlbtn' id='" + tmute_id + "'>Mute</button>" +
-                "<button class='ctlbtn' id='" + gainup_id + "'>Gain -</button>" +
-                "<button class='ctlbtn' id='" + gaindn_id + "'>Gain +</button>" +
-                "<button class='ctlbtn' id='" + voldn_id + "'>Vol -</button>" +
-                "<button class='ctlbtn' id='" + volup_id + "'>Vol +</button>" +
-                "<button class='ctlbtn' id='" + transfer_id + "'>Transfer</button>";
-               
-           if (confMan.params.hasVid) {
-               html += "<br><br><b>Video Controls</b><hr noshade>";
-
-
-                html += "<button class='ctlbtn' id='" + tvmute_id + "'>VMute</button>" +
-                    "<button class='ctlbtn' id='" + tvpresenter_id + "'>Presenter</button>" +
-                    "<button class='ctlbtn' id='" + tvfloor_id + "'>Vid Floor</button>" +
-                    "<button class='ctlbtn' id='" + vbanner_id + "'>Banner</button>";
-
-               if (confMan.canvasCount > 1) {
-                    html += "<br><br><b>Canvas Controls</b><hr noshade>" +
-                       "<button class='ctlbtn' id='" + canvas_in_set_id + "'>Set Input Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_in_prev_id + "'>Prev Input Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_in_next_id + "'>Next Input Canvas</button>" +
-                       
-                   "<br>" +
-                       
-                   "<button class='ctlbtn' id='" + canvas_out_set_id + "'>Set Watching Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_out_prev_id + "'>Prev Watching Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_out_next_id + "'>Next Watching Canvas</button>";
-               }
-               
-               html += "<br>" +
-
-                "<button class='ctlbtn' id='" + layer_set_id + "'>Set Layer</button>" +
-                    "<button class='ctlbtn' id='" + layer_prev_id + "'>Prev Layer</button>" +
-                    "<button class='ctlbtn' id='" + layer_next_id + "'>Next Layer</button>" +
-
-
-
-                    "</div>";
-            }
-
-            jq.html(html);
-
-
-            if (!jq.data("mouse")) {
-                $("#" + box_id).hide();
-            }
-
-            jq.mouseover(function(e) {
-                jq.data({"mouse": true});
-                $("#" + box_id).show();
-            });
-
-            jq.mouseout(function(e) {
-                jq.data({"mouse": false});
-                $("#" + box_id).hide();
-            });
-
-            $("#" + transfer_id).click(function() {
-                var xten = prompt("Enter Extension");
-               if (xten) {
-                    confMan.modCommand("transfer", x, xten);
-               }
-            });
-
-            $("#" + kick_id).click(function() {
-                confMan.modCommand("kick", x);
-            });
-
-
-            $("#" + layer_set_id).click(function() {
-                var cid = prompt("Please enter layer ID", "");
-               if (cid) {
-                    confMan.modCommand("vid-layer", x, cid);
-               }
-            });
-
-            $("#" + layer_next_id).click(function() {
-                confMan.modCommand("vid-layer", x, "next");
-            });
-            $("#" + layer_prev_id).click(function() {
-                confMan.modCommand("vid-layer", x, "prev");
-            });
-
-            $("#" + canvas_in_set_id).click(function() {
-                var cid = prompt("Please enter canvas ID", "");
-               if (cid) {
-                    confMan.modCommand("vid-canvas", x, cid);
-               }
-            });
-
-            $("#" + canvas_out_set_id).click(function() {
-                var cid = prompt("Please enter canvas ID", "");
-               if (cid) {
-                    confMan.modCommand("vid-watching-canvas", x, cid);
-               }
-            });
-
-            $("#" + canvas_in_next_id).click(function() {
-                confMan.modCommand("vid-canvas", x, "next");
-            });
-            $("#" + canvas_in_prev_id).click(function() {
-                confMan.modCommand("vid-canvas", x, "prev");
-            });
-
-
-            $("#" + canvas_out_next_id).click(function() {
-                confMan.modCommand("vid-watching-canvas", x, "next");
-            });
-            $("#" + canvas_out_prev_id).click(function() {
-                confMan.modCommand("vid-watching-canvas", x, "prev");
-            });
-           
-            $("#" + tmute_id).click(function() {
-                confMan.modCommand("tmute", x);
-            });
-
-           if (confMan.params.hasVid) {
-               $("#" + tvmute_id).click(function() {
-                    confMan.modCommand("tvmute", x);
-               });
-               $("#" + tvpresenter_id).click(function() {
-                    confMan.modCommand("vid-res-id", x, "presenter");
-               });
-               $("#" + tvfloor_id).click(function() {
-                    confMan.modCommand("vid-floor", x, "force");
-               });
-               $("#" + vbanner_id).click(function() {
-                    var text = prompt("Please enter text", "");
-                   if (text) {
-                       confMan.modCommand("vid-banner", x, escape(text));
-                   }
-               });
-           }
-
-            $("#" + gainup_id).click(function() {
-                confMan.modCommand("volume_in", x, "up");
-            });
-
-            $("#" + gaindn_id).click(function() {
-                confMan.modCommand("volume_in", x, "down");
-            });
-
-            $("#" + volup_id).click(function() {
-                confMan.modCommand("volume_out", x, "up");
-            });
-
-            $("#" + voldn_id).click(function() {
-                confMan.modCommand("volume_out", x, "down");
-            });
-
-            return html;
-        }
-
-        var atitle = "";
-        var awidth = 0;
-
-        //$(".jsDataTable").width(confMan.params.hasVid ? "900px" : "800px");
-
-        verto.subscribe(confMan.params.laData.infoChannel, {
-            handler: function(v, e) {
-                if (typeof(confMan.params.infoCallback) === "function") {
-                    confMan.params.infoCallback(v,e);
-                }
-            }
-        });
-
-       verto.subscribe(confMan.params.laData.chatChannel, {
-           handler: function(v, e) {
-               if (typeof(confMan.params.chatCallback) === "function") {
-                   confMan.params.chatCallback(v,e);
-               }
-           }
-       });
-
-        if (confMan.params.laData.role === "moderator") {
-            atitle = "Action";
-            awidth = 600;
-
-            if (confMan.params.mainModID) {
-                genMainMod($(confMan.params.mainModID));
-                $(confMan.params.displayID).html("Moderator Controls Ready<br><br>");
-            } else {
-                $(confMan.params.mainModID).html("");
-            }
-
-            verto.subscribe(confMan.params.laData.modChannel, {
-                handler: function(v, e) {
-                    //console.error("MODDATA:", e.data);
-                    if (confMan.params.onBroadcast) {
-                        confMan.params.onBroadcast(verto, confMan, e.data);
-                    }
-
-                   if (e.data["conf-command"] === "list-videoLayouts") {
-                       for (var j = 0; j < confMan.canvasCount; j++) {
-                           var vlselect_id = "#confman_vl_select_" + j + "_" + confMan.serno;
-                           var vlayout_id = "#confman_vid_layout_" + j + "_" + confMan.serno;
-                           
-                           var x = 0;
-                           var options;
-                           
-                           $(vlselect_id).selectmenu({});
-                           $(vlselect_id).selectmenu("enable");
-                           $(vlselect_id).empty();
-                           
-                           $(vlselect_id).append(new Option("Choose a Layout", "none"));
-
-                           if (e.data.responseData) {
-                               var rdata = [];
-
-                               for (var i in e.data.responseData) {
-                                   rdata.push(e.data.responseData[i].name);
-                               }
-                               
-                               options = rdata.sort(function(a, b) {
-                                   var ga = a.substring(0, 6) == "group:" ? true : false;
-                                   var gb = b.substring(0, 6) == "group:" ? true : false;
-                                   
-                                   if ((ga || gb) && ga != gb) {
-                                       return ga ? -1 : 1;
-                                   }
-                                   
-                                   return ( ( a == b ) ? 0 : ( ( a > b ) ? 1 : -1 ) );
-                               });
-
-                               for (var i in options) {
-                                   $(vlselect_id).append(new Option(options[i], options[i]));
-                                   x++;
-                               }
-                           }
-
-                           if (x) {
-                               $(vlselect_id).selectmenu('refresh', true);
-                           } else {
-                               $(vlayout_id).hide();
-                           }
-                       }
-                   } else {
-
-                       if (!confMan.destroyed && confMan.params.displayID) {
-                            $(confMan.params.displayID).html(e.data.response + "<br><br>");
-                            if (confMan.lastTimeout) {
-                               clearTimeout(confMan.lastTimeout);
-                               confMan.lastTimeout = 0;
-                            }
-                            confMan.lastTimeout = setTimeout(function() { $(confMan.params.displayID).html(confMan.destroyed ? "" : "Moderator Controls Ready<br><br>");}, 4000);
-                       }
-                   }
-                }
-            });
-
-
-           if (confMan.params.hasVid) {
-               confMan.modCommand("list-videoLayouts", null, null);
-           }
-        }
-
-        var row_callback = null;
-
-        if (confMan.params.laData.role === "moderator") {
-            row_callback = function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
-                if (!aData[5]) {
-                    var $row = $('td:eq(5)', nRow);
-                    genControls($row, aData);
-
-                    if (confMan.params.onLaRow) {
-                        confMan.params.onLaRow(verto, confMan, $row, aData);
-                    }
-                }
-            };
-        }
-
-        confMan.lt = new $.verto.liveTable(verto, confMan.params.laData.laChannel, confMan.params.laData.laName, $(confMan.params.tableID), {
-            subParams: {
-                callID: confMan.params.dialog ? confMan.params.dialog.callID : null
-            },
-
-            "onChange": function(obj, args) {
-                $(confMan.params.statusID).text("Conference Members: " + " (" + obj.arrayLen() + " Total)");
-                if (confMan.params.onLaChange) {
-                    confMan.params.onLaChange(verto, confMan, $.verto.enum.confEvent.laChange, obj, args);
-                }
-            },
-
-            "aaData": [],
-            "aoColumns": [
-                {
-                    "sTitle": "ID",
-                    "sWidth": "50"
-                },
-                {
-                    "sTitle": "Number",
-                   "sWidth": "250"
-                },
-                {
-                    "sTitle": "Name",
-                   "sWidth": "250"
-                },
-                {
-                    "sTitle": "Codec",
-                    "sWidth": "100"
-                },
-                {
-                    "sTitle": "Status",
-                    "sWidth": confMan.params.hasVid ? "200px" : "150px"
-                },
-                {
-                    "sTitle": atitle,
-                    "sWidth": awidth,
-                }
-            ],
-            "bAutoWidth": true,
-            "bDestroy": true,
-            "bSort": false,
-            "bInfo": false,
-            "bFilter": false,
-            "bLengthChange": false,
-            "bPaginate": false,
-            "iDisplayLength": 1400,
-
-            "oLanguage": {
-                "sEmptyTable": "The Conference is Empty....."
-            },
-
-            "fnRowCallback": row_callback
-
-        });
-    };
-
-    $.verto.confMan.prototype.modCommand = function(cmd, id, value) {
-        var confMan = this;
-
-        confMan.verto.rpcClient.call("verto.broadcast", {
-            "eventChannel": confMan.params.laData.modChannel,
-            "data": {
-               "application": "conf-control",
-               "command": cmd,
-               "id": id,
-               "value": value
-            }
-       });
-    };
-
-    $.verto.confMan.prototype.sendChat = function(message, type) {
-        var confMan = this;
-        confMan.verto.rpcClient.call("verto.broadcast", {
-            "eventChannel": confMan.params.laData.chatChannel,
-            "data": {
-               "action": "send",
-               "message": message,
-               "type": type
-            }
-       });
-    };
-
-
-    $.verto.confMan.prototype.destroy = function() {
-        var confMan = this;
-
-        confMan.destroyed = true;
-
-        if (confMan.lt) {
-            confMan.lt.destroy();
-        }
-
-        if (confMan.params.laData.chatChannel) {
-            confMan.verto.unsubscribe(confMan.params.laData.chatChannel);
-        }
-
-        if (confMan.params.laData.modChannel) {
-            confMan.verto.unsubscribe(confMan.params.laData.modChannel);
-        }
-
-        if (confMan.params.mainModID) {
-            $(confMan.params.mainModID).html("");
-        }
-    };
-
-    $.verto.dialog = function(direction, verto, params) {
-        var dialog = this;
-
-        dialog.params = $.extend({
-            useVideo: verto.options.useVideo,
-            useStereo: verto.options.useStereo,
-           screenShare: false,
-           useCamera: false,
-           useMic: verto.options.deviceParams.useMic,
-           useSpeak: verto.options.deviceParams.useSpeak,
-            tag: verto.options.tag,
-            localTag: verto.options.localTag,
-            login: verto.options.login,
-      videoParams: verto.options.videoParams,
-      useStream: verto.options.useStream,
-        }, params);
-       
-
-       if (!dialog.params.screenShare) {
-           dialog.params.useCamera = verto.options.deviceParams.useCamera;
-       }
-
-        dialog.verto = verto;
-        dialog.direction = direction;
-        dialog.lastState = null;
-        dialog.state = dialog.lastState = $.verto.enum.state.new;
-        dialog.callbacks = verto.callbacks;
-        dialog.answered = false;
-        dialog.attach = params.attach || false;
-       dialog.screenShare = params.screenShare || false;
-       dialog.useCamera = dialog.params.useCamera;
-       dialog.useMic = dialog.params.useMic;
-       dialog.useSpeak = dialog.params.useSpeak;
-       
-        if (dialog.params.callID) {
-            dialog.callID = dialog.params.callID;
-        } else {
-            dialog.callID = dialog.params.callID = generateGUID();
-        }
-
-       if (typeof(dialog.params.tag) === "function") {
-               dialog.params.tag = dialog.params.tag();
-       }
-       
-        if (dialog.params.tag) {
-            dialog.audioStream = document.getElementById(dialog.params.tag);
-
-            if (dialog.params.useVideo) {
-                dialog.videoStream = dialog.audioStream;
-            }
-        } //else conjure one TBD
-
-        if (dialog.params.localTag) {
-           dialog.localVideo = document.getElementById(dialog.params.localTag);
-       }
-
-        dialog.verto.dialogs[dialog.callID] = dialog;
-
-        var RTCcallbacks = {};
-
-        if (dialog.direction == $.verto.enum.direction.inbound) {
-            if (dialog.params.display_direction === "outbound") {
-                dialog.params.remote_caller_id_name = dialog.params.caller_id_name;
-                dialog.params.remote_caller_id_number = dialog.params.caller_id_number;
-            } else {
-                dialog.params.remote_caller_id_name = dialog.params.callee_id_name;
-                dialog.params.remote_caller_id_number = dialog.params.callee_id_number;
-            }
-
-            if (!dialog.params.remote_caller_id_name) {
-                dialog.params.remote_caller_id_name = "Nobody";
-            }
-
-            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 { //answer
-                dialog.setState($.verto.enum.state.answering);
-
-                dialog.sendMethod(dialog.attach ? "verto.attach" : "verto.answer", {
-                    sdp: dialog.rtc.mediaData.SDP
-                });
-            }
-        };
-
-        RTCcallbacks.onICE = function(rtc) {
-            //console.log("cand", rtc.mediaData.candidate);
-            if (rtc.type == "offer") {
-                console.log("offer", rtc.mediaData.candidate);
-                return;
-            }
-        };
-
-        RTCcallbacks.onStream = function(rtc, stream) {
-            if (dialog.callbacks.permissionCallback &&
-                typeof dialog.callbacks.permissionCallback.onGranted === 'function') {
-                dialog.callbacks.permissionCallback.onGranted(stream);
-            }
-            else if (dialog.verto.options.permissionCallback &&
-                typeof dialog.verto.options.permissionCallback.onGranted === 'function'){
-                dialog.verto.options.permissionCallback.onGranted(stream);
-            }
-            console.log("stream started");
-        };
-
-        RTCcallbacks.onRemoteStream = function(rtc, stream) {
-          if (typeof dialog.callbacks.onRemoteStream === 'function') {
-            dialog.callbacks.onRemoteStream(stream, dialog);
-          }
-          console.log("remote stream started");
-        };
-
-        RTCcallbacks.onError = function(e) {
-            if (dialog.callbacks.permissionCallback &&
-                typeof dialog.callbacks.permissionCallback.onDenied === 'function') {
-                dialog.callbacks.permissionCallback.onDenied();
-            }
-            else 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,
-      turnServer: verto.options.turnServer,
-      useStream: dialog.params.useStream
-        });
-
-        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 ((obj.noDialogParams && i != "callID")) {
-               continue;
-           }
-
-           obj.dialogParams[i] = dialog.params[i];
-        }
-       
-       delete obj.noDialogParams;
-
-        dialog.verto.rpcClient.call(method, obj,
-
-        function(e) {
-            /* Success */
-            dialog.processReply(method, true, e);
-        },
-
-        function(e) {
-            /* Error */
-            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;
-    }
-
-
-    // Attach audio output device to video element using device/sink ID.                                                                                           
-    function find_name(id) {
-       for (var i in $.verto.audioOutDevices) {
-           var source = $.verto.audioOutDevices[i];
-           if (source.id === id) {
-               return(source.label);
-           }
-       }
-
-       return id;
-    }
-
-    $.verto.dialog.prototype.setAudioPlaybackDevice = function(sinkId, callback, arg) {
-       var dialog = this;
-       var element = dialog.audioStream;
-
-       if (typeof element.sinkId !== 'undefined') {
-           var devname = find_name(sinkId);
-           console.info("Dialog: " + dialog.callID + " Setting speaker:", element, devname);
-
-           element.setSinkId(sinkId)
-               .then(function() {
-                   console.log("Dialog: " + dialog.callID + ' Success, audio output device attached: ' + sinkId);
-                   if (callback) {
-                       callback(true, devname, arg);
-                   }
-               })
-               .catch(function(error) {
-                   var errorMessage = error;
-                   if (error.name === 'SecurityError') {
-                       errorMessage = "Dialog: " + dialog.callID + ' You need to use HTTPS for selecting audio output ' +
-                           'device: ' + error;
-                   }
-                   if (callback) {
-                       callback(false, null, arg);
-                   }
-                   console.error(errorMessage);
-               });
-       } else {
-           console.warn("Dialog: " + dialog.callID + ' Browser does not support output device selection.');
-           if (callback) {
-               callback(false, null, arg);
-           }
-       }
-    }
-
-    $.verto.dialog.prototype.setState = function(state) {
-        var dialog = this;
-
-        if (dialog.state == $.verto.enum.state.ringing) {
-            dialog.stopRinging();
-        }
-
-        if (dialog.state == state || !checkStateChange(dialog.state, state)) {
-            console.error("Dialog " + dialog.callID + ": INVALID state change from " + dialog.state.name + " to " + state.name);
-            dialog.hangup();
-            return false;
-        }
-
-        console.log("Dialog " + dialog.callID + ": state change from " + dialog.state.name + " to " + state.name);
-
-        dialog.lastState = dialog.state;
-        dialog.state = state;
-
-        if (dialog.callbacks.onDialogState) {
-            dialog.callbacks.onDialogState(this);
-        }
-
-        switch (dialog.state) {
-
-        case $.verto.enum.state.early:
-        case $.verto.enum.state.active:
-
-           var speaker = dialog.useSpeak;
-           console.info("Using Speaker: ", speaker);
-
-           if (speaker && speaker !== "any" && speaker !== "none") {
-               setTimeout(function() {
-                   dialog.setAudioPlaybackDevice(speaker);
-               }, 500);
-           }
-
-           break;
-
-        case $.verto.enum.state.trying:
-            setTimeout(function() {
-                if (dialog.state == $.verto.enum.state.trying) {
-                    dialog.setState($.verto.enum.state.hangup);
-                }
-            }, 30000);
-            break;
-        case $.verto.enum.state.purge:
-            dialog.setState($.verto.enum.state.destroy);
-            break;
-        case $.verto.enum.state.hangup:
-
-            if (dialog.lastState.val > $.verto.enum.state.requesting.val && dialog.lastState.val < $.verto.enum.state.hangup.val) {
-                dialog.sendMethod("verto.bye", {});
-            }
-
-            dialog.setState($.verto.enum.state.destroy);
-            break;
-        case $.verto.enum.state.destroy:
-
-            if (typeof(dialog.verto.options.tag) === "function") {
-              $('#' + dialog.params.tag).remove();
-            }
-
-            delete dialog.verto.dialogs[dialog.callID];
-           if (dialog.params.screenShare) {
-               dialog.rtc.stopPeer();
-           } else {
-               dialog.rtc.stop();
-           }
-            break;
-        }
-
-        return true;
-    };
-
-    $.verto.dialog.prototype.processReply = function(method, success, e) {
-        var dialog = this;
-
-        //console.log("Response: " + method + " State:" + dialog.state.name, success, e);
-
-        switch (method) {
-
-        case "verto.answer":
-        case "verto.attach":
-            if (success) {
-                dialog.setState($.verto.enum.state.active);
-            } else {
-                dialog.hangup();
-            }
-            break;
-        case "verto.invite":
-            if (success) {
-                dialog.setState($.verto.enum.state.trying);
-            } else {
-                dialog.setState($.verto.enum.state.destroy);
-            }
-            break;
-
-        case "verto.bye":
-            dialog.hangup();
-            break;
-
-        case "verto.modify":
-            if (e.holdState) {
-                if (e.holdState == "held") {
-                    if (dialog.state != $.verto.enum.state.held) {
-                        dialog.setState($.verto.enum.state.held);
-                    }
-                } else if (e.holdState == "active") {
-                    if (dialog.state != $.verto.enum.state.active) {
-                        dialog.setState($.verto.enum.state.active);
-                    }
-                }
-            }
-
-            if (success) {}
-
-            break;
-
-        default:
-            break;
-        }
-
-    };
-
-    $.verto.dialog.prototype.hangup = function(params) {
-        var dialog = this;
-
-        if (params) {
-            if (params.causeCode) {
-               dialog.causeCode = params.causeCode;
-            }
-
-            if (params.cause) {
-               dialog.cause = params.cause;
-            }
-        }
-
-       if (!dialog.cause && !dialog.causeCode) {
-           dialog.cause = "NORMAL_CLEARING";
-       }
-
-        if (dialog.state.val >= $.verto.enum.state.new.val && dialog.state.val < $.verto.enum.state.hangup.val) {
-            dialog.setState($.verto.enum.state.hangup);
-        } else if (dialog.state.val < $.verto.enum.state.destroy) {
-            dialog.setState($.verto.enum.state.destroy);
-        }
-    };
-
-    $.verto.dialog.prototype.stopRinging = function() {
-        var dialog = this;
-        if (dialog.verto.ringer) {
-            dialog.verto.ringer.stop();
-        }
-    };
-
-    $.verto.dialog.prototype.indicateRing = function() {
-        var dialog = this;
-
-        if (dialog.verto.ringer) {
-            dialog.verto.ringer.attr("src", dialog.verto.options.ringFile)[0].play();
-
-            setTimeout(function() {
-                dialog.stopRinging();
-                if (dialog.state == $.verto.enum.state.ringing) {
-                    dialog.indicateRing();
-                }
-            },
-            dialog.verto.options.ringSleep);
-        }
-    };
-
-    $.verto.dialog.prototype.ring = function() {
-        var dialog = this;
-
-        dialog.setState($.verto.enum.state.ringing);
-        dialog.indicateRing();
-    };
-
-    $.verto.dialog.prototype.useVideo = function(on) {
-        var dialog = this;
-
-        dialog.params.useVideo = on;
-
-        if (on) {
-            dialog.videoStream = dialog.audioStream;
-        } else {
-            dialog.videoStream = null;
-        }
-
-        dialog.rtc.useVideo(dialog.videoStream, dialog.localVideo);
-
-    };
-
-    $.verto.dialog.prototype.setMute = function(what) {
-       var dialog = this;
-       return dialog.rtc.setMute(what);
-    };
-
-    $.verto.dialog.prototype.getMute = function() {
-       var dialog = this; 
-       return dialog.rtc.getMute();
-    };
-
-    $.verto.dialog.prototype.setVideoMute = function(what) {
-       var dialog = this;
-       return dialog.rtc.setVideoMute(what);
-    };
-
-    $.verto.dialog.prototype.getVideoMute = function() {
-       var dialog = this; 
-       return dialog.rtc.getVideoMute();
-    };
-
-    $.verto.dialog.prototype.useStereo = function(on) {
-        var dialog = this;
-
-        dialog.params.useStereo = on;
-        dialog.rtc.useStereo(on);
-    };
-
-    $.verto.dialog.prototype.dtmf = function(digits) {
-        var dialog = this;
-        if (digits) {
-            dialog.sendMethod("verto.info", {
-                dtmf: digits
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.rtt = function(obj) {
-        var dialog = this;
-       var pobj = {};
-
-       if (!obj) {
-           return false;
-       }
-
-       pobj.code = obj.code;
-       pobj.chars = obj.chars;
-
-
-        if (pobj.chars || pobj.code) {
-            dialog.sendMethod("verto.info", {
-                txt: obj,
-               noDialogParams: true
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.transfer = function(dest, params) {
-        var dialog = this;
-        if (dest) {
-            dialog.sendMethod("verto.modify", {
-                action: "transfer",
-                destination: dest,
-                params: params
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.replace = function(replaceCallID, params) {
-        var dialog = this;
-        if (replaceCallID) {
-            dialog.sendMethod("verto.modify", {
-                action: "replace",
-                replaceCallID: replaceCallID,
-                params: params
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.hold = function(params) {
-        var dialog = this;
-
-        dialog.sendMethod("verto.modify", {
-            action: "hold",
-            params: params
-        });
-    };
-
-    $.verto.dialog.prototype.unhold = function(params) {
-        var dialog = this;
-
-        dialog.sendMethod("verto.modify", {
-            action: "unhold",
-            params: params
-        });
-    };
-
-    $.verto.dialog.prototype.toggleHold = function(params) {
-        var dialog = this;
-
-        dialog.sendMethod("verto.modify", {
-            action: "toggleHold",
-            params: params
-        });
-    };
-
-    $.verto.dialog.prototype.message = function(msg) {
-        var dialog = this;
-        var err = 0;
-
-        msg.from = dialog.params.login;
-
-        if (!msg.to) {
-            console.error("Missing To");
-            err++;
-        }
-
-        if (!msg.body) {
-            console.error("Missing Body");
-            err++;
-        }
-
-        if (err) {
-            return false;
-        }
-
-        dialog.sendMethod("verto.info", {
-            msg: msg
-        });
-
-        return true;
-    };
-
-    $.verto.dialog.prototype.answer = function(params) {
-        var dialog = this;
-       
-        if (!dialog.answered) {
-           if (!params) {
-               params = {};
-           }
-
-           params.sdp = dialog.params.sdp;
-
-            if (params) {
-                if (params.useVideo) {
-                    dialog.useVideo(true);
-                }
-               dialog.params.callee_id_name = params.callee_id_name;
-               dialog.params.callee_id_number = params.callee_id_number;
-
-               if (params.useCamera) {
-                   dialog.useCamera = params.useCamera;
-               }
-
-               if (params.useMic) {
-                   dialog.useMic = params.useMic;
-               }
-
-               if (params.useSpeak) {
-                   dialog.useSpeak = params.useSpeak;
-               }
-            }
-           
-            dialog.rtc.createAnswer(params);
-            dialog.answered = true;
-        }
-    };
-
-    $.verto.dialog.prototype.handleAnswer = function(params) {
-        var dialog = this;
-
-        dialog.gotAnswer = true;
-
-        if (dialog.state.val >= $.verto.enum.state.active.val) {
-            return;
-        }
-
-        if (dialog.state.val >= $.verto.enum.state.early.val) {
-            dialog.setState($.verto.enum.state.active);
-        } else {
-            if (dialog.gotEarly) {
-                console.log("Dialog " + dialog.callID + " Got answer while still establishing early media, delaying...");
-            } else {
-                console.log("Dialog " + dialog.callID + " Answering Channel");
-                dialog.rtc.answer(params.sdp, function() {
-                    dialog.setState($.verto.enum.state.active);
-                }, function(e) {
-                    console.error(e);
-                    dialog.hangup();
-                });
-                console.log("Dialog " + dialog.callID + "ANSWER SDP", params.sdp);
-            }
-        }
-
-
-    };
-
-    $.verto.dialog.prototype.cidString = function(enc) {
-        var dialog = this;
-        var party = dialog.params.remote_caller_id_name + (enc ? " &lt;" : " <") + dialog.params.remote_caller_id_number + (enc ? "&gt;" : ">");
-        return party;
-    };
-
-    $.verto.dialog.prototype.sendMessage = function(msg, params) {
-        var dialog = this;
-
-        if (dialog.callbacks.onMessage) {
-            dialog.callbacks.onMessage(dialog.verto, dialog, msg, params);
-        }
-    };
-
-    $.verto.dialog.prototype.handleInfo = function(params) {
-        var dialog = this;
-
-        dialog.sendMessage($.verto.enum.message.info, params);
-
-    };
-
-    $.verto.dialog.prototype.handleDisplay = function(params) {
-        var dialog = this;
-
-        if (params.display_name) {
-            dialog.params.remote_caller_id_name = params.display_name;
-        }
-        if (params.display_number) {
-            dialog.params.remote_caller_id_number = params.display_number;
-        }
-
-        dialog.sendMessage($.verto.enum.message.display, {});
-    };
-
-    $.verto.dialog.prototype.handleMedia = function(params) {
-        var dialog = this;
-
-        if (dialog.state.val >= $.verto.enum.state.early.val) {
-            return;
-        }
-
-        dialog.gotEarly = true;
-
-        dialog.rtc.answer(params.sdp, function() {
-            console.log("Dialog " + dialog.callID + "Establishing early media");
-            dialog.setState($.verto.enum.state.early);
-
-            if (dialog.gotAnswer) {
-                console.log("Dialog " + dialog.callID + "Answering Channel");
-                dialog.setState($.verto.enum.state.active);
-            }
-        }, function(e) {
-            console.error(e);
-            dialog.hangup();
-        });
-        console.log("Dialog " + dialog.callID + "EARLY SDP", params.sdp);
-    };
-
-    $.verto.ENUM = function(s) {
-        var i = 0,
-        o = {};
-        s.split(" ").map(function(x) {
-            o[x] = {
-                name: x,
-                val: i++
-            };
-        });
-        return Object.freeze(o);
-    };
-
-    $.verto.enum = {};
-
-    $.verto.enum.states = Object.freeze({
-        new: {
-            requesting: 1,
-            recovering: 1,
-            ringing: 1,
-            destroy: 1,
-            answering: 1,
-           hangup: 1
-        },
-        requesting: {
-            trying: 1,
-            hangup: 1,
-           active: 1
-        },
-        recovering: {
-            answering: 1,
-            hangup: 1
-        },
-        trying: {
-            active: 1,
-            early: 1,
-            hangup: 1
-        },
-        ringing: {
-            answering: 1,
-            hangup: 1
-        },
-        answering: {
-            active: 1,
-            hangup: 1
-        },
-        active: {
-            answering: 1,
-            requesting: 1,
-            hangup: 1,
-            held: 1
-        },
-        held: {
-            hangup: 1,
-            active: 1
-        },
-        early: {
-            hangup: 1,
-            active: 1
-        },
-        hangup: {
-            destroy: 1
-        },
-        destroy: {},
-        purge: {
-            destroy: 1
-        }
-    });
-
-    $.verto.enum.state = $.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge");
-    $.verto.enum.direction = $.verto.ENUM("inbound outbound");
-    $.verto.enum.message = $.verto.ENUM("display info pvtEvent clientReady");
-
-    $.verto.enum = Object.freeze($.verto.enum);
-
-    $.verto.saved = [];
-    
-    $.verto.unloadJobs = [];
-
-    var unloadEventName = 'beforeunload';
-    // Hacks for Mobile Safari
-    var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
-    if (iOS) {
-      unloadEventName = 'pagehide';
-    }
-
-    $(window).bind(unloadEventName, function() {
-        for (var f in $.verto.unloadJobs) {
-          $.verto.unloadJobs[f]();
-        }
-
-        if ($.verto.haltClosure)
-          return $.verto.haltClosure();
-
-        for (var i in $.verto.saved) {
-            var verto = $.verto.saved[i];
-            if (verto) {
-                verto.purge();
-                verto.logout();
-            }
-        }
-
-        return $.verto.warnOnUnload;
-    });
-
-    $.verto.videoDevices = [];
-    $.verto.audioInDevices = [];
-    $.verto.audioOutDevices = [];
-
-    var checkDevices = function(runtime) {
-       console.info("enumerating devices");
-       var aud_in = [], aud_out = [], vid = [];        
-       var has_video = 0, has_audio = 0;
-       var Xstream;
-
-       function gotDevices(deviceInfos) {
-             // Handles being called several times to update labels. Preserve values.
-           for (var i = 0; i !== deviceInfos.length; ++i) {
-               var deviceInfo = deviceInfos[i];
-               var text = "";
-
-               console.log(deviceInfo);
-               console.log(deviceInfo.kind + ": " + deviceInfo.label + " id = " + deviceInfo.deviceId);
-
-               if (deviceInfo.kind === 'audioinput') {
-                   text = deviceInfo.label || 'microphone ' + (aud_in.length + 1);
-                   aud_in.push({id: deviceInfo.deviceId, kind: "audio_in", label: text});
-               } else if (deviceInfo.kind === 'audiooutput') {
-                   text = deviceInfo.label || 'speaker ' + (aud_out.length + 1);
-                   aud_out.push({id: deviceInfo.deviceId, kind: "audio_out", label: text});
-               } else if (deviceInfo.kind === 'videoinput') {
-                   text = deviceInfo.label || 'camera ' + (vid.length + 1);
-                   vid.push({id: deviceInfo.deviceId, kind: "video", label: text});
-               } else {
-                   console.log('Some other kind of source/device: ', deviceInfo);
-               }
-           }
-
-
-           $.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);
-
-           if (Xstream) {
-               Xstream.getTracks().forEach(function(track) {track.stop();});
-           }
-
-           if (runtime) {
-               runtime(true);
-           }
-       }
-
-
-       
-
-       function handleError(error) {
-           console.log('device enumeration error: ', error);
-           if (runtime) runtime(false);
-       }
-
-
-       function checkTypes(devs) {
-           for (var i = 0; i !== devs.length; ++i) {
-
-               if (devs[i].kind === 'audioinput') {
-                   has_audio++;
-               } else if (devs[i].kind === 'videoinput') {
-                   has_video++;
-               }
-           }
-
-           navigator.getUserMedia({ audio: (has_audio > 0 ? true : false), video: (has_video > 0 ? true : false)},
-                                  function(stream) {
-                                      Xstream = stream;
-                                      navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
-                                  },
-                                  function(err) {
-                                      console.log("The following error occurred: " + err.name);
-                                  }
-                                 );
-       }
-
-       navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);
-
-  };
-
-    $.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);
-(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){
-/*
- *  Copyright (c) 2017 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');
-
-function writeMediaSection(transceiver, caps, type, stream, dtlsRole) {
-  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' : dtlsRole || '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) {
-    // spec.
-    var msid = 'msid:' + stream.id + ' ' +
-        transceiver.rtpSender.track.id + '\r\n';
-    sdp += 'a=' + msid;
-
-    // for Chrome.
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-        ' ' + msid;
-    if (transceiver.sendEncodingParameters[0].rtx) {
-      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-          ' ' + msid;
-      sdp += 'a=ssrc-group:FID ' +
-          transceiver.sendEncodingParameters[0].ssrc + ' ' +
-          transceiver.sendEncodingParameters[0].rtx.ssrc +
-          '\r\n';
-    }
-  }
-  // FIXME: this should be written by writeRtpDescription.
-  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-      ' cname:' + SDPUtils.localCName + '\r\n';
-  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-        ' cname:' + SDPUtils.localCName + '\r\n';
-  }
-  return sdp;
-}
-
-// Edge does not like
-// 1) stun: filtered after 14393 unless ?transport=udp is present
-// 2) turn: that does not have all of turn:host:port?transport=udp
-// 3) turn: with ipv6 addresses
-// 4) turn: occurring muliple times
-function filterIceServers(iceServers, edgeVersion) {
-  var hasTurn = false;
-  iceServers = JSON.parse(JSON.stringify(iceServers));
-  return iceServers.filter(function(server) {
-    if (server && (server.urls || server.url)) {
-      var urls = server.urls || server.url;
-      if (server.url && !server.urls) {
-        console.warn('RTCIceServer.url is deprecated! Use urls instead.');
-      }
-      var isString = typeof urls === 'string';
-      if (isString) {
-        urls = [urls];
-      }
-      urls = urls.filter(function(url) {
-        var validTurn = url.indexOf('turn:') === 0 &&
-            url.indexOf('transport=udp') !== -1 &&
-            url.indexOf('turn:[') === -1 &&
-            !hasTurn;
-
-        if (validTurn) {
-          hasTurn = true;
-          return true;
-        }
-        return url.indexOf('stun:') === 0 && edgeVersion >= 14393 &&
-            url.indexOf('?transport=udp') === -1;
-      });
-
-      delete server.url;
-      server.urls = isString ? urls[0] : urls;
-      return !!urls.length;
-    }
-  });
-}
-
-// Determines the intersection of local and remote capabilities.
-function getCommonCapabilities(localCapabilities, remoteCapabilities) {
-  var commonCapabilities = {
-    codecs: [],
-    headerExtensions: [],
-    fecMechanisms: []
-  };
-
-  var findCodecByPayloadType = function(pt, codecs) {
-    pt = parseInt(pt, 10);
-    for (var i = 0; i < codecs.length; i++) {
-      if (codecs[i].payloadType === pt ||
-          codecs[i].preferredPayloadType === pt) {
-        return codecs[i];
-      }
-    }
-  };
-
-  var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
-    var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
-    var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
-    return lCodec && rCodec &&
-        lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
-  };
-
-  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) {
-        if (lCodec.name.toLowerCase() === 'rtx' &&
-            lCodec.parameters && rCodec.parameters.apt) {
-          // for RTX we need to find the local rtx that has a apt
-          // which points to the same local codec as the remote one.
-          if (!rtxCapabilityMatches(lCodec, rCodec,
-              localCapabilities.codecs, remoteCapabilities.codecs)) {
-            continue;
-          }
-        }
-        rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
-        // number of channels is the highest common number of channels
-        rCodec.numChannels = Math.min(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;
-}
-
-// is action=setLocalDescription with type allowed in signalingState
-function isActionAllowedInSignalingState(action, type, signalingState) {
-  return {
-    offer: {
-      setLocalDescription: ['stable', 'have-local-offer'],
-      setRemoteDescription: ['stable', 'have-remote-offer']
-    },
-    answer: {
-      setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
-      setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
-    }
-  }[type][action].indexOf(signalingState) !== -1;
-}
-
-function maybeAddCandidate(iceTransport, candidate) {
-  // Edge's internal representation adds some fields therefore
-  // not all fieldÑ• are taken into account.
-  var alreadyAdded = iceTransport.getRemoteCandidates()
-      .find(function(remoteCandidate) {
-        return candidate.foundation === remoteCandidate.foundation &&
-            candidate.ip === remoteCandidate.ip &&
-            candidate.port === remoteCandidate.port &&
-            candidate.priority === remoteCandidate.priority &&
-            candidate.protocol === remoteCandidate.protocol &&
-            candidate.type === remoteCandidate.type;
-      });
-  if (!alreadyAdded) {
-    iceTransport.addRemoteCandidate(candidate);
-  }
-  return !alreadyAdded;
-}
-
-
-// https://w3c.github.io/mediacapture-main/#mediastream
-// Helper function to add the track to the stream and
-// dispatch the event ourselves.
-function addTrackToStreamAndFireEvent(track, stream) {
-  stream.addTrack(track);
-  var e = new Event('addtrack'); // TODO: MediaStreamTrackEvent
-  e.track = track;
-  stream.dispatchEvent(e);
-}
-
-function removeTrackFromStreamAndFireEvent(track, stream) {
-  stream.removeTrack(track);
-  var e = new Event('removetrack'); // TODO: MediaStreamTrackEvent
-  e.track = track;
-  stream.dispatchEvent(e);
-}
-
-function fireAddTrack(pc, track, receiver, streams) {
-  var trackEvent = new Event('track');
-  trackEvent.track = track;
-  trackEvent.receiver = receiver;
-  trackEvent.transceiver = {receiver: receiver};
-  trackEvent.streams = streams;
-  window.setTimeout(function() {
-    pc._dispatchEvent('track', trackEvent);
-  });
-}
-
-function makeError(name, description) {
-  var e = new Error(description);
-  e.name = name;
-  return e;
-}
-
-module.exports = function(window, edgeVersion) {
-  var RTCPeerConnection = function(config) {
-    var pc = this;
-
-    var _eventTarget = document.createDocumentFragment();
-    ['addEventListener', 'removeEventListener', 'dispatchEvent']
-        .forEach(function(method) {
-          pc[method] = _eventTarget[method].bind(_eventTarget);
-        });
-
-    this.canTrickleIceCandidates = null;
-
-    this.needNegotiation = false;
-
-    this.localStreams = [];
-    this.remoteStreams = [];
-
-    this.localDescription = null;
-    this.remoteDescription = null;
-
-    this.signalingState = 'stable';
-    this.iceConnectionState = 'new';
-    this.iceGatheringState = 'new';
-
-    config = JSON.parse(JSON.stringify(config || {}));
-
-    this.usingBundle = config.bundlePolicy === 'max-bundle';
-    if (config.rtcpMuxPolicy === 'negotiate') {
-      throw(makeError('NotSupportedError',
-          'rtcpMuxPolicy \'negotiate\' is not supported'));
-    } else if (!config.rtcpMuxPolicy) {
-      config.rtcpMuxPolicy = 'require';
-    }
-
-    switch (config.iceTransportPolicy) {
-      case 'all':
-      case 'relay':
-        break;
-      default:
-        config.iceTransportPolicy = 'all';
-        break;
-    }
-
-    switch (config.bundlePolicy) {
-      case 'balanced':
-      case 'max-compat':
-      case 'max-bundle':
-        break;
-      default:
-        config.bundlePolicy = 'balanced';
-        break;
-    }
-
-    config.iceServers = filterIceServers(config.iceServers || [], edgeVersion);
-
-    this._iceGatherers = [];
-    if (config.iceCandidatePoolSize) {
-      for (var i = config.iceCandidatePoolSize; i > 0; i--) {
-        this._iceGatherers = new window.RTCIceGatherer({
-          iceServers: config.iceServers,
-          gatherPolicy: config.iceTransportPolicy
-        });
-      }
-    } else {
-      config.iceCandidatePoolSize = 0;
-    }
-
-    this._config = config;
-
-    // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
-    // everything that is needed to describe a SDP m-line.
-    this.transceivers = [];
-
-    this._sdpSessionId = SDPUtils.generateSessionId();
-    this._sdpSessionVersion = 0;
-
-    this._dtlsRole = undefined; // role for a=setup to use in answers.
-
-    this._isClosed = false;
-  };
-
-  // set up event handlers on prototype
-  RTCPeerConnection.prototype.onicecandidate = null;
-  RTCPeerConnection.prototype.onaddstream = null;
-  RTCPeerConnection.prototype.ontrack = null;
-  RTCPeerConnection.prototype.onremovestream = null;
-  RTCPeerConnection.prototype.onsignalingstatechange = null;
-  RTCPeerConnection.prototype.oniceconnectionstatechange = null;
-  RTCPeerConnection.prototype.onicegatheringstatechange = null;
-  RTCPeerConnection.prototype.onnegotiationneeded = null;
-  RTCPeerConnection.prototype.ondatachannel = null;
-
-  RTCPeerConnection.prototype._dispatchEvent = function(name, event) {
-    if (this._isClosed) {
-      return;
-    }
-    this.dispatchEvent(event);
-    if (typeof this['on' + name] === 'function') {
-      this['on' + name](event);
-    }
-  };
-
-  RTCPeerConnection.prototype._emitGatheringStateChange = function() {
-    var event = new Event('icegatheringstatechange');
-    this._dispatchEvent('icegatheringstatechange', event);
-  };
-
-  RTCPeerConnection.prototype.getConfiguration = function() {
-    return this._config;
-  };
-
-  RTCPeerConnection.prototype.getLocalStreams = function() {
-    return this.localStreams;
-  };
-
-  RTCPeerConnection.prototype.getRemoteStreams = function() {
-    return this.remoteStreams;
-  };
-
-  // internal helper to create a transceiver object.
-  // (whih is not yet the same as the WebRTC 1.0 transceiver)
-  RTCPeerConnection.prototype._createTransceiver = function(kind) {
-    var hasBundleTransport = this.transceivers.length > 0;
-    var transceiver = {
-      track: null,
-      iceGatherer: null,
-      iceTransport: null,
-      dtlsTransport: null,
-      localCapabilities: null,
-      remoteCapabilities: null,
-      rtpSender: null,
-      rtpReceiver: null,
-      kind: kind,
-      mid: null,
-      sendEncodingParameters: null,
-      recvEncodingParameters: null,
-      stream: null,
-      associatedRemoteMediaStreams: [],
-      wantReceive: true
-    };
-    if (this.usingBundle && hasBundleTransport) {
-      transceiver.iceTransport = this.transceivers[0].iceTransport;
-      transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
-    } else {
-      var transports = this._createIceAndDtlsTransports();
-      transceiver.iceTransport = transports.iceTransport;
-      transceiver.dtlsTransport = transports.dtlsTransport;
-    }
-    this.transceivers.push(transceiver);
-    return transceiver;
-  };
-
-  RTCPeerConnection.prototype.addTrack = function(track, stream) {
-    var alreadyExists = this.transceivers.find(function(s) {
-      return s.track === track;
-    });
-
-    if (alreadyExists) {
-      throw makeError('InvalidAccessError', 'Track already exists.');
-    }
-
-    if (this.signalingState === 'closed') {
-      throw makeError('InvalidStateError',
-          'Attempted to call addTrack on a closed peerconnection.');
-    }
-
-    var transceiver;
-    for (var i = 0; i < this.transceivers.length; i++) {
-      if (!this.transceivers[i].track &&
-          this.transceivers[i].kind === track.kind) {
-        transceiver = this.transceivers[i];
-      }
-    }
-    if (!transceiver) {
-      transceiver = this._createTransceiver(track.kind);
-    }
-
-    this._maybeFireNegotiationNeeded();
-
-    if (this.localStreams.indexOf(stream) === -1) {
-      this.localStreams.push(stream);
-    }
-
-    transceiver.track = track;
-    transceiver.stream = stream;
-    transceiver.rtpSender = new window.RTCRtpSender(track,
-        transceiver.dtlsTransport);
-    return transceiver.rtpSender;
-  };
-
-  RTCPeerConnection.prototype.addStream = function(stream) {
-    var pc = this;
-    if (edgeVersion >= 15025) {
-      stream.getTracks().forEach(function(track) {
-        pc.addTrack(track, stream);
-      });
-    } else {
-      // Clone is necessary for local demos mostly, attaching directly
-      // to two different senders does not work (build 10547).
-      // Fixed in 15025 (or earlier)
-      var clonedStream = stream.clone();
-      stream.getTracks().forEach(function(track, idx) {
-        var clonedTrack = clonedStream.getTracks()[idx];
-        track.addEventListener('enabled', function(event) {
-          clonedTrack.enabled = event.enabled;
-        });
-      });
-      clonedStream.getTracks().forEach(function(track) {
-        pc.addTrack(track, clonedStream);
-      });
-    }
-  };
-
-  RTCPeerConnection.prototype.removeTrack = function(sender) {
-    if (!(sender instanceof window.RTCRtpSender)) {
-      throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' +
-          'does not implement interface RTCRtpSender.');
-    }
-
-    var transceiver = this.transceivers.find(function(t) {
-      return t.rtpSender === sender;
-    });
-
-    if (!transceiver) {
-      throw makeError('InvalidAccessError',
-          'Sender was not created by this connection.');
-    }
-    var stream = transceiver.stream;
-
-    transceiver.rtpSender.stop();
-    transceiver.rtpSender = null;
-    transceiver.track = null;
-    transceiver.stream = null;
-
-    // remove the stream from the set of local streams
-    var localStreams = this.transceivers.map(function(t) {
-      return t.stream;
-    });
-    if (localStreams.indexOf(stream) === -1 &&
-        this.localStreams.indexOf(stream) > -1) {
-      this.localStreams.splice(this.localStreams.indexOf(stream), 1);
-    }
-
-    this._maybeFireNegotiationNeeded();
-  };
-
-  RTCPeerConnection.prototype.removeStream = function(stream) {
-    var pc = this;
-    stream.getTracks().forEach(function(track) {
-      var sender = pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-      if (sender) {
-        pc.removeTrack(sender);
-      }
-    });
-  };
-
-  RTCPeerConnection.prototype.getSenders = function() {
-    return this.transceivers.filter(function(transceiver) {
-      return !!transceiver.rtpSender;
-    })
-    .map(function(transceiver) {
-      return transceiver.rtpSender;
-    });
-  };
-
-  RTCPeerConnection.prototype.getReceivers = function() {
-    return this.transceivers.filter(function(transceiver) {
-      return !!transceiver.rtpReceiver;
-    })
-    .map(function(transceiver) {
-      return transceiver.rtpReceiver;
-    });
-  };
-
-
-  RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex,
-      usingBundle) {
-    var pc = this;
-    if (usingBundle && sdpMLineIndex > 0) {
-      return this.transceivers[0].iceGatherer;
-    } else if (this._iceGatherers.length) {
-      return this._iceGatherers.shift();
-    }
-    var iceGatherer = new window.RTCIceGatherer({
-      iceServers: this._config.iceServers,
-      gatherPolicy: this._config.iceTransportPolicy
-    });
-    Object.defineProperty(iceGatherer, 'state',
-        {value: 'new', writable: true}
-    );
-
-    this.transceivers[sdpMLineIndex].candidates = [];
-    this.transceivers[sdpMLineIndex].bufferCandidates = function(event) {
-      var end = !event.candidate || Object.keys(event.candidate).length === 0;
-      // polyfill since RTCIceGatherer.state is not implemented in
-      // Edge 10547 yet.
-      iceGatherer.state = end ? 'completed' : 'gathering';
-      if (pc.transceivers[sdpMLineIndex].candidates !== null) {
-        pc.transceivers[sdpMLineIndex].candidates.push(event.candidate);
-      }
-    };
-    iceGatherer.addEventListener('localcandidate',
-      this.transceivers[sdpMLineIndex].bufferCandidates);
-    return iceGatherer;
-  };
-
-  // start gathering from an RTCIceGatherer.
-  RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) {
-    var pc = this;
-    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
-    if (iceGatherer.onlocalcandidate) {
-      return;
-    }
-    var candidates = this.transceivers[sdpMLineIndex].candidates;
-    this.transceivers[sdpMLineIndex].candidates = null;
-    iceGatherer.removeEventListener('localcandidate',
-      this.transceivers[sdpMLineIndex].bufferCandidates);
-    iceGatherer.onlocalcandidate = function(evt) {
-      if (pc.usingBundle && sdpMLineIndex > 0) {
-        // if we know that we use bundle we can drop candidates with
-        // Ñ•dpMLineIndex > 0. If we don't do this then our state gets
-        // confused since we dispose the extra ice gatherer.
-        return;
-      }
-      var event = new Event('icecandidate');
-      event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
-
-      var cand = evt.candidate;
-      // Edge emits an empty object for RTCIceCandidateComplete‥
-      var end = !cand || Object.keys(cand).length === 0;
-      if (end) {
-        // polyfill since RTCIceGatherer.state is not implemented in
-        // Edge 10547 yet.
-        if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') {
-          iceGatherer.state = 'completed';
-        }
-      } else {
-        if (iceGatherer.state === 'new') {
-          iceGatherer.state = 'gathering';
-        }
-        // RTCIceCandidate doesn't have a component, needs to be added
-        cand.component = 1;
-        event.candidate.candidate = SDPUtils.writeCandidate(cand);
-      }
-
-      // update local description.
-      var sections = SDPUtils.splitSections(pc.localDescription.sdp);
-      if (!end) {
-        sections[event.candidate.sdpMLineIndex + 1] +=
-            'a=' + event.candidate.candidate + '\r\n';
-      } else {
-        sections[event.candidate.sdpMLineIndex + 1] +=
-            'a=end-of-candidates\r\n';
-      }
-      pc.localDescription.sdp = sections.join('');
-      var complete = pc.transceivers.every(function(transceiver) {
-        return transceiver.iceGatherer &&
-            transceiver.iceGatherer.state === 'completed';
-      });
-
-      if (pc.iceGatheringState !== 'gathering') {
-        pc.iceGatheringState = 'gathering';
-        pc._emitGatheringStateChange();
-      }
-
-      // Emit candidate. Also emit null candidate when all gatherers are
-      // complete.
-      if (!end) {
-        pc._dispatchEvent('icecandidate', event);
-      }
-      if (complete) {
-        pc._dispatchEvent('icecandidate', new Event('icecandidate'));
-        pc.iceGatheringState = 'complete';
-        pc._emitGatheringStateChange();
-      }
-    };
-
-    // emit already gathered candidates.
-    window.setTimeout(function() {
-      candidates.forEach(function(candidate) {
-        var e = new Event('RTCIceGatherEvent');
-        e.candidate = candidate;
-        iceGatherer.onlocalcandidate(e);
-      });
-    }, 0);
-  };
-
-  // Create ICE transport and DTLS transport.
-  RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
-    var pc = this;
-    var iceTransport = new window.RTCIceTransport(null);
-    iceTransport.onicestatechange = function() {
-      pc._updateConnectionState();
-    };
-
-    var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
-    dtlsTransport.ondtlsstatechange = function() {
-      pc._updateConnectionState();
-    };
-    dtlsTransport.onerror = function() {
-      // onerror does not set state to failed by itpc.
-      Object.defineProperty(dtlsTransport, 'state',
-          {value: 'failed', writable: true});
-      pc._updateConnectionState();
-    };
-
-    return {
-      iceTransport: iceTransport,
-      dtlsTransport: dtlsTransport
-    };
-  };
-
-  // Destroy ICE gatherer, ICE transport and DTLS transport.
-  // Without triggering the callbacks.
-  RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
-      sdpMLineIndex) {
-    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
-    if (iceGatherer) {
-      delete iceGatherer.onlocalcandidate;
-      delete this.transceivers[sdpMLineIndex].iceGatherer;
-    }
-    var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
-    if (iceTransport) {
-      delete iceTransport.onicestatechange;
-      delete this.transceivers[sdpMLineIndex].iceTransport;
-    }
-    var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
-    if (dtlsTransport) {
-      delete dtlsTransport.ondtlsstatechange;
-      delete dtlsTransport.onerror;
-      delete this.transceivers[sdpMLineIndex].dtlsTransport;
-    }
-  };
-
-  // Start the RTP Sender and Receiver for a transceiver.
-  RTCPeerConnection.prototype._transceive = function(transceiver,
-      send, recv) {
-    var params = getCommonCapabilities(transceiver.localCapabilities,
-        transceiver.remoteCapabilities);
-    if (send && transceiver.rtpSender) {
-      params.encodings = transceiver.sendEncodingParameters;
-      params.rtcp = {
-        cname: SDPUtils.localCName,
-        compound: transceiver.rtcpParameters.compound
-      };
-      if (transceiver.recvEncodingParameters.length) {
-        params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
-      }
-      transceiver.rtpSender.send(params);
-    }
-    if (recv && transceiver.rtpReceiver && params.codecs.length > 0) {
-      // remove RTX field in Edge 14942
-      if (transceiver.kind === 'video'
-          && transceiver.recvEncodingParameters
-          && edgeVersion < 15019) {
-        transceiver.recvEncodingParameters.forEach(function(p) {
-          delete p.rtx;
-        });
-      }
-      if (transceiver.recvEncodingParameters.length) {
-        params.encodings = transceiver.recvEncodingParameters;
-      }
-      params.rtcp = {
-        compound: transceiver.rtcpParameters.compound
-      };
-      if (transceiver.rtcpParameters.cname) {
-        params.rtcp.cname = transceiver.rtcpParameters.cname;
-      }
-      if (transceiver.sendEncodingParameters.length) {
-        params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
-      }
-      transceiver.rtpReceiver.receive(params);
-    }
-  };
-
-  RTCPeerConnection.prototype.setLocalDescription = function(description) {
-    var pc = this;
-
-    if (!isActionAllowedInSignalingState('setLocalDescription',
-        description.type, this.signalingState) || this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not set local ' + description.type +
-          ' in state ' + pc.signalingState));
-    }
-
-    var sections;
-    var sessionpart;
-    if (description.type === 'offer') {
-      // 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);
-        pc.transceivers[sdpMLineIndex].localCapabilities = caps;
-      });
-
-      this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-        pc._gather(transceiver.mid, sdpMLineIndex);
-      });
-    } else if (description.type === 'answer') {
-      sections = SDPUtils.splitSections(pc.remoteDescription.sdp);
-      sessionpart = sections.shift();
-      var isIceLite = SDPUtils.matchPrefix(sessionpart,
-          'a=ice-lite').length > 0;
-      sections.forEach(function(mediaSection, sdpMLineIndex) {
-        var transceiver = pc.transceivers[sdpMLineIndex];
-        var iceGatherer = transceiver.iceGatherer;
-        var iceTransport = transceiver.iceTransport;
-        var dtlsTransport = transceiver.dtlsTransport;
-        var localCapabilities = transceiver.localCapabilities;
-        var remoteCapabilities = transceiver.remoteCapabilities;
-
-        // treat bundle-only as not-rejected.
-        var rejected = SDPUtils.isRejected(mediaSection) &&
-            SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
-
-        if (!rejected && !transceiver.isDatachannel) {
-          var remoteIceParameters = SDPUtils.getIceParameters(
-              mediaSection, sessionpart);
-          var remoteDtlsParameters = SDPUtils.getDtlsParameters(
-              mediaSection, sessionpart);
-          if (isIceLite) {
-            remoteDtlsParameters.role = 'server';
-          }
-
-          if (!pc.usingBundle || sdpMLineIndex === 0) {
-            pc._gather(transceiver.mid, sdpMLineIndex);
-            if (iceTransport.state === 'new') {
-              iceTransport.start(iceGatherer, remoteIceParameters,
-                  isIceLite ? 'controlling' : 'controlled');
-            }
-            if (dtlsTransport.state === 'new') {
-              dtlsTransport.start(remoteDtlsParameters);
-            }
-          }
-
-          // Calculate intersection of capabilities.
-          var params = getCommonCapabilities(localCapabilities,
-              remoteCapabilities);
-
-          // Start the RTCRtpSender. The RTCRtpReceiver for this
-          // transceiver has already been started in setRemoteDescription.
-          pc._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 +
-            '"');
-    }
-
-    return Promise.resolve();
-  };
-
-  RTCPeerConnection.prototype.setRemoteDescription = function(description) {
-    var pc = this;
-
-    if (!isActionAllowedInSignalingState('setRemoteDescription',
-        description.type, this.signalingState) || this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not set remote ' + description.type +
-          ' in state ' + pc.signalingState));
-    }
-
-    var streams = {};
-    this.remoteStreams.forEach(function(stream) {
-      streams[stream.id] = stream;
-    });
-    var receiverList = [];
-    var sections = SDPUtils.splitSections(description.sdp);
-    var sessionpart = sections.shift();
-    var isIceLite = SDPUtils.matchPrefix(sessionpart,
-        'a=ice-lite').length > 0;
-    var usingBundle = SDPUtils.matchPrefix(sessionpart,
-        'a=group:BUNDLE ').length > 0;
-    this.usingBundle = usingBundle;
-    var iceOptions = SDPUtils.matchPrefix(sessionpart,
-        'a=ice-options:')[0];
-    if (iceOptions) {
-      this.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
-          .indexOf('trickle') >= 0;
-    } else {
-      this.canTrickleIceCandidates = false;
-    }
-
-    sections.forEach(function(mediaSection, sdpMLineIndex) {
-      var lines = SDPUtils.splitLines(mediaSection);
-      var kind = SDPUtils.getKind(mediaSection);
-      // treat bundle-only as not-rejected.
-      var rejected = SDPUtils.isRejected(mediaSection) &&
-          SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
-      var protocol = lines[0].substr(2).split(' ')[2];
-
-      var direction = SDPUtils.getDirection(mediaSection, sessionpart);
-      var remoteMsid = SDPUtils.parseMsid(mediaSection);
-
-      var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
-
-      // Reject datachannels which are not implemented yet.
-      if (kind === 'application' && protocol === 'DTLS/SCTP') {
-        pc.transceivers[sdpMLineIndex] = {
-          mid: mid,
-          isDatachannel: true
-        };
-        return;
-      }
-
-      var transceiver;
-      var iceGatherer;
-      var iceTransport;
-      var dtlsTransport;
-      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 rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
-
-      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;
-          });
-
-      // Check if we can use BUNDLE and dispose transports.
-      if ((description.type === 'offer' || description.type === 'answer') &&
-          !rejected && usingBundle && sdpMLineIndex > 0 &&
-          pc.transceivers[sdpMLineIndex]) {
-        pc._disposeIceAndDtlsTransports(sdpMLineIndex);
-        pc.transceivers[sdpMLineIndex].iceGatherer =
-            pc.transceivers[0].iceGatherer;
-        pc.transceivers[sdpMLineIndex].iceTransport =
-            pc.transceivers[0].iceTransport;
-        pc.transceivers[sdpMLineIndex].dtlsTransport =
-            pc.transceivers[0].dtlsTransport;
-        if (pc.transceivers[sdpMLineIndex].rtpSender) {
-          pc.transceivers[sdpMLineIndex].rtpSender.setTransport(
-              pc.transceivers[0].dtlsTransport);
-        }
-        if (pc.transceivers[sdpMLineIndex].rtpReceiver) {
-          pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
-              pc.transceivers[0].dtlsTransport);
-        }
-      }
-      if (description.type === 'offer' && !rejected) {
-        transceiver = pc.transceivers[sdpMLineIndex] ||
-            pc._createTransceiver(kind);
-        transceiver.mid = mid;
-
-        if (!transceiver.iceGatherer) {
-          transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
-              usingBundle);
-        }
-
-        if (cands.length && transceiver.iceTransport.state === 'new') {
-          if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {
-            transceiver.iceTransport.setRemoteCandidates(cands);
-          } else {
-            cands.forEach(function(candidate) {
-              maybeAddCandidate(transceiver.iceTransport, candidate);
-            });
-          }
-        }
-
-        localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
-
-        // filter RTX until additional stuff needed for RTX is implemented
-        // in adapter.js
-        if (edgeVersion < 15019) {
-          localCapabilities.codecs = localCapabilities.codecs.filter(
-              function(codec) {
-                return codec.name !== 'rtx';
-              });
-        }
-
-        sendEncodingParameters = transceiver.sendEncodingParameters || [{
-          ssrc: (2 * sdpMLineIndex + 2) * 1001
-        }];
-
-        // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
-        var isNewTrack = false;
-        if (direction === 'sendrecv' || direction === 'sendonly') {
-          isNewTrack = !transceiver.rtpReceiver;
-          rtpReceiver = transceiver.rtpReceiver ||
-              new window.RTCRtpReceiver(transceiver.dtlsTransport, kind);
-
-          if (isNewTrack) {
-            var stream;
-            track = rtpReceiver.track;
-            // FIXME: does not work with Plan B.
-            if (remoteMsid && remoteMsid.stream === '-') {
-              // no-op. a stream id of '-' means: no associated stream.
-            } else if (remoteMsid) {
-              if (!streams[remoteMsid.stream]) {
-                streams[remoteMsid.stream] = new window.MediaStream();
-                Object.defineProperty(streams[remoteMsid.stream], 'id', {
-                  get: function() {
-                    return remoteMsid.stream;
-                  }
-                });
-              }
-              Object.defineProperty(track, 'id', {
-                get: function() {
-                  return remoteMsid.track;
-                }
-              });
-              stream = streams[remoteMsid.stream];
-            } else {
-              if (!streams.default) {
-                streams.default = new window.MediaStream();
-              }
-              stream = streams.default;
-            }
-            if (stream) {
-              addTrackToStreamAndFireEvent(track, stream);
-              transceiver.associatedRemoteMediaStreams.push(stream);
-            }
-            receiverList.push([track, rtpReceiver, stream]);
-          }
-        } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) {
-          transceiver.associatedRemoteMediaStreams.forEach(function(s) {
-            var nativeTrack = s.getTracks().find(function(t) {
-              return t.id === transceiver.rtpReceiver.track.id;
-            });
-            if (nativeTrack) {
-              removeTrackFromStreamAndFireEvent(nativeTrack, s);
-            }
-          });
-          transceiver.associatedRemoteMediaStreams = [];
-        }
-
-        transceiver.localCapabilities = localCapabilities;
-        transceiver.remoteCapabilities = remoteCapabilities;
-        transceiver.rtpReceiver = rtpReceiver;
-        transceiver.rtcpParameters = rtcpParameters;
-        transceiver.sendEncodingParameters = sendEncodingParameters;
-        transceiver.recvEncodingParameters = recvEncodingParameters;
-
-        // Start the RTCRtpReceiver now. The RTPSender is started in
-        // setLocalDescription.
-        pc._transceive(pc.transceivers[sdpMLineIndex],
-            false,
-            isNewTrack);
-      } else if (description.type === 'answer' && !rejected) {
-        transceiver = pc.transceivers[sdpMLineIndex];
-        iceGatherer = transceiver.iceGatherer;
-        iceTransport = transceiver.iceTransport;
-        dtlsTransport = transceiver.dtlsTransport;
-        rtpReceiver = transceiver.rtpReceiver;
-        sendEncodingParameters = transceiver.sendEncodingParameters;
-        localCapabilities = transceiver.localCapabilities;
-
-        pc.transceivers[sdpMLineIndex].recvEncodingParameters =
-            recvEncodingParameters;
-        pc.transceivers[sdpMLineIndex].remoteCapabilities =
-            remoteCapabilities;
-        pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
-
-        if (cands.length && iceTransport.state === 'new') {
-          if ((isIceLite || isComplete) &&
-              (!usingBundle || sdpMLineIndex === 0)) {
-            iceTransport.setRemoteCandidates(cands);
-          } else {
-            cands.forEach(function(candidate) {
-              maybeAddCandidate(transceiver.iceTransport, candidate);
-            });
-          }
-        }
-
-        if (!usingBundle || sdpMLineIndex === 0) {
-          if (iceTransport.state === 'new') {
-            iceTransport.start(iceGatherer, remoteIceParameters,
-                'controlling');
-          }
-          if (dtlsTransport.state === 'new') {
-            dtlsTransport.start(remoteDtlsParameters);
-          }
-        }
-
-        pc._transceive(transceiver,
-            direction === 'sendrecv' || direction === 'recvonly',
-            direction === 'sendrecv' || direction === 'sendonly');
-
-        // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
-        if (rtpReceiver &&
-            (direction === 'sendrecv' || direction === 'sendonly')) {
-          track = rtpReceiver.track;
-          if (remoteMsid) {
-            if (!streams[remoteMsid.stream]) {
-              streams[remoteMsid.stream] = new window.MediaStream();
-            }
-            addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]);
-            receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
-          } else {
-            if (!streams.default) {
-              streams.default = new window.MediaStream();
-            }
-            addTrackToStreamAndFireEvent(track, streams.default);
-            receiverList.push([track, rtpReceiver, streams.default]);
-          }
-        } else {
-          // FIXME: actually the receiver should be created later.
-          delete transceiver.rtpReceiver;
-        }
-      }
-    });
-
-    if (this._dtlsRole === undefined) {
-      this._dtlsRole = description.type === 'offer' ? 'active' : 'passive';
-    }
-
-    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 +
-            '"');
-    }
-    Object.keys(streams).forEach(function(sid) {
-      var stream = streams[sid];
-      if (stream.getTracks().length) {
-        if (pc.remoteStreams.indexOf(stream) === -1) {
-          pc.remoteStreams.push(stream);
-          var event = new Event('addstream');
-          event.stream = stream;
-          window.setTimeout(function() {
-            pc._dispatchEvent('addstream', event);
-          });
-        }
-
-        receiverList.forEach(function(item) {
-          var track = item[0];
-          var receiver = item[1];
-          if (stream.id !== item[2].id) {
-            return;
-          }
-          fireAddTrack(pc, track, receiver, [stream]);
-        });
-      }
-    });
-    receiverList.forEach(function(item) {
-      if (item[2]) {
-        return;
-      }
-      fireAddTrack(pc, item[0], item[1], []);
-    });
-
-    // check whether addIceCandidate({}) was called within four seconds after
-    // setRemoteDescription.
-    window.setTimeout(function() {
-      if (!(pc && pc.transceivers)) {
-        return;
-      }
-      pc.transceivers.forEach(function(transceiver) {
-        if (transceiver.iceTransport &&
-            transceiver.iceTransport.state === 'new' &&
-            transceiver.iceTransport.getRemoteCandidates().length > 0) {
-          console.warn('Timeout for addRemoteCandidate. Consider sending ' +
-              'an end-of-candidates notification');
-          transceiver.iceTransport.addRemoteCandidate({});
-        }
-      });
-    }, 4000);
-
-    return Promise.resolve();
-  };
-
-  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._isClosed = true;
-    this._updateSignalingState('closed');
-  };
-
-  // Update the signaling state.
-  RTCPeerConnection.prototype._updateSignalingState = function(newState) {
-    this.signalingState = newState;
-    var event = new Event('signalingstatechange');
-    this._dispatchEvent('signalingstatechange', event);
-  };
-
-  // Determine whether to fire the negotiationneeded event.
-  RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
-    var pc = this;
-    if (this.signalingState !== 'stable' || this.needNegotiation === true) {
-      return;
-    }
-    this.needNegotiation = true;
-    window.setTimeout(function() {
-      if (pc.needNegotiation === false) {
-        return;
-      }
-      pc.needNegotiation = false;
-      var event = new Event('negotiationneeded');
-      pc._dispatchEvent('negotiationneeded', event);
-    }, 0);
-  };
-
-  // Update the connection state.
-  RTCPeerConnection.prototype._updateConnectionState = function() {
-    var newState;
-    var states = {
-      'new': 0,
-      closed: 0,
-      connecting: 0,
-      checking: 0,
-      connected: 0,
-      completed: 0,
-      disconnected: 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 !== this.iceConnectionState) {
-      this.iceConnectionState = newState;
-      var event = new Event('iceconnectionstatechange');
-      this._dispatchEvent('iceconnectionstatechange', event);
-    }
-  };
-
-  RTCPeerConnection.prototype.createOffer = function() {
-    var pc = this;
-
-    if (this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not call createOffer after close'));
-    }
-
-    var numAudioTracks = this.transceivers.filter(function(t) {
-      return t.kind === 'audio';
-    }).length;
-    var numVideoTracks = this.transceivers.filter(function(t) {
-      return t.kind === 'video';
-    }).length;
-
-    // Determine number of audio and video tracks we need to send/recv.
-    var offerOptions = arguments[0];
-    if (offerOptions) {
-      // Reject Chrome legacy constraints.
-      if (offerOptions.mandatory || offerOptions.optional) {
-        throw new TypeError(
-            'Legacy mandatory/optional constraints not supported.');
-      }
-      if (offerOptions.offerToReceiveAudio !== undefined) {
-        if (offerOptions.offerToReceiveAudio === true) {
-          numAudioTracks = 1;
-        } else if (offerOptions.offerToReceiveAudio === false) {
-          numAudioTracks = 0;
-        } else {
-          numAudioTracks = offerOptions.offerToReceiveAudio;
-        }
-      }
-      if (offerOptions.offerToReceiveVideo !== undefined) {
-        if (offerOptions.offerToReceiveVideo === true) {
-          numVideoTracks = 1;
-        } else if (offerOptions.offerToReceiveVideo === false) {
-          numVideoTracks = 0;
-        } else {
-          numVideoTracks = offerOptions.offerToReceiveVideo;
-        }
-      }
-    }
-
-    this.transceivers.forEach(function(transceiver) {
-      if (transceiver.kind === 'audio') {
-        numAudioTracks--;
-        if (numAudioTracks < 0) {
-          transceiver.wantReceive = false;
-        }
-      } else if (transceiver.kind === 'video') {
-        numVideoTracks--;
-        if (numVideoTracks < 0) {
-          transceiver.wantReceive = false;
-        }
-      }
-    });
-
-    // Create M-lines for recvonly streams.
-    while (numAudioTracks > 0 || numVideoTracks > 0) {
-      if (numAudioTracks > 0) {
-        this._createTransceiver('audio');
-        numAudioTracks--;
-      }
-      if (numVideoTracks > 0) {
-        this._createTransceiver('video');
-        numVideoTracks--;
-      }
-    }
-
-    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId,
-        this._sdpSessionVersion++);
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      // For each track, create an ice gatherer, ice transport,
-      // dtls transport, potentially rtpsender and rtpreceiver.
-      var track = transceiver.track;
-      var kind = transceiver.kind;
-      var mid = SDPUtils.generateIdentifier();
-      transceiver.mid = mid;
-
-      if (!transceiver.iceGatherer) {
-        transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
-            pc.usingBundle);
-      }
-
-      var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
-      // filter RTX until additional stuff needed for RTX is implemented
-      // in adapter.js
-      if (edgeVersion < 15019) {
-        localCapabilities.codecs = localCapabilities.codecs.filter(
-            function(codec) {
-              return codec.name !== 'rtx';
-            });
-      }
-      localCapabilities.codecs.forEach(function(codec) {
-        // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
-        // by adding level-asymmetry-allowed=1
-        if (codec.name === 'H264' &&
-            codec.parameters['level-asymmetry-allowed'] === undefined) {
-          codec.parameters['level-asymmetry-allowed'] = '1';
-        }
-      });
-
-      // generate an ssrc now, to be used later in rtpSender.send
-      var sendEncodingParameters = transceiver.sendEncodingParameters || [{
-        ssrc: (2 * sdpMLineIndex + 1) * 1001
-      }];
-      if (track) {
-        // add RTX
-        if (edgeVersion >= 15019 && kind === 'video' &&
-            !sendEncodingParameters[0].rtx) {
-          sendEncodingParameters[0].rtx = {
-            ssrc: sendEncodingParameters[0].ssrc + 1
-          };
-        }
-      }
-
-      if (transceiver.wantReceive) {
-        transceiver.rtpReceiver = new window.RTCRtpReceiver(
-            transceiver.dtlsTransport, kind);
-      }
-
-      transceiver.localCapabilities = localCapabilities;
-      transceiver.sendEncodingParameters = sendEncodingParameters;
-    });
-
-    // always offer BUNDLE and dispose on return if not supported.
-    if (this._config.bundlePolicy !== 'max-compat') {
-      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
-        return t.mid;
-      }).join(' ') + '\r\n';
-    }
-    sdp += 'a=ice-options:trickle\r\n';
-
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      sdp += writeMediaSection(transceiver, transceiver.localCapabilities,
-          'offer', transceiver.stream, pc._dtlsRole);
-      sdp += 'a=rtcp-rsize\r\n';
-
-      if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' &&
-          (sdpMLineIndex === 0 || !pc.usingBundle)) {
-        transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) {
-          cand.component = 1;
-          sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n';
-        });
-
-        if (transceiver.iceGatherer.state === 'completed') {
-          sdp += 'a=end-of-candidates\r\n';
-        }
-      }
-    });
-
-    var desc = new window.RTCSessionDescription({
-      type: 'offer',
-      sdp: sdp
-    });
-    return Promise.resolve(desc);
-  };
-
-  RTCPeerConnection.prototype.createAnswer = function() {
-    var pc = this;
-
-    if (this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not call createAnswer after close'));
-    }
-
-    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId,
-        this._sdpSessionVersion++);
-    if (this.usingBundle) {
-      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
-        return t.mid;
-      }).join(' ') + '\r\n';
-    }
-    var mediaSectionsInOffer = SDPUtils.splitSections(
-        this.remoteDescription.sdp).length - 1;
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      if (sdpMLineIndex + 1 > mediaSectionsInOffer) {
-        return;
-      }
-      if (transceiver.isDatachannel) {
-        sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
-            'c=IN IP4 0.0.0.0\r\n' +
-            'a=mid:' + transceiver.mid + '\r\n';
-        return;
-      }
-
-      // FIXME: look at direction.
-      if (transceiver.stream) {
-        var localTrack;
-        if (transceiver.kind === 'audio') {
-          localTrack = transceiver.stream.getAudioTracks()[0];
-        } else if (transceiver.kind === 'video') {
-          localTrack = transceiver.stream.getVideoTracks()[0];
-        }
-        if (localTrack) {
-          // add RTX
-          if (edgeVersion >= 15019 && transceiver.kind === 'video' &&
-              !transceiver.sendEncodingParameters[0].rtx) {
-            transceiver.sendEncodingParameters[0].rtx = {
-              ssrc: transceiver.sendEncodingParameters[0].ssrc + 1
-            };
-          }
-        }
-      }
-
-      // Calculate intersection of capabilities.
-      var commonCapabilities = getCommonCapabilities(
-          transceiver.localCapabilities,
-          transceiver.remoteCapabilities);
-
-      var hasRtx = commonCapabilities.codecs.filter(function(c) {
-        return c.name.toLowerCase() === 'rtx';
-      }).length;
-      if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
-        delete transceiver.sendEncodingParameters[0].rtx;
-      }
-
-      sdp += writeMediaSection(transceiver, commonCapabilities,
-          'answer', transceiver.stream, pc._dtlsRole);
-      if (transceiver.rtcpParameters &&
-          transceiver.rtcpParameters.reducedSize) {
-        sdp += 'a=rtcp-rsize\r\n';
-      }
-    });
-
-    var desc = new window.RTCSessionDescription({
-      type: 'answer',
-      sdp: sdp
-    });
-    return Promise.resolve(desc);
-  };
-
-  RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
-    var sections;
-    if (!candidate || candidate.candidate === '') {
-      for (var j = 0; j < this.transceivers.length; j++) {
-        if (this.transceivers[j].isDatachannel) {
-          continue;
-        }
-        this.transceivers[j].iceTransport.addRemoteCandidate({});
-        sections = SDPUtils.splitSections(this.remoteDescription.sdp);
-        sections[j + 1] += 'a=end-of-candidates\r\n';
-        this.remoteDescription.sdp = sections.join('');
-        if (this.usingBundle) {
-          break;
-        }
-      }
-    } else if (!(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) {
-      throw new TypeError('sdpMLineIndex or sdpMid required');
-    } else if (!this.remoteDescription) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not add ICE candidate without a remote description'));
-    } else {
-      var sdpMLineIndex = candidate.sdpMLineIndex;
-      if (candidate.sdpMid) {
-        for (var i = 0; i < this.transceivers.length; i++) {
-          if (this.transceivers[i].mid === candidate.sdpMid) {
-            sdpMLineIndex = i;
-            break;
-          }
-        }
-      }
-      var transceiver = this.transceivers[sdpMLineIndex];
-      if (transceiver) {
-        if (transceiver.isDatachannel) {
-          return Promise.resolve();
-        }
-        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 Promise.resolve();
-        }
-        // Ignore RTCP candidates, we assume RTCP-MUX.
-        if (cand.component && cand.component !== 1) {
-          return Promise.resolve();
-        }
-        // when using bundle, avoid adding candidates to the wrong
-        // ice transport. And avoid adding candidates added in the SDP.
-        if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 &&
-            transceiver.iceTransport !== this.transceivers[0].iceTransport)) {
-          if (!maybeAddCandidate(transceiver.iceTransport, cand)) {
-            return Promise.reject(makeError('OperationError',
-                'Can not add ICE candidate'));
-          }
-        }
-
-        // update the remoteDescription.
-        var candidateString = candidate.candidate.trim();
-        if (candidateString.indexOf('a=') === 0) {
-          candidateString = candidateString.substr(2);
-        }
-        sections = SDPUtils.splitSections(this.remoteDescription.sdp);
-        sections[sdpMLineIndex + 1] += 'a=' +
-            (cand.type ? candidateString : 'end-of-candidates')
-            + '\r\n';
-        this.remoteDescription.sdp = sections.join('');
-      } else {
-        return Promise.reject(makeError('OperationError',
-            'Can not add ICE candidate'));
-      }
-    }
-    return Promise.resolve();
-  };
-
-  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 fixStatsType = function(stat) {
-      return {
-        inboundrtp: 'inbound-rtp',
-        outboundrtp: 'outbound-rtp',
-        candidatepair: 'candidate-pair',
-        localcandidate: 'local-candidate',
-        remotecandidate: 'remote-candidate'
-      }[stat.type] || stat.type;
-    };
-    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) {
-            result[id].type = fixStatsType(result[id]);
-            results.set(id, result[id]);
-          });
-        });
-        resolve(results);
-      });
-    });
-  };
-
-  // legacy callback shims. Should be moved to adapter.js some days.
-  var methods = ['createOffer', 'createAnswer'];
-  methods.forEach(function(method) {
-    var nativeMethod = RTCPeerConnection.prototype[method];
-    RTCPeerConnection.prototype[method] = function() {
-      var args = arguments;
-      if (typeof args[0] === 'function' ||
-          typeof args[1] === 'function') { // legacy
-        return nativeMethod.apply(this, [arguments[2]])
-        .then(function(description) {
-          if (typeof args[0] === 'function') {
-            args[0].apply(null, [description]);
-          }
-        }, function(error) {
-          if (typeof args[1] === 'function') {
-            args[1].apply(null, [error]);
-          }
-        });
-      }
-      return nativeMethod.apply(this, arguments);
-    };
-  });
-
-  methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'];
-  methods.forEach(function(method) {
-    var nativeMethod = RTCPeerConnection.prototype[method];
-    RTCPeerConnection.prototype[method] = function() {
-      var args = arguments;
-      if (typeof args[1] === 'function' ||
-          typeof args[2] === 'function') { // legacy
-        return nativeMethod.apply(this, arguments)
-        .then(function() {
-          if (typeof args[1] === 'function') {
-            args[1].apply(null);
-          }
-        }, function(error) {
-          if (typeof args[2] === 'function') {
-            args[2].apply(null, [error]);
-          }
-        });
-      }
-      return nativeMethod.apply(this, arguments);
-    };
-  });
-
-  // getStats is special. It doesn't have a spec legacy method yet we support
-  // getStats(something, cb) without error callbacks.
-  ['getStats'].forEach(function(method) {
-    var nativeMethod = RTCPeerConnection.prototype[method];
-    RTCPeerConnection.prototype[method] = function() {
-      var args = arguments;
-      if (typeof args[1] === 'function') {
-        return nativeMethod.apply(this, arguments)
-        .then(function() {
-          if (typeof args[1] === 'function') {
-            args[1].apply(null);
-          }
-        });
-      }
-      return nativeMethod.apply(this, arguments);
-    };
-  });
-
-  return RTCPeerConnection;
-};
-
-},{"sdp":2}],2:[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: parseInt(parts[1], 10),
-    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;
-      case 'ufrag':
-        candidate.ufrag = parts[i + 1]; // for backward compability.
-        candidate.usernameFragment = parts[i + 1];
-        break;
-      default: // extension handling, in particular ufrag
-        candidate[parts[i]] = parts[i + 1];
-        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);
-  }
-  if (candidate.ufrag) {
-    sdp.push('ufrag');
-    sdp.push(candidate.ufrag);
-  }
-  return 'candidate:' + sdp.join(' ');
-};
-
-// Parses an ice-options line, returns an array of option tags.
-// a=ice-options:foo bar
-SDPUtils.parseIceOptions = function(line) {
-  return line.substr(14).split(' ');
-}
-
-// 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
-// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
-SDPUtils.parseExtmap = function(line) {
-  var parts = line.substr(9).split(' ');
-  return {
-    id: parseInt(parts[0], 10),
-    direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
-    uri: parts[1]
-  };
-};
-
-// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
-// RTCRtpHeaderExtension.
-SDPUtils.writeExtmap = function(headerExtension) {
-  return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
-      (headerExtension.direction && headerExtension.direction !== 'sendrecv'
-          ? '/' + headerExtension.direction
-          : '') +
-      ' ' + 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 the MID (RFC 5888) from a media section.
-// returns the MID or undefined if no mid line was found.
-SDPUtils.getMid = function(mediaSection) {
-  var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
-  if (mid) {
-    return mid.substr(6);
-  }
-}
-
-SDPUtils.parseFingerprint = function(line) {
-  var parts = line.substr(14).split(' ');
-  return {
-    algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
-    value: parts[1]
-  };
-};
-
-// 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.matchPrefix(mediaSection + sessionpart,
-      'a=fingerprint:');
-  // Note: a=setup line is ignored since we use the 'auto' role.
-  // Note2: 'algorithm' is not case sensitive except in Edge.
-  return {
-    role: 'auto',
-    fingerprints: lines.map(SDPUtils.parseFingerprint)
-  };
-};
-
-// 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);
-  });
-  var maxptime = 0;
-  caps.codecs.forEach(function(codec) {
-    if (codec.maxptime > maxptime) {
-      maxptime = codec.maxptime;
-    }
-  });
-  if (maxptime > 0) {
-    sdp += 'a=maxptime:' + maxptime + '\r\n';
-  }
-  sdp += 'a=rtcp-mux\r\n';
-
-  caps.headerExtensions.forEach(function(extension) {
-    sdp += SDPUtils.writeExtmap(extension);
-  });
-  // FIXME: write fecMechanisms.
-  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: {
-          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) {
-      // use formula from JSEP to convert b=AS to TIAS value.
-      bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95
-          - (50 * 40 * 8);
-    } else {
-      bandwidth = undefined;
-    }
-    encodingParameters.forEach(function(params) {
-      params.maxBitrate = bandwidth;
-    });
-  }
-  return encodingParameters;
-};
-
-// parses http://draft.ortc.org/#rtcrtcpparameters*
-SDPUtils.parseRtcpParameters = function(mediaSection) {
-  var rtcpParameters = {};
-
-  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) {
-    rtcpParameters.cname = remoteSsrc.value;
-    rtcpParameters.ssrc = remoteSsrc.ssrc;
-  }
-
-  // Edge uses the compound attribute instead of reducedSize
-  // compound is !reducedSize
-  var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
-  rtcpParameters.reducedSize = rsize.length > 0;
-  rtcpParameters.compound = rsize.length === 0;
-
-  // parses the rtcp-mux attrÑ–bute.
-  // Note that Edge does not support unmuxed RTCP.
-  var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
-  rtcpParameters.mux = mux.length > 0;
-
-  return rtcpParameters;
-};
-
-// parses either a=msid: or a=ssrc:... msid lines and returns
-// the id of the MediaStream and MediaStreamTrack.
-SDPUtils.parseMsid = function(mediaSection) {
-  var parts;
-  var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
-  if (spec.length === 1) {
-    parts = spec[0].substr(7).split(' ');
-    return {stream: parts[0], track: parts[1]};
-  }
-  var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
-  .map(function(line) {
-    return SDPUtils.parseSsrcMedia(line);
-  })
-  .filter(function(parts) {
-    return parts.attribute === 'msid';
-  });
-  if (planB.length > 0) {
-    parts = planB[0].value.split(' ');
-    return {stream: parts[0], track: parts[1]};
-  }
-};
-
-// Generate a session ID for SDP.
-// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
-// recommends using a cryptographically random +ve 64-bit value
-// but right now this should be acceptable and within the right range
-SDPUtils.generateSessionId = function() {
-  return Math.random().toString().substr(2, 21);
-};
-
-// Write boilder plate for start of SDP
-// sessId argument is optional - if not supplied it will
-// be generated randomly
-// sessVersion is optional and defaults to 2
-SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) {
-  var sessionId;
-  var version = sessVer !== undefined ? sessVer : 2;
-  if (sessId) {
-    sessionId = sessId;
-  } else {
-    sessionId = SDPUtils.generateSessionId();
-  }
-  // FIXME: sess-id should be an NTP timestamp.
-  return 'v=0\r\n' +
-      'o=thisisadapterortc ' + sessionId + ' ' + version + ' 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.direction) {
-    sdp += 'a=' + transceiver.direction + '\r\n';
-  } else 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) {
-    // spec.
-    var msid = 'msid:' + stream.id + ' ' +
-        transceiver.rtpSender.track.id + '\r\n';
-    sdp += 'a=' + msid;
-
-    // for Chrome.
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-        ' ' + msid;
-    if (transceiver.sendEncodingParameters[0].rtx) {
-      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-          ' ' + msid;
-      sdp += 'a=ssrc-group:FID ' +
-          transceiver.sendEncodingParameters[0].ssrc + ' ' +
-          transceiver.sendEncodingParameters[0].rtx.ssrc +
-          '\r\n';
-    }
-  }
-  // FIXME: this should be written by writeRtpDescription.
-  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-      ' cname:' + SDPUtils.localCName + '\r\n';
-  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.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';
-};
-
-SDPUtils.getKind = function(mediaSection) {
-  var lines = SDPUtils.splitLines(mediaSection);
-  var mline = lines[0].split(' ');
-  return mline[0].substr(2);
-};
-
-SDPUtils.isRejected = function(mediaSection) {
-  return mediaSection.split(' ', 2)[1] === '0';
-};
-
-SDPUtils.parseMLine = function(mediaSection) {
-  var lines = SDPUtils.splitLines(mediaSection);
-  var mline = lines[0].split(' ');
-  return {
-    kind: mline[0].substr(2),
-    port: parseInt(mline[1], 10),
-    protocol: mline[2],
-    fmt: mline.slice(3).join(' ')
-  };
-};
-
-// Expose public methods.
-if (typeof module === 'object') {
-  module.exports = SDPUtils;
-}
-
-},{}],3:[function(require,module,exports){
-(function (global){
-/*
- *  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 adapterFactory = require('./adapter_factory.js');
-module.exports = adapterFactory({window: global.window});
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"./adapter_factory.js":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 utils = require('./utils');
-// Shimming starts here.
-module.exports = function(dependencies, opts) {
-  var window = dependencies && dependencies.window;
-
-  var options = {
-    shimChrome: true,
-    shimFirefox: true,
-    shimEdge: true,
-    shimSafari: true,
-  };
-
-  for (var key in opts) {
-    if (hasOwnProperty.call(opts, key)) {
-      options[key] = opts[key];
-    }
-  }
-
-  // Utils.
-  var logging = utils.log;
-  var browserDetails = utils.detectBrowser(window);
-
-  // 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;
-  var commonShim = require('./common_shim') || null;
-
-  // Export to the adapter global object visible in the browser.
-  var adapter = {
-    browserDetails: browserDetails,
-    commonShim: commonShim,
-    extractVersion: utils.extractVersion,
-    disableLog: utils.disableLog,
-    disableWarnings: utils.disableWarnings
-  };
-
-  // Shim browser if found.
-  switch (browserDetails.browser) {
-    case 'chrome':
-      if (!chromeShim || !chromeShim.shimPeerConnection ||
-          !options.shimChrome) {
-        logging('Chrome shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming chrome.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = chromeShim;
-      commonShim.shimCreateObjectURL(window);
-
-      chromeShim.shimGetUserMedia(window);
-      chromeShim.shimMediaStream(window);
-      chromeShim.shimSourceObject(window);
-      chromeShim.shimPeerConnection(window);
-      chromeShim.shimOnTrack(window);
-      chromeShim.shimAddTrackRemoveTrack(window);
-      chromeShim.shimGetSendersWithDtmf(window);
-
-      commonShim.shimRTCIceCandidate(window);
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    case 'firefox':
-      if (!firefoxShim || !firefoxShim.shimPeerConnection ||
-          !options.shimFirefox) {
-        logging('Firefox shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming firefox.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = firefoxShim;
-      commonShim.shimCreateObjectURL(window);
-
-      firefoxShim.shimGetUserMedia(window);
-      firefoxShim.shimSourceObject(window);
-      firefoxShim.shimPeerConnection(window);
-      firefoxShim.shimOnTrack(window);
-      firefoxShim.shimRemoveStream(window);
-
-      commonShim.shimRTCIceCandidate(window);
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    case 'edge':
-      if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) {
-        logging('MS edge shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming edge.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = edgeShim;
-      commonShim.shimCreateObjectURL(window);
-
-      edgeShim.shimGetUserMedia(window);
-      edgeShim.shimPeerConnection(window);
-      edgeShim.shimReplaceTrack(window);
-
-      // the edge shim implements the full RTCIceCandidate object.
-
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    case 'safari':
-      if (!safariShim || !options.shimSafari) {
-        logging('Safari shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming safari.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = safariShim;
-      commonShim.shimCreateObjectURL(window);
-
-      safariShim.shimRTCIceServerUrls(window);
-      safariShim.shimCallbacksAPI(window);
-      safariShim.shimLocalStreamsAPI(window);
-      safariShim.shimRemoteStreamsAPI(window);
-      safariShim.shimTrackEventTransceiver(window);
-      safariShim.shimGetUserMedia(window);
-      safariShim.shimCreateOfferLegacy(window);
-
-      commonShim.shimRTCIceCandidate(window);
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    default:
-      logging('Unsupported browser!');
-      break;
-  }
-
-  return adapter;
-};
-
-},{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":10,"./safari/safari_shim":12,"./utils":13}],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 utils = require('../utils.js');
-var logging = utils.log;
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimMediaStream: function(window) {
-    window.MediaStream = window.MediaStream || window.webkitMediaStream;
-  },
-
-  shimOnTrack: function(window) {
-    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.addEventListener('track', this._ontrack = f);
-        }
-      });
-      var origSetRemoteDescription =
-          window.RTCPeerConnection.prototype.setRemoteDescription;
-      window.RTCPeerConnection.prototype.setRemoteDescription = function() {
-        var pc = this;
-        if (!pc._ontrackpoly) {
-          pc._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 receiver;
-              if (window.RTCPeerConnection.prototype.getReceivers) {
-                receiver = pc.getReceivers().find(function(r) {
-                  return r.track && r.track.id === te.track.id;
-                });
-              } else {
-                receiver = {track: te.track};
-              }
-
-              var event = new Event('track');
-              event.track = te.track;
-              event.receiver = receiver;
-              event.transceiver = {receiver: receiver};
-              event.streams = [e.stream];
-              pc.dispatchEvent(event);
-            });
-            e.stream.getTracks().forEach(function(track) {
-              var receiver;
-              if (window.RTCPeerConnection.prototype.getReceivers) {
-                receiver = pc.getReceivers().find(function(r) {
-                  return r.track && r.track.id === track.id;
-                });
-              } else {
-                receiver = {track: track};
-              }
-              var event = new Event('track');
-              event.track = track;
-              event.receiver = receiver;
-              event.transceiver = {receiver: receiver};
-              event.streams = [e.stream];
-              pc.dispatchEvent(event);
-            });
-          };
-          pc.addEventListener('addstream', pc._ontrackpoly);
-        }
-        return origSetRemoteDescription.apply(pc, arguments);
-      };
-    } else if (!('RTCRtpTransceiver' in window)) {
-      utils.wrapPeerConnectionEvent(window, 'track', function(e) {
-        if (!e.transceiver) {
-          e.transceiver = {receiver: e.receiver};
-        }
-        return e;
-      });
-    }
-  },
-
-  shimGetSendersWithDtmf: function(window) {
-    // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
-    if (typeof window === 'object' && window.RTCPeerConnection &&
-        !('getSenders' in window.RTCPeerConnection.prototype) &&
-        'createDTMFSender' in window.RTCPeerConnection.prototype) {
-      var shimSenderWithDtmf = function(pc, track) {
-        return {
-          track: track,
-          get dtmf() {
-            if (this._dtmf === undefined) {
-              if (track.kind === 'audio') {
-                this._dtmf = pc.createDTMFSender(track);
-              } else {
-                this._dtmf = null;
-              }
-            }
-            return this._dtmf;
-          },
-          _pc: pc
-        };
-      };
-
-      // augment addTrack when getSenders is not available.
-      if (!window.RTCPeerConnection.prototype.getSenders) {
-        window.RTCPeerConnection.prototype.getSenders = function() {
-          this._senders = this._senders || [];
-          return this._senders.slice(); // return a copy of the internal state.
-        };
-        var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
-        window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-          var pc = this;
-          var sender = origAddTrack.apply(pc, arguments);
-          if (!sender) {
-            sender = shimSenderWithDtmf(pc, track);
-            pc._senders.push(sender);
-          }
-          return sender;
-        };
-
-        var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
-        window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-          var pc = this;
-          origRemoveTrack.apply(pc, arguments);
-          var idx = pc._senders.indexOf(sender);
-          if (idx !== -1) {
-            pc._senders.splice(idx, 1);
-          }
-        };
-      }
-      var origAddStream = window.RTCPeerConnection.prototype.addStream;
-      window.RTCPeerConnection.prototype.addStream = function(stream) {
-        var pc = this;
-        pc._senders = pc._senders || [];
-        origAddStream.apply(pc, [stream]);
-        stream.getTracks().forEach(function(track) {
-          pc._senders.push(shimSenderWithDtmf(pc, track));
-        });
-      };
-
-      var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-      window.RTCPeerConnection.prototype.removeStream = function(stream) {
-        var pc = this;
-        pc._senders = pc._senders || [];
-        origRemoveStream.apply(pc, [stream]);
-
-        stream.getTracks().forEach(function(track) {
-          var sender = pc._senders.find(function(s) {
-            return s.track === track;
-          });
-          if (sender) {
-            pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender
-          }
-        });
-      };
-    } else if (typeof window === 'object' && window.RTCPeerConnection &&
-               'getSenders' in window.RTCPeerConnection.prototype &&
-               'createDTMFSender' in window.RTCPeerConnection.prototype &&
-               window.RTCRtpSender &&
-               !('dtmf' in window.RTCRtpSender.prototype)) {
-      var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
-      window.RTCPeerConnection.prototype.getSenders = function() {
-        var pc = this;
-        var senders = origGetSenders.apply(pc, []);
-        senders.forEach(function(sender) {
-          sender._pc = pc;
-        });
-        return senders;
-      };
-
-      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
-        get: function() {
-          if (this._dtmf === undefined) {
-            if (this.track.kind === 'audio') {
-              this._dtmf = this._pc.createDTMFSender(this.track);
-            } else {
-              this._dtmf = null;
-            }
-          }
-          return this._dtmf;
-        }
-      });
-    }
-  },
-
-  shimSourceObject: function(window) {
-    var URL = window && window.URL;
-
-    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 undefined;
-            }
-            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);
-            });
-          }
-        });
-      }
-    }
-  },
-
-  shimAddTrackRemoveTrackWithNative: function(window) {
-    // shim addTrack/removeTrack with native variants in order to make
-    // the interactions with legacy getLocalStreams behave as in other browsers.
-    // Keeps a mapping stream.id => [stream, rtpsenders...]
-    window.RTCPeerConnection.prototype.getLocalStreams = function() {
-      var pc = this;
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-      return Object.keys(this._shimmedLocalStreams).map(function(streamId) {
-        return pc._shimmedLocalStreams[streamId][0];
-      });
-    };
-
-    var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
-    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-      if (!stream) {
-        return origAddTrack.apply(this, arguments);
-      }
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-
-      var sender = origAddTrack.apply(this, arguments);
-      if (!this._shimmedLocalStreams[stream.id]) {
-        this._shimmedLocalStreams[stream.id] = [stream, sender];
-      } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) {
-        this._shimmedLocalStreams[stream.id].push(sender);
-      }
-      return sender;
-    };
-
-    var origAddStream = window.RTCPeerConnection.prototype.addStream;
-    window.RTCPeerConnection.prototype.addStream = function(stream) {
-      var pc = this;
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-
-      stream.getTracks().forEach(function(track) {
-        var alreadyExists = pc.getSenders().find(function(s) {
-          return s.track === track;
-        });
-        if (alreadyExists) {
-          throw new DOMException('Track already exists.',
-              'InvalidAccessError');
-        }
-      });
-      var existingSenders = pc.getSenders();
-      origAddStream.apply(this, arguments);
-      var newSenders = pc.getSenders().filter(function(newSender) {
-        return existingSenders.indexOf(newSender) === -1;
-      });
-      this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders);
-    };
-
-    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-      delete this._shimmedLocalStreams[stream.id];
-      return origRemoveStream.apply(this, arguments);
-    };
-
-    var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
-    window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-      var pc = this;
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-      if (sender) {
-        Object.keys(this._shimmedLocalStreams).forEach(function(streamId) {
-          var idx = pc._shimmedLocalStreams[streamId].indexOf(sender);
-          if (idx !== -1) {
-            pc._shimmedLocalStreams[streamId].splice(idx, 1);
-          }
-          if (pc._shimmedLocalStreams[streamId].length === 1) {
-            delete pc._shimmedLocalStreams[streamId];
-          }
-        });
-      }
-      return origRemoveTrack.apply(this, arguments);
-    };
-  },
-
-  shimAddTrackRemoveTrack: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-    // shim addTrack and removeTrack.
-    if (window.RTCPeerConnection.prototype.addTrack &&
-        browserDetails.version >= 65) {
-      return this.shimAddTrackRemoveTrackWithNative(window);
-    }
-
-    // also shim pc.getLocalStreams when addTrack is shimmed
-    // to return the original streams.
-    var origGetLocalStreams = window.RTCPeerConnection.prototype
-        .getLocalStreams;
-    window.RTCPeerConnection.prototype.getLocalStreams = function() {
-      var pc = this;
-      var nativeStreams = origGetLocalStreams.apply(this);
-      pc._reverseStreams = pc._reverseStreams || {};
-      return nativeStreams.map(function(stream) {
-        return pc._reverseStreams[stream.id];
-      });
-    };
-
-    var origAddStream = window.RTCPeerConnection.prototype.addStream;
-    window.RTCPeerConnection.prototype.addStream = function(stream) {
-      var pc = this;
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-
-      stream.getTracks().forEach(function(track) {
-        var alreadyExists = pc.getSenders().find(function(s) {
-          return s.track === track;
-        });
-        if (alreadyExists) {
-          throw new DOMException('Track already exists.',
-              'InvalidAccessError');
-        }
-      });
-      // Add identity mapping for consistency with addTrack.
-      // Unless this is being used with a stream from addTrack.
-      if (!pc._reverseStreams[stream.id]) {
-        var newStream = new window.MediaStream(stream.getTracks());
-        pc._streams[stream.id] = newStream;
-        pc._reverseStreams[newStream.id] = stream;
-        stream = newStream;
-      }
-      origAddStream.apply(pc, [stream]);
-    };
-
-    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      var pc = this;
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-
-      origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
-      delete pc._reverseStreams[(pc._streams[stream.id] ?
-          pc._streams[stream.id].id : stream.id)];
-      delete pc._streams[stream.id];
-    };
-
-    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-      var pc = this;
-      if (pc.signalingState === 'closed') {
-        throw new DOMException(
-          'The RTCPeerConnection\'s signalingState is \'closed\'.',
-          'InvalidStateError');
-      }
-      var streams = [].slice.call(arguments, 1);
-      if (streams.length !== 1 ||
-          !streams[0].getTracks().find(function(t) {
-            return t === track;
-          })) {
-        // this is not fully correct but all we can manage without
-        // [[associated MediaStreams]] internal slot.
-        throw new DOMException(
-          'The adapter.js addTrack polyfill only supports a single ' +
-          ' stream which is associated with the specified track.',
-          'NotSupportedError');
-      }
-
-      var alreadyExists = pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-      if (alreadyExists) {
-        throw new DOMException('Track already exists.',
-            'InvalidAccessError');
-      }
-
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-      var oldStream = pc._streams[stream.id];
-      if (oldStream) {
-        // this is using odd Chrome behaviour, use with caution:
-        // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
-        // Note: we rely on the high-level addTrack/dtmf shim to
-        // create the sender with a dtmf sender.
-        oldStream.addTrack(track);
-
-        // Trigger ONN async.
-        Promise.resolve().then(function() {
-          pc.dispatchEvent(new Event('negotiationneeded'));
-        });
-      } else {
-        var newStream = new window.MediaStream([track]);
-        pc._streams[stream.id] = newStream;
-        pc._reverseStreams[newStream.id] = stream;
-        pc.addStream(newStream);
-      }
-      return pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-    };
-
-    // replace the internal stream id with the external one and
-    // vice versa.
-    function replaceInternalStreamId(pc, description) {
-      var sdp = description.sdp;
-      Object.keys(pc._reverseStreams || []).forEach(function(internalId) {
-        var externalStream = pc._reverseStreams[internalId];
-        var internalStream = pc._streams[externalStream.id];
-        sdp = sdp.replace(new RegExp(internalStream.id, 'g'),
-            externalStream.id);
-      });
-      return new RTCSessionDescription({
-        type: description.type,
-        sdp: sdp
-      });
-    }
-    function replaceExternalStreamId(pc, description) {
-      var sdp = description.sdp;
-      Object.keys(pc._reverseStreams || []).forEach(function(internalId) {
-        var externalStream = pc._reverseStreams[internalId];
-        var internalStream = pc._streams[externalStream.id];
-        sdp = sdp.replace(new RegExp(externalStream.id, 'g'),
-            internalStream.id);
-      });
-      return new RTCSessionDescription({
-        type: description.type,
-        sdp: sdp
-      });
-    }
-    ['createOffer', 'createAnswer'].forEach(function(method) {
-      var nativeMethod = window.RTCPeerConnection.prototype[method];
-      window.RTCPeerConnection.prototype[method] = function() {
-        var pc = this;
-        var args = arguments;
-        var isLegacyCall = arguments.length &&
-            typeof arguments[0] === 'function';
-        if (isLegacyCall) {
-          return nativeMethod.apply(pc, [
-            function(description) {
-              var desc = replaceInternalStreamId(pc, description);
-              args[0].apply(null, [desc]);
-            },
-            function(err) {
-              if (args[1]) {
-                args[1].apply(null, err);
-              }
-            }, arguments[2]
-          ]);
-        }
-        return nativeMethod.apply(pc, arguments)
-        .then(function(description) {
-          return replaceInternalStreamId(pc, description);
-        });
-      };
-    });
-
-    var origSetLocalDescription =
-        window.RTCPeerConnection.prototype.setLocalDescription;
-    window.RTCPeerConnection.prototype.setLocalDescription = function() {
-      var pc = this;
-      if (!arguments.length || !arguments[0].type) {
-        return origSetLocalDescription.apply(pc, arguments);
-      }
-      arguments[0] = replaceExternalStreamId(pc, arguments[0]);
-      return origSetLocalDescription.apply(pc, arguments);
-    };
-
-    // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier
-
-    var origLocalDescription = Object.getOwnPropertyDescriptor(
-        window.RTCPeerConnection.prototype, 'localDescription');
-    Object.defineProperty(window.RTCPeerConnection.prototype,
-        'localDescription', {
-          get: function() {
-            var pc = this;
-            var description = origLocalDescription.get.apply(this);
-            if (description.type === '') {
-              return description;
-            }
-            return replaceInternalStreamId(pc, description);
-          }
-        });
-
-    window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-      var pc = this;
-      if (pc.signalingState === 'closed') {
-        throw new DOMException(
-          'The RTCPeerConnection\'s signalingState is \'closed\'.',
-          'InvalidStateError');
-      }
-      // We can not yet check for sender instanceof RTCRtpSender
-      // since we shim RTPSender. So we check if sender._pc is set.
-      if (!sender._pc) {
-        throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' +
-            'does not implement interface RTCRtpSender.', 'TypeError');
-      }
-      var isLocal = sender._pc === pc;
-      if (!isLocal) {
-        throw new DOMException('Sender was not created by this connection.',
-            'InvalidAccessError');
-      }
-
-      // Search for the native stream the senders track belongs to.
-      pc._streams = pc._streams || {};
-      var stream;
-      Object.keys(pc._streams).forEach(function(streamid) {
-        var hasTrack = pc._streams[streamid].getTracks().find(function(track) {
-          return sender.track === track;
-        });
-        if (hasTrack) {
-          stream = pc._streams[streamid];
-        }
-      });
-
-      if (stream) {
-        if (stream.getTracks().length === 1) {
-          // if this is the last track of the stream, remove the stream. This
-          // takes care of any shimmed _senders.
-          pc.removeStream(pc._reverseStreams[stream.id]);
-        } else {
-          // relying on the same odd chrome behaviour as above.
-          stream.removeTrack(sender.track);
-        }
-        pc.dispatchEvent(new Event('negotiationneeded'));
-      }
-    };
-  },
-
-  shimPeerConnection: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    // The RTCPeerConnection object.
-    if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) {
-      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-        // Translate iceTransportPolicy to iceTransports,
-        // see https://code.google.com/p/webrtc/issues/detail?id=4869
-        // this was fixed in M56 along with unprefixing RTCPeerConnection.
-        logging('PeerConnection');
-        if (pcConfig && pcConfig.iceTransportPolicy) {
-          pcConfig.iceTransports = pcConfig.iceTransportPolicy;
-        }
-
-        return new window.webkitRTCPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype =
-          window.webkitRTCPeerConnection.prototype;
-      // wrap static methods. Currently just generateCertificate.
-      if (window.webkitRTCPeerConnection.generateCertificate) {
-        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-          get: function() {
-            return window.webkitRTCPeerConnection.generateCertificate;
-          }
-        });
-      }
-    } else {
-      // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
-      var OrigPeerConnection = window.RTCPeerConnection;
-      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-        if (pcConfig && pcConfig.iceServers) {
-          var newIceServers = [];
-          for (var i = 0; i < pcConfig.iceServers.length; i++) {
-            var server = pcConfig.iceServers[i];
-            if (!server.hasOwnProperty('urls') &&
-                server.hasOwnProperty('url')) {
-              utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
-              server = JSON.parse(JSON.stringify(server));
-              server.urls = server.url;
-              newIceServers.push(server);
-            } else {
-              newIceServers.push(pcConfig.iceServers[i]);
-            }
-          }
-          pcConfig.iceServers = newIceServers;
-        }
-        return new OrigPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
-      // wrap static methods. Currently just generateCertificate.
-      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-        get: function() {
-          return OrigPeerConnection.generateCertificate;
-        }
-      });
-    }
-
-    var origGetStats = window.RTCPeerConnection.prototype.getStats;
-    window.RTCPeerConnection.prototype.getStats = function(selector,
-        successCallback, errorCallback) {
-      var pc = 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.apply(this, arguments);
-      }
-
-      // When spec-style getStats is supported, return those when called with
-      // either no arguments or the selector argument is null.
-      if (origGetStats.length === 0 && (arguments.length === 0 ||
-          typeof arguments[0] !== 'function')) {
-        return origGetStats.apply(this, []);
-      }
-
-      var fixChromeStats_ = function(response) {
-        var standardReport = {};
-        var reports = response.result();
-        reports.forEach(function(report) {
-          var standardStats = {
-            id: report.id,
-            timestamp: report.timestamp,
-            type: {
-              localcandidate: 'local-candidate',
-              remotecandidate: 'remote-candidate'
-            }[report.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) {
-        return new Map(Object.keys(stats).map(function(key) {
-          return [key, stats[key]];
-        }));
-      };
-
-      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) {
-        origGetStats.apply(pc, [
-          function(response) {
-            resolve(makeMapStats(fixChromeStats_(response)));
-          }, reject]);
-      }).then(successCallback, errorCallback);
-    };
-
-    // add promise support -- natively available in Chrome 51
-    if (browserDetails.version < 51) {
-      ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-          .forEach(function(method) {
-            var nativeMethod = window.RTCPeerConnection.prototype[method];
-            window.RTCPeerConnection.prototype[method] = function() {
-              var args = arguments;
-              var pc = this;
-              var promise = new Promise(function(resolve, reject) {
-                nativeMethod.apply(pc, [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]);
-                }
-              });
-            };
-          });
-    }
-
-    // promise support for createOffer and createAnswer. Available (without
-    // bugs) since M52: crbug/619289
-    if (browserDetails.version < 52) {
-      ['createOffer', 'createAnswer'].forEach(function(method) {
-        var nativeMethod = window.RTCPeerConnection.prototype[method];
-        window.RTCPeerConnection.prototype[method] = function() {
-          var pc = 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(pc, [resolve, reject, opts]);
-            });
-          }
-          return nativeMethod.apply(this, arguments);
-        };
-      });
-    }
-
-    // shim implicit creation of RTCSessionDescription/RTCIceCandidate
-    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-        .forEach(function(method) {
-          var nativeMethod = window.RTCPeerConnection.prototype[method];
-          window.RTCPeerConnection.prototype[method] = function() {
-            arguments[0] = new ((method === 'addIceCandidate') ?
-                window.RTCIceCandidate :
-                window.RTCSessionDescription)(arguments[0]);
-            return nativeMethod.apply(this, arguments);
-          };
-        });
-
-    // support for addIceCandidate(null or undefined)
-    var nativeAddIceCandidate =
-        window.RTCPeerConnection.prototype.addIceCandidate;
-    window.RTCPeerConnection.prototype.addIceCandidate = function() {
-      if (!arguments[0]) {
-        if (arguments[1]) {
-          arguments[1].apply(null);
-        }
-        return Promise.resolve();
-      }
-      return nativeAddIceCandidate.apply(this, arguments);
-    };
-  }
-};
-
-},{"../utils.js":13,"./getusermedia":6}],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';
-var utils = require('../utils.js');
-var logging = utils.log;
-
-// Expose public methods.
-module.exports = function(window) {
-  var browserDetails = utils.detectBrowser(window);
-  var navigator = window && window.navigator;
-
-  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) {
-    if (browserDetails.version >= 61) {
-      return func(constraints);
-    }
-    constraints = JSON.parse(JSON.stringify(constraints));
-    if (constraints && typeof constraints.audio === 'object') {
-      var remap = function(obj, a, b) {
-        if (a in obj && !(b in obj)) {
-          obj[b] = obj[a];
-          delete obj[a];
-        }
-      };
-      constraints = JSON.parse(JSON.stringify(constraints));
-      remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
-      remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
-      constraints.audio = constraintsToChrome_(constraints.audio);
-    }
-    if (constraints && typeof constraints.video === 'object') {
-      // Shim facingMode for mobile & surface pro.
-      var face = constraints.video.facingMode;
-      face = face && ((typeof face === 'object') ? face : {ideal: face});
-      var getSupportedFacingModeLies = browserDetails.version < 66;
-
-      if ((face && (face.exact === 'user' || face.exact === 'environment' ||
-                    face.ideal === 'user' || face.ideal === 'environment')) &&
-          !(navigator.mediaDevices.getSupportedConstraints &&
-            navigator.mediaDevices.getSupportedConstraints().facingMode &&
-            !getSupportedFacingModeLies)) {
-        delete constraints.video.facingMode;
-        var matches;
-        if (face.exact === 'environment' || face.ideal === 'environment') {
-          matches = ['back', 'rear'];
-        } else if (face.exact === 'user' || face.ideal === 'user') {
-          matches = ['front'];
-        }
-        if (matches) {
-          // Look for matches in label, or use last cam for back (typical).
-          return navigator.mediaDevices.enumerateDevices()
-          .then(function(devices) {
-            devices = devices.filter(function(d) {
-              return d.kind === 'videoinput';
-            });
-            var dev = devices.find(function(d) {
-              return matches.some(function(match) {
-                return d.label.toLowerCase().indexOf(match) !== -1;
-              });
-            });
-            if (!dev && devices.length && matches.indexOf('back') !== -1) {
-              dev = devices[devices.length - 1]; // more likely the back cam
-            }
-            if (dev) {
-              constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :
-                                                        {ideal: dev.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',
-        InvalidStateError: 'NotReadableError',
-        DevicesNotFoundError: 'NotFoundError',
-        ConstraintNotSatisfiedError: 'OverconstrainedError',
-        TrackStartError: 'NotReadableError',
-        MediaDeviceFailedDueToShutdown: 'NotReadableError',
-        MediaDeviceKillSwitchOn: 'NotReadableError'
-      }[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) {
-        if (onError) {
-          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 window.MediaStreamTrack.getSources(function(devices) {
-            resolve(devices.map(function(device) {
-              return {label: device.label,
-                kind: kinds[device.kind],
-                deviceId: device.id,
-                groupId: ''};
-            }));
-          });
-        });
-      },
-      getSupportedConstraints: function() {
-        return {
-          deviceId: true, echoCancellation: true, facingMode: true,
-          frameRate: true, height: true, width: true
-        };
-      }
-    };
-  }
-
-  // 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).then(function(stream) {
-          if (c.audio && !stream.getAudioTracks().length ||
-              c.video && !stream.getVideoTracks().length) {
-            stream.getTracks().forEach(function(track) {
-              track.stop();
-            });
-            throw new DOMException('', 'NotFoundError');
-          }
-          return stream;
-        }, 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":13}],7:[function(require,module,exports){
-/*
- *  Copyright (c) 2017 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 utils = require('./utils');
-
-module.exports = {
-  shimRTCIceCandidate: function(window) {
-    // foundation is arbitrarily chosen as an indicator for full support for
-    // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
-    if (window.RTCIceCandidate && 'foundation' in
-        window.RTCIceCandidate.prototype) {
-      return;
-    }
-
-    var NativeRTCIceCandidate = window.RTCIceCandidate;
-    window.RTCIceCandidate = function(args) {
-      // Remove the a= which shouldn't be part of the candidate string.
-      if (typeof args === 'object' && args.candidate &&
-          args.candidate.indexOf('a=') === 0) {
-        args = JSON.parse(JSON.stringify(args));
-        args.candidate = args.candidate.substr(2);
-      }
-
-      // Augment the native candidate with the parsed fields.
-      var nativeCandidate = new NativeRTCIceCandidate(args);
-      var parsedCandidate = SDPUtils.parseCandidate(args.candidate);
-      var augmentedCandidate = Object.assign(nativeCandidate,
-          parsedCandidate);
-
-      // Add a serializer that does not serialize the extra attributes.
-      augmentedCandidate.toJSON = function() {
-        return {
-          candidate: augmentedCandidate.candidate,
-          sdpMid: augmentedCandidate.sdpMid,
-          sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
-          usernameFragment: augmentedCandidate.usernameFragment,
-        };
-      };
-      return augmentedCandidate;
-    };
-
-    // Hook up the augmented candidate in onicecandidate and
-    // addEventListener('icecandidate', ...)
-    utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) {
-      if (e.candidate) {
-        Object.defineProperty(e, 'candidate', {
-          value: new window.RTCIceCandidate(e.candidate),
-          writable: 'false'
-        });
-      }
-      return e;
-    });
-  },
-
-  // shimCreateObjectURL must be called before shimSourceObject to avoid loop.
-
-  shimCreateObjectURL: function(window) {
-    var URL = window && window.URL;
-
-    if (!(typeof window === 'object' && window.HTMLMediaElement &&
-          'srcObject' in window.HTMLMediaElement.prototype &&
-        URL.createObjectURL && URL.revokeObjectURL)) {
-      // Only shim CreateObjectURL using srcObject if srcObject exists.
-      return undefined;
-    }
-
-    var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
-    var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
-    var streams = new Map(), newId = 0;
-
-    URL.createObjectURL = function(stream) {
-      if ('getTracks' in stream) {
-        var url = 'polyblob:' + (++newId);
-        streams.set(url, stream);
-        utils.deprecated('URL.createObjectURL(stream)',
-            'elem.srcObject = stream');
-        return url;
-      }
-      return nativeCreateObjectURL(stream);
-    };
-    URL.revokeObjectURL = function(url) {
-      nativeRevokeObjectURL(url);
-      streams.delete(url);
-    };
-
-    var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
-                                              'src');
-    Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
-      get: function() {
-        return dsc.get.apply(this);
-      },
-      set: function(url) {
-        this.srcObject = streams.get(url) || null;
-        return dsc.set.apply(this, [url]);
-      }
-    });
-
-    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
-    window.HTMLMediaElement.prototype.setAttribute = function() {
-      if (arguments.length === 2 &&
-          ('' + arguments[0]).toLowerCase() === 'src') {
-        this.srcObject = streams.get(arguments[1]) || null;
-      }
-      return nativeSetAttribute.apply(this, arguments);
-    };
-  },
-
-  shimMaxMessageSize: function(window) {
-    if (window.RTCSctpTransport || !window.RTCPeerConnection) {
-      return;
-    }
-    var browserDetails = utils.detectBrowser(window);
-
-    if (!('sctp' in window.RTCPeerConnection.prototype)) {
-      Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
-        get: function() {
-          return typeof this._sctp === 'undefined' ? null : this._sctp;
-        }
-      });
-    }
-
-    var sctpInDescription = function(description) {
-      var sections = SDPUtils.splitSections(description.sdp);
-      sections.shift();
-      return sections.some(function(mediaSection) {
-        var mLine = SDPUtils.parseMLine(mediaSection);
-        return mLine && mLine.kind === 'application'
-            && mLine.protocol.indexOf('SCTP') !== -1;
-      });
-    };
-
-    var getRemoteFirefoxVersion = function(description) {
-      // TODO: Is there a better solution for detecting Firefox?
-      var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);
-      if (match === null || match.length < 2) {
-        return -1;
-      }
-      var version = parseInt(match[1], 10);
-      // Test for NaN (yes, this is ugly)
-      return version !== version ? -1 : version;
-    };
-
-    var getCanSendMaxMessageSize = function(remoteIsFirefox) {
-      // Every implementation we know can send at least 64 KiB.
-      // Note: Although Chrome is technically able to send up to 256 KiB, the
-      //       data does not reach the other peer reliably.
-      //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
-      var canSendMaxMessageSize = 65536;
-      if (browserDetails.browser === 'firefox') {
-        if (browserDetails.version < 57) {
-          if (remoteIsFirefox === -1) {
-            // FF < 57 will send in 16 KiB chunks using the deprecated PPID
-            // fragmentation.
-            canSendMaxMessageSize = 16384;
-          } else {
-            // However, other FF (and RAWRTC) can reassemble PPID-fragmented
-            // messages. Thus, supporting ~2 GiB when sending.
-            canSendMaxMessageSize = 2147483637;
-          }
-        } else {
-          // Currently, all FF >= 57 will reset the remote maximum message size
-          // to the default value when a data channel is created at a later
-          // stage. :(
-          // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
-          canSendMaxMessageSize =
-            browserDetails.version === 57 ? 65535 : 65536;
-        }
-      }
-      return canSendMaxMessageSize;
-    };
-
-    var getMaxMessageSize = function(description, remoteIsFirefox) {
-      // Note: 65536 bytes is the default value from the SDP spec. Also,
-      //       every implementation we know supports receiving 65536 bytes.
-      var maxMessageSize = 65536;
-
-      // FF 57 has a slightly incorrect default remote max message size, so
-      // we need to adjust it here to avoid a failure when sending.
-      // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
-      if (browserDetails.browser === 'firefox'
-           && browserDetails.version === 57) {
-        maxMessageSize = 65535;
-      }
-
-      var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:');
-      if (match.length > 0) {
-        maxMessageSize = parseInt(match[0].substr(19), 10);
-      } else if (browserDetails.browser === 'firefox' &&
-                  remoteIsFirefox !== -1) {
-        // If the maximum message size is not present in the remote SDP and
-        // both local and remote are Firefox, the remote peer can receive
-        // ~2 GiB.
-        maxMessageSize = 2147483637;
-      }
-      return maxMessageSize;
-    };
-
-    var origSetRemoteDescription =
-        window.RTCPeerConnection.prototype.setRemoteDescription;
-    window.RTCPeerConnection.prototype.setRemoteDescription = function() {
-      var pc = this;
-      pc._sctp = null;
-
-      if (sctpInDescription(arguments[0])) {
-        // Check if the remote is FF.
-        var isFirefox = getRemoteFirefoxVersion(arguments[0]);
-
-        // Get the maximum message size the local peer is capable of sending
-        var canSendMMS = getCanSendMaxMessageSize(isFirefox);
-
-        // Get the maximum message size of the remote peer.
-        var remoteMMS = getMaxMessageSize(arguments[0], isFirefox);
-
-        // Determine final maximum message size
-        var maxMessageSize;
-        if (canSendMMS === 0 && remoteMMS === 0) {
-          maxMessageSize = Number.POSITIVE_INFINITY;
-        } else if (canSendMMS === 0 || remoteMMS === 0) {
-          maxMessageSize = Math.max(canSendMMS, remoteMMS);
-        } else {
-          maxMessageSize = Math.min(canSendMMS, remoteMMS);
-        }
-
-        // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
-        // attribute.
-        var sctp = {};
-        Object.defineProperty(sctp, 'maxMessageSize', {
-          get: function() {
-            return maxMessageSize;
-          }
-        });
-        pc._sctp = sctp;
-      }
-
-      return origSetRemoteDescription.apply(pc, arguments);
-    };
-  },
-
-  shimSendThrowTypeError: function(window) {
-    // Note: Although Firefox >= 57 has a native implementation, the maximum
-    //       message size can be reset for all data channels at a later stage.
-    //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
-
-    var origCreateDataChannel =
-      window.RTCPeerConnection.prototype.createDataChannel;
-    window.RTCPeerConnection.prototype.createDataChannel = function() {
-      var pc = this;
-      var dataChannel = origCreateDataChannel.apply(pc, arguments);
-      var origDataChannelSend = dataChannel.send;
-
-      // Patch 'send' method
-      dataChannel.send = function() {
-        var dc = this;
-        var data = arguments[0];
-        var length = data.length || data.size || data.byteLength;
-        if (length > pc.sctp.maxMessageSize) {
-          throw new DOMException('Message too large (can send a maximum of ' +
-            pc.sctp.maxMessageSize + ' bytes)', 'TypeError');
-        }
-        return origDataChannelSend.apply(dc, arguments);
-      };
-
-      return dataChannel;
-    };
-  }
-};
-
-},{"./utils":13,"sdp":2}],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 utils = require('../utils');
-var shimRTCPeerConnection = require('rtcpeerconnection-shim');
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimPeerConnection: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    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;
-        };
-      }
-      // this adds an additional event listener to MediaStrackTrack that signals
-      // when a tracks enabled property was changed. Workaround for a bug in
-      // addStream, see below. No longer required in 15025+
-      if (browserDetails.version < 15025) {
-        var origMSTEnabled = Object.getOwnPropertyDescriptor(
-            window.MediaStreamTrack.prototype, 'enabled');
-        Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {
-          set: function(value) {
-            origMSTEnabled.set.call(this, value);
-            var ev = new Event('enabled');
-            ev.enabled = value;
-            this.dispatchEvent(ev);
-          }
-        });
-      }
-    }
-
-    // ORTC defines the DTMF sender a bit different.
-    // https://github.com/w3c/ortc/issues/714
-    if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {
-      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
-        get: function() {
-          if (this._dtmf === undefined) {
-            if (this.track.kind === 'audio') {
-              this._dtmf = new window.RTCDtmfSender(this);
-            } else if (this.track.kind === 'video') {
-              this._dtmf = null;
-            }
-          }
-          return this._dtmf;
-        }
-      });
-    }
-
-    window.RTCPeerConnection =
-        shimRTCPeerConnection(window, browserDetails.version);
-  },
-  shimReplaceTrack: function(window) {
-    // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
-    if (window.RTCRtpSender &&
-        !('replaceTrack' in window.RTCRtpSender.prototype)) {
-      window.RTCRtpSender.prototype.replaceTrack =
-          window.RTCRtpSender.prototype.setTrack;
-    }
-  }
-};
-
-},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],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.
- */
- /* eslint-env node */
-'use strict';
-
-// Expose public methods.
-module.exports = function(window) {
-  var navigator = window && window.navigator;
-
-  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));
-    });
-  };
-};
-
-},{}],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 utils = require('../utils');
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimOnTrack: function(window) {
-    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.transceiver = {receiver: event.receiver};
-              event.streams = [e.stream];
-              this.dispatchEvent(event);
-            }.bind(this));
-          }.bind(this));
-        }
-      });
-    }
-    if (typeof window === 'object' && window.RTCTrackEvent &&
-        ('receiver' in window.RTCTrackEvent.prototype) &&
-        !('transceiver' in window.RTCTrackEvent.prototype)) {
-      Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
-        get: function() {
-          return {receiver: this.receiver};
-        }
-      });
-    }
-  },
-
-  shimSourceObject: function(window) {
-    // 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(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    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 window.mozRTCPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype =
-          window.mozRTCPeerConnection.prototype;
-
-      // wrap static methods. Currently just generateCertificate.
-      if (window.mozRTCPeerConnection.generateCertificate) {
-        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-          get: function() {
-            return window.mozRTCPeerConnection.generateCertificate;
-          }
-        });
-      }
-
-      window.RTCSessionDescription = window.mozRTCSessionDescription;
-      window.RTCIceCandidate = window.mozRTCIceCandidate;
-    }
-
-    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
-    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-        .forEach(function(method) {
-          var nativeMethod = window.RTCPeerConnection.prototype[method];
-          window.RTCPeerConnection.prototype[method] = function() {
-            arguments[0] = new ((method === 'addIceCandidate') ?
-                window.RTCIceCandidate :
-                window.RTCSessionDescription)(arguments[0]);
-            return nativeMethod.apply(this, arguments);
-          };
-        });
-
-    // support for addIceCandidate(null or undefined)
-    var nativeAddIceCandidate =
-        window.RTCPeerConnection.prototype.addIceCandidate;
-    window.RTCPeerConnection.prototype.addIceCandidate = function() {
-      if (!arguments[0]) {
-        if (arguments[1]) {
-          arguments[1].apply(null);
-        }
-        return Promise.resolve();
-      }
-      return 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 modernStatsTypes = {
-      inboundrtp: 'inbound-rtp',
-      outboundrtp: 'outbound-rtp',
-      candidatepair: 'candidate-pair',
-      localcandidate: 'local-candidate',
-      remotecandidate: 'remote-candidate'
-    };
-
-    var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
-    window.RTCPeerConnection.prototype.getStats = function(
-      selector,
-      onSucc,
-      onErr
-    ) {
-      return nativeGetStats.apply(this, [selector || null])
-        .then(function(stats) {
-          if (browserDetails.version < 48) {
-            stats = makeMapStats(stats);
-          }
-          if (browserDetails.version < 53 && !onSucc) {
-            // Shim only promise getStats with spec-hyphens in type names
-            // Leave callback version alone; misc old uses of forEach before Map
-            try {
-              stats.forEach(function(stat) {
-                stat.type = modernStatsTypes[stat.type] || stat.type;
-              });
-            } catch (e) {
-              if (e.name !== 'TypeError') {
-                throw e;
-              }
-              // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
-              stats.forEach(function(stat, i) {
-                stats.set(i, Object.assign({}, stat, {
-                  type: modernStatsTypes[stat.type] || stat.type
-                }));
-              });
-            }
-          }
-          return stats;
-        })
-        .then(onSucc, onErr);
-    };
-  },
-
-  shimRemoveStream: function(window) {
-    if (!window.RTCPeerConnection ||
-        'removeStream' in window.RTCPeerConnection.prototype) {
-      return;
-    }
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      var pc = this;
-      utils.deprecated('removeStream', 'removeTrack');
-      this.getSenders().forEach(function(sender) {
-        if (sender.track && stream.getTracks().indexOf(sender.track) !== -1) {
-          pc.removeTrack(sender);
-        }
-      });
-    };
-  }
-};
-
-},{"../utils":13,"./getusermedia":11}],11:[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 utils = require('../utils');
-var logging = utils.log;
-
-// Expose public methods.
-module.exports = function(window) {
-  var browserDetails = utils.detectBrowser(window);
-  var navigator = window && window.navigator;
-  var MediaStreamTrack = window && window.MediaStreamTrack;
-
-  var shimError_ = function(e) {
-    return {
-      name: {
-        InternalError: 'NotReadableError',
-        NotSupportedError: 'TypeError',
-        PermissionDeniedError: 'NotAllowedError',
-        SecurityError: '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).then(function(stream) {
-        // Work around https://bugzil.la/802326
-        if (c.audio && !stream.getAudioTracks().length ||
-            c.video && !stream.getVideoTracks().length) {
-          stream.getTracks().forEach(function(track) {
-            track.stop();
-          });
-          throw new DOMException('The object can not be found here.',
-                                 'NotFoundError');
-        }
-        return stream;
-      }, function(e) {
-        return Promise.reject(shimError_(e));
-      });
-    };
-  }
-  if (!(browserDetails.version > 55 &&
-      'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {
-    var remap = function(obj, a, b) {
-      if (a in obj && !(b in obj)) {
-        obj[b] = obj[a];
-        delete obj[a];
-      }
-    };
-
-    var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.
-        bind(navigator.mediaDevices);
-    navigator.mediaDevices.getUserMedia = function(c) {
-      if (typeof c === 'object' && typeof c.audio === 'object') {
-        c = JSON.parse(JSON.stringify(c));
-        remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
-        remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
-      }
-      return nativeGetUserMedia(c);
-    };
-
-    if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
-      var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
-      MediaStreamTrack.prototype.getSettings = function() {
-        var obj = nativeGetSettings.apply(this, arguments);
-        remap(obj, 'mozAutoGainControl', 'autoGainControl');
-        remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
-        return obj;
-      };
-    }
-
-    if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {
-      var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
-      MediaStreamTrack.prototype.applyConstraints = function(c) {
-        if (this.kind === 'audio' && typeof c === 'object') {
-          c = JSON.parse(JSON.stringify(c));
-          remap(c, 'autoGainControl', 'mozAutoGainControl');
-          remap(c, 'noiseSuppression', 'mozNoiseSuppression');
-        }
-        return nativeApplyConstraints.apply(this, [c]);
-      };
-    }
-  }
-  navigator.getUserMedia = function(constraints, onSuccess, onError) {
-    if (browserDetails.version < 44) {
-      return getUserMedia_(constraints, onSuccess, onError);
-    }
-    // Replace Firefox 44+'s deprecation warning with unprefixed version.
-    utils.deprecated('navigator.getUserMedia',
-        'navigator.mediaDevices.getUserMedia');
-    navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
-  };
-};
-
-},{"../utils":13}],12:[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 utils = require('../utils');
-
-module.exports = {
-  shimLocalStreamsAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getLocalStreams = function() {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        return this._localStreams;
-      };
-    }
-    if (!('getStreamById' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getStreamById = function(id) {
-        var result = null;
-        if (this._localStreams) {
-          this._localStreams.forEach(function(stream) {
-            if (stream.id === id) {
-              result = stream;
-            }
-          });
-        }
-        if (this._remoteStreams) {
-          this._remoteStreams.forEach(function(stream) {
-            if (stream.id === id) {
-              result = stream;
-            }
-          });
-        }
-        return result;
-      };
-    }
-    if (!('addStream' in window.RTCPeerConnection.prototype)) {
-      var _addTrack = window.RTCPeerConnection.prototype.addTrack;
-      window.RTCPeerConnection.prototype.addStream = function(stream) {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        if (this._localStreams.indexOf(stream) === -1) {
-          this._localStreams.push(stream);
-        }
-        var pc = this;
-        stream.getTracks().forEach(function(track) {
-          _addTrack.call(pc, track, stream);
-        });
-      };
-
-      window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-        if (stream) {
-          if (!this._localStreams) {
-            this._localStreams = [stream];
-          } else if (this._localStreams.indexOf(stream) === -1) {
-            this._localStreams.push(stream);
-          }
-        }
-        return _addTrack.call(this, track, stream);
-      };
-    }
-    if (!('removeStream' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.removeStream = function(stream) {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        var index = this._localStreams.indexOf(stream);
-        if (index === -1) {
-          return;
-        }
-        this._localStreams.splice(index, 1);
-        var pc = this;
-        var tracks = stream.getTracks();
-        this.getSenders().forEach(function(sender) {
-          if (tracks.indexOf(sender.track) !== -1) {
-            pc.removeTrack(sender);
-          }
-        });
-      };
-    }
-  },
-  shimRemoteStreamsAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getRemoteStreams = function() {
-        return this._remoteStreams ? this._remoteStreams : [];
-      };
-    }
-    if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
-      Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
-        get: function() {
-          return this._onaddstream;
-        },
-        set: function(f) {
-          var pc = this;
-          if (this._onaddstream) {
-            this.removeEventListener('addstream', this._onaddstream);
-            this.removeEventListener('track', this._onaddstreampoly);
-          }
-          this.addEventListener('addstream', this._onaddstream = f);
-          this.addEventListener('track', this._onaddstreampoly = function(e) {
-            e.streams.forEach(function(stream) {
-              if (!pc._remoteStreams) {
-                pc._remoteStreams = [];
-              }
-              if (pc._remoteStreams.indexOf(stream) >= 0) {
-                return;
-              }
-              pc._remoteStreams.push(stream);
-              var event = new Event('addstream');
-              event.stream = stream;
-              pc.dispatchEvent(event);
-            });
-          });
-        }
-      });
-    }
-  },
-  shimCallbacksAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    var prototype = window.RTCPeerConnection.prototype;
-    var createOffer = prototype.createOffer;
-    var createAnswer = prototype.createAnswer;
-    var setLocalDescription = prototype.setLocalDescription;
-    var setRemoteDescription = prototype.setRemoteDescription;
-    var addIceCandidate = prototype.addIceCandidate;
-
-    prototype.createOffer = function(successCallback, failureCallback) {
-      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
-      var promise = createOffer.apply(this, [options]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-
-    prototype.createAnswer = function(successCallback, failureCallback) {
-      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
-      var promise = createAnswer.apply(this, [options]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-
-    var withCallback = function(description, successCallback, failureCallback) {
-      var promise = setLocalDescription.apply(this, [description]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.setLocalDescription = withCallback;
-
-    withCallback = function(description, successCallback, failureCallback) {
-      var promise = setRemoteDescription.apply(this, [description]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.setRemoteDescription = withCallback;
-
-    withCallback = function(candidate, successCallback, failureCallback) {
-      var promise = addIceCandidate.apply(this, [candidate]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.addIceCandidate = withCallback;
-  },
-  shimGetUserMedia: function(window) {
-    var navigator = window && window.navigator;
-
-    if (!navigator.getUserMedia) {
-      if (navigator.webkitGetUserMedia) {
-        navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
-      } else if (navigator.mediaDevices &&
-          navigator.mediaDevices.getUserMedia) {
-        navigator.getUserMedia = function(constraints, cb, errcb) {
-          navigator.mediaDevices.getUserMedia(constraints)
-          .then(cb, errcb);
-        }.bind(navigator);
-      }
-    }
-  },
-  shimRTCIceServerUrls: function(window) {
-    // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
-    var OrigPeerConnection = window.RTCPeerConnection;
-    window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-      if (pcConfig && pcConfig.iceServers) {
-        var newIceServers = [];
-        for (var i = 0; i < pcConfig.iceServers.length; i++) {
-          var server = pcConfig.iceServers[i];
-          if (!server.hasOwnProperty('urls') &&
-              server.hasOwnProperty('url')) {
-            utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
-            server = JSON.parse(JSON.stringify(server));
-            server.urls = server.url;
-            delete server.url;
-            newIceServers.push(server);
-          } else {
-            newIceServers.push(pcConfig.iceServers[i]);
-          }
-        }
-        pcConfig.iceServers = newIceServers;
-      }
-      return new OrigPeerConnection(pcConfig, pcConstraints);
-    };
-    window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
-    // wrap static methods. Currently just generateCertificate.
-    if ('generateCertificate' in window.RTCPeerConnection) {
-      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-        get: function() {
-          return OrigPeerConnection.generateCertificate;
-        }
-      });
-    }
-  },
-  shimTrackEventTransceiver: function(window) {
-    // Add event.transceiver member over deprecated event.receiver
-    if (typeof window === 'object' && window.RTCPeerConnection &&
-        ('receiver' in window.RTCTrackEvent.prototype) &&
-        // can't check 'transceiver' in window.RTCTrackEvent.prototype, as it is
-        // defined for some reason even when window.RTCTransceiver is not.
-        !window.RTCTransceiver) {
-      Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
-        get: function() {
-          return {receiver: this.receiver};
-        }
-      });
-    }
-  },
-
-  shimCreateOfferLegacy: function(window) {
-    var origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
-    window.RTCPeerConnection.prototype.createOffer = function(offerOptions) {
-      var pc = this;
-      if (offerOptions) {
-        var audioTransceiver = pc.getTransceivers().find(function(transceiver) {
-          return transceiver.sender.track &&
-              transceiver.sender.track.kind === 'audio';
-        });
-        if (offerOptions.offerToReceiveAudio === false && audioTransceiver) {
-          if (audioTransceiver.direction === 'sendrecv') {
-            if (audioTransceiver.setDirection) {
-              audioTransceiver.setDirection('sendonly');
-            } else {
-              audioTransceiver.direction = 'sendonly';
-            }
-          } else if (audioTransceiver.direction === 'recvonly') {
-            if (audioTransceiver.setDirection) {
-              audioTransceiver.setDirection('inactive');
-            } else {
-              audioTransceiver.direction = 'inactive';
-            }
-          }
-        } else if (offerOptions.offerToReceiveAudio === true &&
-            !audioTransceiver) {
-          pc.addTransceiver('audio');
-        }
-
-        var videoTransceiver = pc.getTransceivers().find(function(transceiver) {
-          return transceiver.sender.track &&
-              transceiver.sender.track.kind === 'video';
-        });
-        if (offerOptions.offerToReceiveVideo === false && videoTransceiver) {
-          if (videoTransceiver.direction === 'sendrecv') {
-            videoTransceiver.setDirection('sendonly');
-          } else if (videoTransceiver.direction === 'recvonly') {
-            videoTransceiver.setDirection('inactive');
-          }
-        } else if (offerOptions.offerToReceiveVideo === true &&
-            !videoTransceiver) {
-          pc.addTransceiver('video');
-        }
-      }
-      return origCreateOffer.apply(pc, arguments);
-    };
-  }
-};
-
-},{"../utils":13}],13:[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;
-var deprecationWarnings_ = true;
-
-/**
- * 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.
- */
-function extractVersion(uastring, expr, pos) {
-  var match = uastring.match(expr);
-  return match && match.length >= pos && parseInt(match[pos], 10);
-}
-
-// Wraps the peerconnection event eventNameToWrap in a function
-// which returns the modified event object.
-function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
-  if (!window.RTCPeerConnection) {
-    return;
-  }
-  var proto = window.RTCPeerConnection.prototype;
-  var nativeAddEventListener = proto.addEventListener;
-  proto.addEventListener = function(nativeEventName, cb) {
-    if (nativeEventName !== eventNameToWrap) {
-      return nativeAddEventListener.apply(this, arguments);
-    }
-    var wrappedCallback = function(e) {
-      cb(wrapper(e));
-    };
-    this._eventMap = this._eventMap || {};
-    this._eventMap[cb] = wrappedCallback;
-    return nativeAddEventListener.apply(this, [nativeEventName,
-      wrappedCallback]);
-  };
-
-  var nativeRemoveEventListener = proto.removeEventListener;
-  proto.removeEventListener = function(nativeEventName, cb) {
-    if (nativeEventName !== eventNameToWrap || !this._eventMap
-        || !this._eventMap[cb]) {
-      return nativeRemoveEventListener.apply(this, arguments);
-    }
-    var unwrappedCb = this._eventMap[cb];
-    delete this._eventMap[cb];
-    return nativeRemoveEventListener.apply(this, [nativeEventName,
-      unwrappedCb]);
-  };
-
-  Object.defineProperty(proto, 'on' + eventNameToWrap, {
-    get: function() {
-      return this['_on' + eventNameToWrap];
-    },
-    set: function(cb) {
-      if (this['_on' + eventNameToWrap]) {
-        this.removeEventListener(eventNameToWrap,
-            this['_on' + eventNameToWrap]);
-        delete this['_on' + eventNameToWrap];
-      }
-      if (cb) {
-        this.addEventListener(eventNameToWrap,
-            this['_on' + eventNameToWrap] = cb);
-      }
-    }
-  });
-}
-
-// Utility methods.
-module.exports = {
-  extractVersion: extractVersion,
-  wrapPeerConnectionEvent: wrapPeerConnectionEvent,
-  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';
-  },
-
-  /**
-   * Disable or enable deprecation warnings
-   * @param {!boolean} bool set to true to disable warnings.
-   */
-  disableWarnings: function(bool) {
-    if (typeof bool !== 'boolean') {
-      return new Error('Argument type: ' + typeof bool +
-          '. Please use a boolean.');
-    }
-    deprecationWarnings_ = !bool;
-    return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
-  },
-
-  log: function() {
-    if (typeof window === 'object') {
-      if (logDisabled_) {
-        return;
-      }
-      if (typeof console !== 'undefined' && typeof console.log === 'function') {
-        console.log.apply(console, arguments);
-      }
-    }
-  },
-
-  /**
-   * Shows a deprecation warning suggesting the modern and spec-compatible API.
-   */
-  deprecated: function(oldMethod, newMethod) {
-    if (!deprecationWarnings_) {
-      return;
-    }
-    console.warn(oldMethod + ' is deprecated, please use ' + newMethod +
-        ' instead.');
-  },
-
-  /**
-   * Browser detector.
-   *
-   * @return {object} result containing browser and version
-   *     properties.
-   */
-  detectBrowser: function(window) {
-    var navigator = window && window.navigator;
-
-    // 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;
-    }
-
-    if (navigator.mozGetUserMedia) { // Firefox.
-      result.browser = 'firefox';
-      result.version = extractVersion(navigator.userAgent,
-          /Firefox\/(\d+)\./, 1);
-    } else if (navigator.webkitGetUserMedia) {
-      // Chrome, Chromium, Webview, Opera.
-      // Version matches Chrome/WebRTC version.
-      result.browser = 'chrome';
-      result.version = extractVersion(navigator.userAgent,
-          /Chrom(e|ium)\/(\d+)\./, 2);
-    } else if (navigator.mediaDevices &&
-        navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge.
-      result.browser = 'edge';
-      result.version = extractVersion(navigator.userAgent,
-          /Edge\/(\d+).(\d+)$/, 2);
-    } else if (navigator.mediaDevices &&
-        navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari.
-      result.browser = 'safari';
-      result.version = extractVersion(navigator.userAgent,
-          /AppleWebKit\/(\d+)\./, 1);
-    } else { // Default fallthrough: not supported.
-      result.browser = 'Not a supported browser.';
-      return result;
-    }
-
-    return result;
-  }
-};
-
-},{}]},{},[3])(3)
-});
\ No newline at end of file
+(function($){function findLine(sdpLines,prefix,substr){return findLineInRange(sdpLines,0,-1,prefix,substr);}
+function findLineInRange(sdpLines,startLine,endLine,prefix,substr){var realEndLine=(endLine!=-1)?endLine:sdpLines.length;for(var i=startLine;i<realEndLine;++i){if(sdpLines[i].indexOf(prefix)===0){if(!substr||sdpLines[i].toLowerCase().indexOf(substr.toLowerCase())!==-1){return i;}}}
+return null;}
+function getCodecPayloadType(sdpLine){var pattern=new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');var result=sdpLine.match(pattern);return(result&&result.length==2)?result[1]:null;}
+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(){}},useStream:null,},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(){}
+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 onRemoteStreamSuccess(self,stream){console.log("Remote Stream Success");doCallback(self,"onRemoteStream",stream);}
+function onICE(self,candidate){self.mediaData.candidate=candidate;self.mediaData.candidateList.push(self.mediaData.candidate);doCallback(self,"onICE");}
+function doCallback(self,func,arg){if(func in self.options.callbacks){self.options.callbacks[func](self,arg);}}
+function onICEComplete(self,candidate){console.log("ICE Complete");doCallback(self,"onICEComplete");}
+function onChannelError(self,e){console.error("Channel Error",e);doCallback(self,"onError",e);}
+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(typeof element.srcObject!=='undefined'){element.srcObject=stream;}else{console.error('Error attaching stream to element.');}}
+function onRemoteStream(self,stream){if(self.options.useVideo){self.options.useVideo.style.display='block';var iOS=['iPad','iPhone','iPod'].indexOf(navigator.platform)>=0;if(iOS){self.options.useVideo.setAttribute("playsinline",true);}}
+var element=self.options.useAudio;console.log("REMOTE STREAM",stream,element);FSRTCattachMediaStream(element,stream);self.remoteStream=stream;onRemoteStreamSuccess(self,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';self.options.useVideo['src']='';}
+if(self.localStream&&!self.options.useStream){if(typeof self.localStream.stop=='function'){self.localStream.stop();}else{if(self.localStream.active){var tracks=self.localStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}
+self.localStream=null;}
+if(self.options.localVideo){deactivateLocalVideo(self.options.localVideo);}
+if(self.options.localVideoStream&&!self.options.useStream){if(typeof self.options.localVideoStream.stop=='function'){self.options.localVideoStream.stop();}else{if(self.options.localVideoStream.active){var tracks=self.options.localVideoStream.getTracks();console.log(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;if(!self.localStream){return false;}
+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;}
+self.audioEnabled=audioTracks[i].enabled;}
+return!self.audioEnabled;}
+$.FSRTC.prototype.getVideoMute=function(){var self=this;return self.videoEnabled;}
+$.FSRTC.prototype.setVideoMute=function(what){var self=this;if(!self.localStream){return false;}
+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=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},turnServer:self.options.turnServer});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(self.options.useVideo&&self.options.localVideo&&!self.options.useStream){getUserMedia({constraints:{audio:false,video:{deviceId:params.useCamera},},localVideo:self.options.localVideo,useCameraLabel:self.options.useCameraLabel,useMicLabel:self.options.useMicLabel,onsuccess:function(e){self.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!",e);}});}
+if(self.options.useStream){if(self.options.useVideo){self.options.localVideoStream=self.options.useStream;if(self.options.localVideo){activateLocalVideo(self.options.localVideo,self.options.useStream);}}
+onSuccess(self.options.useStream);}
+else{getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,useCameraLabel:self.options.useCameraLabel,useMicLabel:self.options.useMicLabel,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={};if(obj.options.audioParams){audio=obj.options.audioParams;}
+if(obj.options.useMic!=="any"){audio.deviceId=assignMediaIdToConstraint(obj.options.useMic);}}
+if(obj.options.useVideo&&obj.options.localVideo&&!obj.options.useStream){getUserMedia({constraints:{audio:false,video:{deviceId:obj.options.useCamera},},localVideo:obj.options.localVideo,useCameraLabel:obj.options.useCameraLabel,useMicLabel:obj.options.useMicLabel,onsuccess:function(e){obj.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!",e);}});}
+var video={};var bestFrameRate=obj.options.videoParams.vertoBestFrameRate;var minFrameRate=obj.options.videoParams.minFrameRate||15;delete obj.options.videoParams.vertoBestFrameRate;if(obj.options.screenShare){if(!obj.options.useCamera&&!!navigator.mozGetUserMedia){var dowin=window.confirm("Do you want to share an application window?  If not you can share an entire screen.");video={width:{min:obj.options.videoParams.minWidth,max:obj.options.videoParams.maxWidth},height:{min:obj.options.videoParams.minHeight,max:obj.options.videoParams.maxHeight},mediaSource:dowin?"window":"screen"}}else{var opt=[];if(obj.options.useCamera){opt.push({sourceId:obj.options.useCamera});}
+if(bestFrameRate){opt.push({minFrameRate:bestFrameRate});opt.push({maxFrameRate:bestFrameRate});}
+video={mandatory:obj.options.videoParams,optional:opt};}}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=assignMediaIdToConstraint(obj.options.useCamera,video);}
+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){self.constraints.offerToReceiveVideo=false;self.constraints.offerToReceiveAudio=false;self.constraints.offerToSendAudio=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,turnServer:self.options.turnServer});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(self.options.useStream){if(self.options.useVideo){self.options.localVideoStream=self.options.useStream;if(self.options.localVideo){activateLocalVideo(self.options.localVideo,self.options.useStream);}}
+onSuccess(self.options.useStream);}
+else if(mediaParams.audio||mediaParams.video){getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,onsuccess:onSuccess,onerror:onError,useCameraLabel:self.options.useCameraLabel,useMicLabel:self.options.useMicLabel,});}else{onSuccess(null);}};function FSRTCPeerConnection(options){var gathering=false,done=false;var config={};var default_ice=[{urls:['stun:stun.l.google.com:19302']}];if(self.options.turnServer){default_ice.push(self.options.turnServer)}
+if(options.iceServers){if(typeof(options.iceServers)==="boolean"){config.iceServers=default_ice;}else{config.iceServers=options.iceServers;}}
+config.bundlePolicy="max-compat";config.sdpSemantics="plan-b";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){options.attachStream.getAudioTracks().forEach(function(track){peer.addTrack(track,options.attachStream)});options.attachStream.getVideoTracks().forEach(function(track){peer.addTrack(track,options.attachStream)});}
+if(options.attachStreams&&options.attachStreams.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.oninactive=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)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);};}
+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 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 activateLocalVideo(el,stream){el.srcObject=stream;el.style.display='block';}
+function deactivateLocalVideo(el){el.srcObject=null;el.style.display='none';}
+function assureConstraintByLabel(constraint,fallbackLabel){if(fallbackLabel===undefined&&constraint===undefined){return Promise.resolve(constraint);}
+if(typeof(assureMediaInputId)!=='function'){console.warn('Tried to use constraint fallbacks but did not found vendor function `assureMediaInputId` on window scope. Did you forget to import `vendor/media-device-id.js` before Verto?');return Promise.resolve(constraint);}
+if(typeof(constraint)==='object'&&!constraint.deviceId){return Promise.resolve(constraint);}
+if(constraint.deviceId){if(typeof(constraint.deviceId)==='string'){return new Promise(function(resolve){assureMediaInputId(fallbackLabel,constraint.deviceId).then(function(id){resolve(Object.assign({},constraint,{deviceId:id}));}).catch(function(){resolve(constraint);});});}
+if(typeof(constraint.deviceId)==='object'&&typeof(constraint.deviceId.exact)==='string'){return new Promise(function(resolve){assureMediaInputId(fallbackLabel,constraint.deviceId.exact).then(function(id){resolve(assignMediaIdToConstraint(id,constraint));}).catch(function(){resolve(constraint);});});}}
+return Promise.resolve(constraint);}
+function trustyGetUserMedia(options,constraints){navigator.mediaDevices.getUserMedia(constraints).then(function(stream){if(options.localVideo){activateLocalVideo(options.localVideo,stream);}
+if(options.onsuccess){options.onsuccess(stream);}}).catch(options.onerror||function(e){console.error(e);});}
+function assignMediaIdToConstraint(mediaId,rest){return Object.assign({},rest||{},{deviceId:{exact:mediaId}});}
+function getUserMedia(options){var constraints=options.constraints||{audio:true,video:video_constraints,};Promise.all([assureConstraintByLabel(constraints.audio,options.useMicLabel),assureConstraintByLabel(constraints.video,options.useCameraLabel),]).then(function(assurances){trustyGetUserMedia(options,{audio:assurances[0],video:assurances[1]});}).catch(function(error){console.error('Unexpected error on media id assurance attempts:',error,'Options:',options);});}
+$.FSRTC.resSupported=function(w,h){for(var i in $.FSRTC.validRes){if($.FSRTC.validRes[i][0]==w&&$.FSRTC.validRes[i][1]==h){return true;}}
+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=[[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;}
+w=resList[resI][0];h=resList[resI][1];resI++;var video={width:{exact:w},height:{exact:h}};if(cam!=="any"){video=assignMediaIdToConstraint(cam,video);}
+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.warn(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);}
+$.FSRTC.checkPerms=function(runtime,check_audio,check_video){getUserMedia({constraints:{audio:check_audio,video:check_video,},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info("media perm init complete");if(runtime){setTimeout(runtime,100,true);}},onerror:function(e){if(check_video&&check_audio){console.error("error, retesting with audio params only");return $.FSRTC.checkPerms(runtime,check_audio,false);}
+console.error("media perm init error");if(runtime){runtime(false)}}});}})(jQuery);(function($){$.JsonRpcClient=function(options){var self=this;this.options=$.extend({ajaxUrl:null,socketUrl:null,onmessage:null,login:null,passwd:null,sessid:null,loginParams:null,userVariables:null,getSocket:function(onmessage_cb){return self._getSocket(onmessage_cb);}},options);self.ws_cnt=0;this.wsOnMessage=function(event){self._wsOnMessage(event);};};$.JsonRpcClient.prototype._ws_socket=null;$.JsonRpcClient.prototype._ws_callbacks={};$.JsonRpcClient.prototype._current_id=1;$.JsonRpcClient.prototype.speedTest=function(bytes,cb){var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this.speedCB=cb;this.speedBytes=bytes;socket.send("#SPU "+bytes);var loops=bytes/1024;var rem=bytes%1024;var i;var data=new Array(1024).join(".");for(i=0;i<loops;i++){socket.send("#SPB "+data);}
+if(rem){socket.send("#SPB "+data);}
+socket.send("#SPE");}};$.JsonRpcClient.prototype.call=function(method,params,success_cb,error_cb){if(!params){params={};}
+if(this.options.sessid){params.sessid=this.options.sessid;}
+var request={jsonrpc:'2.0',method:method,params:params,id:this._current_id++};if(!success_cb){success_cb=function(e){console.log("Success: ",e);};}
+if(!error_cb){error_cb=function(e){console.log("Error: ",e);};}
+var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request,success_cb,error_cb);return;}
+if(this.options.ajaxUrl===null){throw"$.JsonRpcClient.call used with no websocket and no http endpoint.";}
+$.ajax({type:'POST',url:this.options.ajaxUrl,data:$.toJSON(request),dataType:'json',cache:false,success:function(data){if('error'in data)error_cb(data.error,this);success_cb(data.result,this);},error:function(jqXHR,textStatus,errorThrown){try{var response=$.parseJSON(jqXHR.responseText);if('console'in window)console.log(response);error_cb(response.error,this);}catch(err){error_cb({error:jqXHR.responseText},this);}}});};$.JsonRpcClient.prototype.notify=function(method,params){if(this.options.sessid){params.sessid=this.options.sessid;}
+var request={jsonrpc:'2.0',method:method,params:params};var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request);return;}
+if(this.options.ajaxUrl===null){throw"$.JsonRpcClient.notify used with no websocket and no http endpoint.";}
+$.ajax({type:'POST',url:this.options.ajaxUrl,data:$.toJSON(request),dataType:'json',cache:false});};$.JsonRpcClient.prototype.batch=function(callback,all_done_cb,error_cb){var batch=new $.JsonRpcClient._batchObject(this,all_done_cb,error_cb);callback(batch);batch._execute();};$.JsonRpcClient.prototype.socketReady=function(){if(this._ws_socket===null||this._ws_socket.readyState>1){return false;}
+return true;};$.JsonRpcClient.prototype.closeSocket=function(){var self=this;if(self.socketReady()){self._ws_socket.onclose=function(w){console.log("Closing Socket");};self._ws_socket.close();}};$.JsonRpcClient.prototype.loginData=function(params){var self=this;self.options.login=params.login;self.options.passwd=params.passwd;self.options.loginParams=params.loginParams;self.options.userVariables=params.userVariables;};$.JsonRpcClient.prototype.connectSocket=function(onmessage_cb){var self=this;if(self.to){clearTimeout(self.to);}
+if(!self.socketReady()){self.authing=false;if(self._ws_socket){delete self._ws_socket;}
+self._ws_socket=new WebSocket(self.options.socketUrl);if(self._ws_socket){self._ws_socket.onmessage=onmessage_cb;self._ws_socket.onclose=function(w){if(!self.ws_sleep){self.ws_sleep=1000;}
+if(self.options.onWSClose){self.options.onWSClose(self);}
+if(self.ws_cnt>10&&self.options.wsFallbackURL){self.options.socketUrl=self.options.wsFallbackURL;}
+console.error("Websocket Lost "+self.ws_cnt+" sleep: "+self.ws_sleep+"msec");self.to=setTimeout(function(){console.log("Attempting Reconnection....");self.connectSocket(onmessage_cb);},self.ws_sleep);self.ws_cnt++;if(self.ws_sleep<3000&&(self.ws_cnt%10)===0){self.ws_sleep+=1000;}};self._ws_socket.onopen=function(){if(self.to){clearTimeout(self.to);}
+self.ws_sleep=1000;self.ws_cnt=0;if(self.options.onWSConnect){self.options.onWSConnect(self);}
+var req;while((req=$.JsonRpcClient.q.pop())){self._ws_socket.send(req);}};}}
+return self._ws_socket?true:false;};$.JsonRpcClient.prototype.stopRetrying=function(){if(self.to)
+clearTimeout(self.to);}
+$.JsonRpcClient.prototype._getSocket=function(onmessage_cb){if(this.options.socketUrl===null||!("WebSocket"in window))return null;this.connectSocket(onmessage_cb);return this._ws_socket;};$.JsonRpcClient.q=[];$.JsonRpcClient.prototype._wsCall=function(socket,request,success_cb,error_cb){var request_json=$.toJSON(request);if(socket.readyState<1){self=this;$.JsonRpcClient.q.push(request_json);}else{socket.send(request_json);}
+if('id'in request&&typeof success_cb!=='undefined'){this._ws_callbacks[request.id]={request:request_json,request_obj:request,success_cb:success_cb,error_cb:error_cb};}};$.JsonRpcClient.prototype._wsOnMessage=function(event){var response;if(event.data[0]=="#"&&event.data[1]=="S"&&event.data[2]=="P"){if(event.data[3]=="U"){this.up_dur=parseInt(event.data.substring(4));}else if(this.speedCB&&event.data[3]=="D"){this.down_dur=parseInt(event.data.substring(4));var up_kps=(((this.speedBytes*8)/(this.up_dur/1000))/1024).toFixed(0);var down_kps=(((this.speedBytes*8)/(this.down_dur/1000))/1024).toFixed(0);console.info("Speed Test: Up: "+up_kps+" Down: "+down_kps);var cb=this.speedCB;this.speedCB=null;cb(event,{upDur:this.up_dur,downDur:this.down_dur,upKPS:up_kps,downKPS:down_kps});}
+return;}
+try{response=$.parseJSON(event.data);if(typeof response==='object'&&'jsonrpc'in response&&response.jsonrpc==='2.0'){if('result'in response&&this._ws_callbacks[response.id]){var success_cb=this._ws_callbacks[response.id].success_cb;delete this._ws_callbacks[response.id];success_cb(response.result,this);return;}else if('error'in response&&this._ws_callbacks[response.id]){var error_cb=this._ws_callbacks[response.id].error_cb;var orig_req=this._ws_callbacks[response.id].request;if(!self.authing&&response.error.code==-32000&&self.options.login&&self.options.passwd){self.authing=true;this.call("login",{login:self.options.login,passwd:self.options.passwd,loginParams:self.options.loginParams,userVariables:self.options.userVariables},this._ws_callbacks[response.id].request_obj.method=="login"?function(e){self.authing=false;console.log("logged in");delete self._ws_callbacks[response.id];if(self.options.onWSLogin){self.options.onWSLogin(true,self);}}:function(e){self.authing=false;console.log("logged in, resending request id: "+response.id);var socket=self.options.getSocket(self.wsOnMessage);if(socket!==null){socket.send(orig_req);}
+if(self.options.onWSLogin){self.options.onWSLogin(true,self);}},function(e){console.log("error logging in, request id:",response.id);delete self._ws_callbacks[response.id];error_cb(response.error,this);if(self.options.onWSLogin){self.options.onWSLogin(false,self);}});return;}
+delete this._ws_callbacks[response.id];error_cb(response.error,this);return;}}}catch(err){console.log("ERROR: "+err);return;}
+if(typeof this.options.onmessage==='function'){event.eventData=response;if(!event.eventData){event.eventData={};}
+var reply=this.options.onmessage(event);if(reply&&typeof reply==="object"&&event.eventData.id){var msg={jsonrpc:"2.0",id:event.eventData.id,result:reply};var socket=self.options.getSocket(self.wsOnMessage);if(socket!==null){socket.send($.toJSON(msg));}}}};$.JsonRpcClient._batchObject=function(jsonrpcclient,all_done_cb,error_cb){this._requests=[];this.jsonrpcclient=jsonrpcclient;this.all_done_cb=all_done_cb;this.error_cb=typeof error_cb==='function'?error_cb:function(){};};$.JsonRpcClient._batchObject.prototype.call=function(method,params,success_cb,error_cb){if(!params){params={};}
+if(this.options.sessid){params.sessid=this.options.sessid;}
+if(!success_cb){success_cb=function(e){console.log("Success: ",e);};}
+if(!error_cb){error_cb=function(e){console.log("Error: ",e);};}
+this._requests.push({request:{jsonrpc:'2.0',method:method,params:params,id:this.jsonrpcclient._current_id++},success_cb:success_cb,error_cb:error_cb});};$.JsonRpcClient._batchObject.prototype.notify=function(method,params){if(this.options.sessid){params.sessid=this.options.sessid;}
+this._requests.push({request:{jsonrpc:'2.0',method:method,params:params}});};$.JsonRpcClient._batchObject.prototype._execute=function(){var self=this;if(this._requests.length===0)return;var batch_request=[];var handlers={};var i=0;var call;var success_cb;var error_cb;var socket=self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);if(socket!==null){for(i=0;i<this._requests.length;i++){call=this._requests[i];success_cb=('success_cb'in call)?call.success_cb:undefined;error_cb=('error_cb'in call)?call.error_cb:undefined;self.jsonrpcclient._wsCall(socket,call.request,success_cb,error_cb);}
+if(typeof all_done_cb==='function')all_done_cb(result);return;}
+for(i=0;i<this._requests.length;i++){call=this._requests[i];batch_request.push(call.request);if('id'in call.request){handlers[call.request.id]={success_cb:call.success_cb,error_cb:call.error_cb};}}
+success_cb=function(data){self._batchCb(data,handlers,self.all_done_cb);};if(self.jsonrpcclient.options.ajaxUrl===null){throw"$.JsonRpcClient.batch used with no websocket and no http endpoint.";}
+$.ajax({url:self.jsonrpcclient.options.ajaxUrl,data:$.toJSON(batch_request),dataType:'json',cache:false,type:'POST',error:function(jqXHR,textStatus,errorThrown){self.error_cb(jqXHR,textStatus,errorThrown);},success:success_cb});};$.JsonRpcClient._batchObject.prototype._batchCb=function(result,handlers,all_done_cb){for(var i=0;i<result.length;i++){var response=result[i];if('error'in response){if(response.id===null||!(response.id in handlers)){if('console'in window)console.log(response);}else{handlers[response.id].error_cb(response.error,this);}}else{if(!(response.id in handlers)&&'console'in window){console.log(response);}else{handlers[response.id].success_cb(response.result,this);}}}
+if(typeof all_done_cb==='function')all_done_cb(result);};})(jQuery);(function($){var sources=[];var generateGUID=(typeof(window.crypto)!=='undefined'&&typeof(window.crypto.getRandomValues)!=='undefined')?function(){var buf=new Uint16Array(8);window.crypto.getRandomValues(buf);var S4=function(num){var ret=num.toString(16);while(ret.length<4){ret="0"+ret;}
+return ret;};return(S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-"+S4(buf[3])+"-"+S4(buf[4])+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7]));}:function(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){var r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);return v.toString(16);});};$.verto=function(options,callbacks){var verto=this;$.verto.saved.push(verto);verto.options=$.extend({login:null,passwd:null,socketUrl:null,tag:null,localTag:null,videoParams:{},audioParams:{},loginParams:{},deviceParams:{onResCheck:null},userVariables:{},iceServers:false,ringSleep:6000,sessid:null,useStream:null},options);if(verto.options.deviceParams.useCamera){$.FSRTC.getValidRes(verto.options.deviceParams.useCamera,verto.options.deviceParams.onResCheck);}
+if(!verto.options.deviceParams.useMic){verto.options.deviceParams.useMic="any";}
+if(!verto.options.deviceParams.useSpeak){verto.options.deviceParams.useSpeak="any";}
+if(verto.options.sessid){verto.sessid=verto.options.sessid;}else{verto.sessid=localStorage.getItem("verto_session_uuid")||generateGUID();localStorage.setItem("verto_session_uuid",verto.sessid);}
+verto.dialogs={};verto.callbacks=callbacks||{};verto.eventSUBS={};verto.rpcClient=new $.JsonRpcClient({login:verto.options.login,passwd:verto.options.passwd,socketUrl:verto.options.socketUrl,wsFallbackURL:verto.options.wsFallbackURL,turnServer:verto.options.turnServer,loginParams:verto.options.loginParams,userVariables:verto.options.userVariables,sessid:verto.sessid,onmessage:function(e){return verto.handleMessage(e.eventData);},onWSConnect:function(o){o.call('login',{});},onWSLogin:function(success){if(verto.callbacks.onWSLogin){verto.callbacks.onWSLogin(verto,success);}},onWSClose:function(success){if(verto.callbacks.onWSClose){verto.callbacks.onWSClose(verto,success);}
+verto.purge();}});var tag=verto.options.tag;if(typeof(tag)==="function"){tag=tag();}
+if(verto.options.ringFile&&verto.options.tag){verto.ringer=$("#"+tag);}
+verto.rpcClient.call('login',{});};$.verto.prototype.deviceParams=function(obj){var verto=this;for(var i in obj){verto.options.deviceParams[i]=obj[i];}
+if(obj.useCamera){$.FSRTC.getValidRes(verto.options.deviceParams.useCamera,obj?obj.onResCheck:undefined);}};$.verto.prototype.videoParams=function(obj){var verto=this;for(var i in obj){verto.options.videoParams[i]=obj[i];}};$.verto.prototype.iceServers=function(obj){var verto=this;verto.options.iceServers=obj;};$.verto.prototype.loginData=function(params){var verto=this;verto.options.login=params.login;verto.options.passwd=params.passwd;verto.rpcClient.loginData(params);};$.verto.prototype.logout=function(msg){var verto=this;verto.rpcClient.closeSocket();if(verto.callbacks.onWSClose){verto.callbacks.onWSClose(verto,false);}
+verto.purge();};$.verto.prototype.login=function(msg){var verto=this;verto.logout();verto.rpcClient.call('login',{});};$.verto.prototype.message=function(msg){var verto=this;var err=0;if(!msg.to){console.error("Missing To");err++;}
+if(!msg.body){console.error("Missing Body");err++;}
+if(err){return false;}
+verto.sendMethod("verto.info",{msg:msg});return true;};$.verto.prototype.processReply=function(method,success,e){var verto=this;var i;switch(method){case"verto.subscribe":for(i in e.unauthorizedChannels){drop_bad(verto,e.unauthorizedChannels[i]);}
+for(i in e.subscribedChannels){mark_ready(verto,e.subscribedChannels[i]);}
+break;case"verto.unsubscribe":break;}};$.verto.prototype.sendMethod=function(method,params){var verto=this;verto.rpcClient.call(method,params,function(e){verto.processReply(method,true,e);},function(e){verto.processReply(method,false,e);});};function do_sub(verto,channel,obj){}
+function drop_bad(verto,channel){console.error("drop unauthorized channel: "+channel);delete verto.eventSUBS[channel];}
+function mark_ready(verto,channel){for(var j in verto.eventSUBS[channel]){verto.eventSUBS[channel][j].ready=true;console.log("subscribed to channel: "+channel);if(verto.eventSUBS[channel][j].readyHandler){verto.eventSUBS[channel][j].readyHandler(verto,channel);}}}
+var SERNO=1;function do_subscribe(verto,channel,subChannels,sparams){var params=sparams||{};var local=params.local;var obj={eventChannel:channel,userData:params.userData,handler:params.handler,ready:false,readyHandler:params.readyHandler,serno:SERNO++};var isnew=false;if(!verto.eventSUBS[channel]){verto.eventSUBS[channel]=[];subChannels.push(channel);isnew=true;}
+verto.eventSUBS[channel].push(obj);if(local){obj.ready=true;obj.local=true;}
+if(!isnew&&verto.eventSUBS[channel][0].ready){obj.ready=true;if(obj.readyHandler){obj.readyHandler(verto,channel);}}
+return{serno:obj.serno,eventChannel:channel};}
+$.verto.prototype.subscribe=function(channel,sparams){var verto=this;var r=[];var subChannels=[];var params=sparams||{};if(typeof(channel)==="string"){r.push(do_subscribe(verto,channel,subChannels,params));}else{for(var i in channel){r.push(do_subscribe(verto,channel,subChannels,params));}}
+if(subChannels.length){verto.sendMethod("verto.subscribe",{eventChannel:subChannels.length==1?subChannels[0]:subChannels,subParams:params.subParams});}
+return r;};$.verto.prototype.unsubscribe=function(handle){var verto=this;var i;if(!handle){for(i in verto.eventSUBS){if(verto.eventSUBS[i]){verto.unsubscribe(verto.eventSUBS[i]);}}}else{var unsubChannels={};var sendChannels=[];var channel;if(typeof(handle)=="string"){delete verto.eventSUBS[handle];unsubChannels[handle]++;}else{for(i in handle){if(typeof(handle[i])=="string"){channel=handle[i];delete verto.eventSUBS[channel];unsubChannels[channel]++;}else{var repl=[];channel=handle[i].eventChannel;for(var j in verto.eventSUBS[channel]){if(verto.eventSUBS[channel][j].serno==handle[i].serno){}else{repl.push(verto.eventSUBS[channel][j]);}}
+verto.eventSUBS[channel]=repl;if(verto.eventSUBS[channel].length===0){delete verto.eventSUBS[channel];unsubChannels[channel]++;}}}}
+for(var u in unsubChannels){console.log("Sending Unsubscribe for: ",u);sendChannels.push(u);}
+if(sendChannels.length){verto.sendMethod("verto.unsubscribe",{eventChannel:sendChannels.length==1?sendChannels[0]:sendChannels});}}};$.verto.prototype.broadcast=function(channel,params){var verto=this;var msg={eventChannel:channel,data:{}};for(var i in params){msg.data[i]=params[i];}
+verto.sendMethod("verto.broadcast",msg);};$.verto.prototype.purge=function(callID){var verto=this;var x=0;var i;for(i in verto.dialogs){if(!x){console.log("purging dialogs");}
+x++;verto.dialogs[i].setState($.verto.enum.state.purge);}
+for(i in verto.eventSUBS){if(verto.eventSUBS[i]){console.log("purging subscription: "+i);delete verto.eventSUBS[i];}}};$.verto.prototype.hangup=function(callID){var verto=this;if(callID){var dialog=verto.dialogs[callID];if(dialog){dialog.hangup();}}else{for(var i in verto.dialogs){verto.dialogs[i].hangup();}}};$.verto.prototype.newCall=function(args,callbacks){var verto=this;if(!verto.rpcClient.socketReady()){console.error("Not Connected...");return;}
+if(args["useCamera"]){verto.options.deviceParams["useCamera"]=args["useCamera"];verto.options.deviceParams["useCameraLabel"]=args["useCameraLabel"];}
+var dialog=new $.verto.dialog($.verto.enum.direction.outbound,this,args);if(callbacks){dialog.callbacks=callbacks;}
+dialog.invite();return dialog;};$.verto.prototype.handleMessage=function(data){var verto=this;if(!(data&&data.method)){console.error("Invalid Data",data);return;}
+if(data.params.callID){var dialog=verto.dialogs[data.params.callID];if(data.method==="verto.attach"&&dialog){delete dialog.verto.dialogs[dialog.callID];dialog.rtc.stop();dialog=null;}
+if(dialog){switch(data.method){case'verto.bye':dialog.hangup(data.params);break;case'verto.answer':dialog.handleAnswer(data.params);break;case'verto.media':dialog.handleMedia(data.params);break;case'verto.display':dialog.handleDisplay(data.params);break;case'verto.info':dialog.handleInfo(data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",dialog,data.method);break;}}else{switch(data.method){case'verto.attach':data.params.attach=true;if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.useVideo=true;}
+if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
+dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);dialog.setState($.verto.enum.state.recovering);break;case'verto.invite':if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.wantVideo=true;}
+if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
+dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED");break;}}
+return{method:data.method};}else{switch(data.method){case'verto.punt':verto.purge();verto.logout();break;case'verto.event':var list=null;var key=null;if(data.params){key=data.params.eventChannel;}
+if(key){list=verto.eventSUBS[key];if(!list){list=verto.eventSUBS[key.split(".")[0]];}}
+if(!list&&key&&key===verto.sessid){if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.pvtEvent,data.params);}}else if(!list&&key&&verto.dialogs[key]){verto.dialogs[key].sendMessage($.verto.enum.message.pvtEvent,data.params);}else if(!list){if(!key){key="UNDEFINED";}
+console.error("UNSUBBED or invalid EVENT "+key+" IGNORED");}else{for(var i in list){var sub=list[i];if(!sub||!sub.ready){console.error("invalid EVENT for "+key+" IGNORED");}else if(sub.handler){sub.handler(verto,data.params,sub.userData);}else if(verto.callbacks.onEvent){verto.callbacks.onEvent(verto,data.params,sub.userData);}else{console.log("EVENT:",data.params);}}}
+break;case"verto.info":if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.info,data.params.msg);}
+console.debug("MESSAGE from: "+data.params.msg.from,data.params.msg.body);break;case'verto.clientReady':if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.clientReady,data.params);}
+console.debug("CLIENT READY",data.params);break;default:console.error("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",data.method);break;}}};var del_array=function(array,name){var r=[];var len=array.length;for(var i=0;i<len;i++){if(array[i]!=name){r.push(array[i]);}}
+return r;};var hashArray=function(){var vha=this;var hash={};var array=[];vha.reorder=function(a){array=a;var h=hash;hash={};var len=array.length;for(var i=0;i<len;i++){var key=array[i];if(h[key]){hash[key]=h[key];delete h[key];}}
+h=undefined;};vha.clear=function(){hash=undefined;array=undefined;hash={};array=[];};vha.add=function(name,val,insertAt){var redraw=false;if(!hash[name]){if(insertAt===undefined||insertAt<0||insertAt>=array.length){array.push(name);}else{var x=0;var n=[];var len=array.length;for(var i=0;i<len;i++){if(x++==insertAt){n.push(name);}
+n.push(array[i]);}
+array=undefined;array=n;n=undefined;redraw=true;}}
+hash[name]=val;return redraw;};vha.del=function(name){var r=false;if(hash[name]){array=del_array(array,name);delete hash[name];r=true;}else{console.error("can't del nonexistant key "+name);}
+return r;};vha.get=function(name){return hash[name];};vha.order=function(){return array;};vha.hash=function(){return hash;};vha.indexOf=function(name){var len=array.length;for(var i=0;i<len;i++){if(array[i]==name){return i;}}};vha.arrayLen=function(){return array.length;};vha.asArray=function(){var r=[];var len=array.length;for(var i=0;i<len;i++){var key=array[i];r.push(hash[key]);}
+return r;};vha.each=function(cb){var len=array.length;for(var i=0;i<len;i++){cb(array[i],hash[array[i]]);}};vha.dump=function(html){var str="";vha.each(function(name,val){str+="name: "+name+" val: "+JSON.stringify(val)+(html?"<br>":"\n");});return str;};};$.verto.liveArray=function(verto,context,name,config){var la=this;var lastSerno=0;var binding=null;var user_obj=config.userObj;var local=false;hashArray.call(la);la._add=la.add;la._del=la.del;la._reorder=la.reorder;la._clear=la.clear;la.context=context;la.name=name;la.user_obj=user_obj;la.verto=verto;la.broadcast=function(channel,obj){verto.broadcast(channel,obj);};la.errs=0;la.clear=function(){la._clear();lastSerno=0;if(la.onChange){la.onChange(la,{action:"clear"});}};la.checkSerno=function(serno){if(serno<0){return true;}
+if(lastSerno>0&&serno!=(lastSerno+1)){if(la.onErr){la.onErr(la,{lastSerno:lastSerno,serno:serno});}
+la.errs++;console.debug(la.errs);if(la.errs<3){la.bootstrap(la.user_obj);}
+return false;}else{lastSerno=serno;return true;}};la.reorder=function(serno,a){if(la.checkSerno(serno)){la._reorder(a);if(la.onChange){la.onChange(la,{serno:serno,action:"reorder"});}}};la.init=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){if(la.onChange){la.onChange(la,{serno:serno,action:"init",index:index,key:key,data:val});}}};la.bootObj=function(serno,val){if(la.checkSerno(serno)){for(var i in val){la._add(val[i][0],val[i][1]);}
+if(la.onChange){la.onChange(la,{serno:serno,action:"bootObj",data:val,redraw:true});}}};la.add=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){var redraw=la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"add",index:index,key:key,data:val,redraw:redraw});}}};la.modify=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"modify",key:key,data:val,index:index});}}};la.del=function(serno,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){if(index===null||index<0||index===undefined){index=la.indexOf(key);}
+var ok=la._del(key);if(ok&&la.onChange){la.onChange(la,{serno:serno,action:"del",key:key,index:index});}}};var eventHandler=function(v,e,la){var packet=e.data;if(packet.name!=la.name){return;}
+switch(packet.action){case"init":la.init(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"bootObj":la.bootObj(packet.wireSerno,packet.data);break;case"add":la.add(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"modify":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.modify(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);}
+break;case"del":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.del(packet.wireSerno,packet.hashKey,packet.arrIndex);}
+break;case"clear":la.clear();break;case"reorder":la.reorder(packet.wireSerno,packet.order);break;default:if(la.checkSerno(packet.wireSerno)){if(la.onChange){la.onChange(la,{serno:packet.wireSerno,action:packet.action,data:packet.data});}}
+break;}};if(la.context){binding=la.verto.subscribe(la.context,{handler:eventHandler,userData:la,subParams:config.subParams});}
+la.destroy=function(){la._clear();la.verto.unsubscribe(binding);};la.sendCommand=function(cmd,obj){var self=la;self.broadcast(self.context,{liveArray:{command:cmd,context:self.context,name:self.name,obj:obj}});};la.bootstrap=function(obj){var self=la;la.sendCommand("bootstrap",obj);};la.changepage=function(obj){var self=la;self.clear();self.broadcast(self.context,{liveArray:{command:"changepage",context:la.context,name:la.name,obj:obj}});};la.heartbeat=function(obj){var self=la;var callback=function(){self.heartbeat.call(self,obj);};self.broadcast(self.context,{liveArray:{command:"heartbeat",context:self.context,name:self.name,obj:obj}});self.hb_pid=setTimeout(callback,30000);};la.bootstrap(la.user_obj);};$.verto.liveTable=function(verto,context,name,jq,config){var dt;var la=new $.verto.liveArray(verto,context,name,{subParams:config.subParams});var lt=this;lt.liveArray=la;lt.dataTable=dt;lt.verto=verto;lt.destroy=function(){if(dt){dt.fnDestroy();}
+if(la){la.destroy();}
+dt=null;la=null;};la.onErr=function(obj,args){console.error("Error: ",obj,args);};function genRow(data){if(typeof(data[4])==="string"&&data[4].indexOf("{")>-1){var tmp=$.parseJSON(data[4]);data[4]=tmp.oldStatus;data[5]=null;}
+return data;}
+function genArray(obj){var data=obj.asArray();for(var i in data){data[i]=genRow(data[i]);}
+return data;}
+la.onChange=function(obj,args){var index=0;var iserr=0;if(!dt){if(!config.aoColumns){if(args.action!="init"){return;}
+config.aoColumns=[];for(var i in args.data){config.aoColumns.push({"sTitle":args.data[i]});}}
+dt=jq.dataTable(config);}
+if(dt&&(args.action=="del"||args.action=="modify")){index=args.index;if(index===undefined&&args.key){index=la.indexOf(args.key);}
+if(index===undefined){console.error("INVALID PACKET Missing INDEX\n",args);return;}}
+if(config.onChange){config.onChange(obj,args);}
+try{switch(args.action){case"bootObj":if(!args.data){console.error("missing data");return;}
+dt.fnClearTable();dt.fnAddData(genArray(obj));dt.fnAdjustColumnSizing();break;case"add":if(!args.data){console.error("missing data");return;}
+if(args.redraw>-1){dt.fnClearTable();dt.fnAddData(genArray(obj));}else{dt.fnAddData(genRow(args.data));}
+dt.fnAdjustColumnSizing();break;case"modify":if(!args.data){return;}
+dt.fnUpdate(genRow(args.data),index);dt.fnAdjustColumnSizing();break;case"del":dt.fnDeleteRow(index);dt.fnAdjustColumnSizing();break;case"clear":dt.fnClearTable();break;case"reorder":dt.fnClearTable();dt.fnAddData(genArray(obj));break;case"hide":jq.hide();break;case"show":jq.show();break;}}catch(err){console.error("ERROR: "+err);iserr++;}
+if(iserr){obj.errs++;if(obj.errs<3){obj.bootstrap(obj.user_obj);}}else{obj.errs=0;}};la.onChange(la,{action:"init"});};var CONFMAN_SERNO=1;$.verto.conf=function(verto,params){var conf=this;conf.params=$.extend({dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);conf.verto=verto;conf.serno=CONFMAN_SERNO++;createMainModeratorMethods();verto.subscribe(conf.params.laData.modChannel,{handler:function(v,e){if(conf.params.onBroadcast){conf.params.onBroadcast(verto,conf,e.data);}}});verto.subscribe(conf.params.laData.infoChannel,{handler:function(v,e){if(typeof(conf.params.infoCallback)==="function"){conf.params.infoCallback(v,e);}}});verto.subscribe(conf.params.laData.chatChannel,{handler:function(v,e){if(typeof(conf.params.chatCallback)==="function"){conf.params.chatCallback(v,e);}}});};$.verto.conf.prototype.modCommand=function(cmd,id,value){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.conf.prototype.destroy=function(){var conf=this;conf.destroyed=true;conf.params.onBroadcast(conf.verto,conf,'destroy');if(conf.params.laData.modChannel){conf.verto.unsubscribe(conf.params.laData.modChannel);}
+if(conf.params.laData.chatChannel){conf.verto.unsubscribe(conf.params.laData.chatChannel);}
+if(conf.params.laData.infoChannel){conf.verto.unsubscribe(conf.params.laData.infoChannel);}};function createMainModeratorMethods(){$.verto.conf.prototype.listVideoLayouts=function(){this.modCommand("list-videoLayouts",null,null);};$.verto.conf.prototype.play=function(file){this.modCommand("play",null,file);};$.verto.conf.prototype.stop=function(){this.modCommand("stop",null,"all");};$.verto.conf.prototype.deaf=function(memberID){this.modCommand("deaf",parseInt(memberID));};$.verto.conf.prototype.undeaf=function(memberID){this.modCommand("undeaf",parseInt(memberID));};$.verto.conf.prototype.record=function(file){this.modCommand("recording",null,["start",file]);};$.verto.conf.prototype.stopRecord=function(){this.modCommand("recording",null,["stop","all"]);};$.verto.conf.prototype.snapshot=function(file){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-write-png",null,file);};$.verto.conf.prototype.setVideoLayout=function(layout,canvasID){if(!this.params.hasVid){throw'Conference has no video';}
+if(canvasID){this.modCommand("vid-layout",null,[layout,canvasID]);}else{this.modCommand("vid-layout",null,layout);}};$.verto.conf.prototype.kick=function(memberID){this.modCommand("kick",parseInt(memberID));};$.verto.conf.prototype.muteMic=function(memberID){this.modCommand("tmute",parseInt(memberID));};$.verto.conf.prototype.muteVideo=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("tvmute",parseInt(memberID));};$.verto.conf.prototype.presenter=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-res-id",parseInt(memberID),"presenter");};$.verto.conf.prototype.videoFloor=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-floor",parseInt(memberID),"force");};$.verto.conf.prototype.banner=function(memberID,text){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-banner",parseInt(memberID),escape(text));};$.verto.conf.prototype.volumeDown=function(memberID){this.modCommand("volume_out",parseInt(memberID),"down");};$.verto.conf.prototype.volumeUp=function(memberID){this.modCommand("volume_out",parseInt(memberID),"up");};$.verto.conf.prototype.gainDown=function(memberID){this.modCommand("volume_in",parseInt(memberID),"down");};$.verto.conf.prototype.gainUp=function(memberID){this.modCommand("volume_in",parseInt(memberID),"up");};$.verto.conf.prototype.transfer=function(memberID,exten){this.modCommand("transfer",parseInt(memberID),exten);};$.verto.conf.prototype.sendChat=function(message,type){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};}
+$.verto.modfuncs={};$.verto.confMan=function(verto,params){var confMan=this;confMan.params=$.extend({tableID:null,statusID:null,mainModID:null,dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);confMan.verto=verto;confMan.serno=CONFMAN_SERNO++;confMan.canvasCount=confMan.params.laData.canvasCount;function genMainMod(jq){var play_id="play_"+confMan.serno;var stop_id="stop_"+confMan.serno;var recording_id="recording_"+confMan.serno;var snapshot_id="snapshot_"+confMan.serno;var rec_stop_id="recording_stop"+confMan.serno;var div_id="confman_"+confMan.serno;var html="<div id='"+div_id+"'><br>"+"<button class='ctlbtn' id='"+play_id+"'>Play</button>"+"<button class='ctlbtn' id='"+stop_id+"'>Stop</button>"+"<button class='ctlbtn' id='"+recording_id+"'>Record</button>"+"<button class='ctlbtn' id='"+rec_stop_id+"'>Record Stop</button>"+
+(confMan.params.hasVid?"<button class='ctlbtn' id='"+snapshot_id+"'>PNG Snapshot</button>":"")+"<br><br></div>";jq.html(html);$.verto.modfuncs.change_video_layout=function(id,canvas_id){var val=$("#"+id+" option:selected").text();if(val!=="none"){confMan.modCommand("vid-layout",null,[val,canvas_id]);}};if(confMan.params.hasVid){for(var j=0;j<confMan.canvasCount;j++){var vlayout_id="confman_vid_layout_"+j+"_"+confMan.serno;var vlselect_id="confman_vl_select_"+j+"_"+confMan.serno;var vlhtml="<div id='"+vlayout_id+"'><br>"+"<b>Video Layout Canvas "+(j+1)+"</b> <select onChange='$.verto.modfuncs.change_video_layout(\""+vlayout_id+"\", \""+(j+1)+"\")' id='"+vlselect_id+"'></select> "+"<br><br></div>";jq.append(vlhtml);}
+$("#"+snapshot_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("vid-write-png",null,file);}});}
+$("#"+play_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("play",null,file);}});$("#"+stop_id).click(function(){confMan.modCommand("stop",null,"all");});$("#"+recording_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("recording",null,["start",file]);}});$("#"+rec_stop_id).click(function(){confMan.modCommand("recording",null,["stop","all"]);});}
+function genControls(jq,rowid){var x=parseInt(rowid);var kick_id="kick_"+x;var canvas_in_next_id="canvas_in_next_"+x;var canvas_in_prev_id="canvas_in_prev_"+x;var canvas_out_next_id="canvas_out_next_"+x;var canvas_out_prev_id="canvas_out_prev_"+x;var canvas_in_set_id="canvas_in_set_"+x;var canvas_out_set_id="canvas_out_set_"+x;var layer_set_id="layer_set_"+x;var layer_next_id="layer_next_"+x;var layer_prev_id="layer_prev_"+x;var tmute_id="tmute_"+x;var tvmute_id="tvmute_"+x;var vbanner_id="vbanner_"+x;var tvpresenter_id="tvpresenter_"+x;var tvfloor_id="tvfloor_"+x;var box_id="box_"+x;var gainup_id="gain_in_up"+x;var gaindn_id="gain_in_dn"+x;var volup_id="vol_in_up"+x;var voldn_id="vol_in_dn"+x;var transfer_id="transfer"+x;var html="<div id='"+box_id+"'>";html+="<b>General Controls</b><hr noshade>";html+="<button class='ctlbtn' id='"+kick_id+"'>Kick</button>"+"<button class='ctlbtn' id='"+tmute_id+"'>Mute</button>"+"<button class='ctlbtn' id='"+gainup_id+"'>Gain -</button>"+"<button class='ctlbtn' id='"+gaindn_id+"'>Gain +</button>"+"<button class='ctlbtn' id='"+voldn_id+"'>Vol -</button>"+"<button class='ctlbtn' id='"+volup_id+"'>Vol +</button>"+"<button class='ctlbtn' id='"+transfer_id+"'>Transfer</button>";if(confMan.params.hasVid){html+="<br><br><b>Video Controls</b><hr noshade>";html+="<button class='ctlbtn' id='"+tvmute_id+"'>VMute</button>"+"<button class='ctlbtn' id='"+tvpresenter_id+"'>Presenter</button>"+"<button class='ctlbtn' id='"+tvfloor_id+"'>Vid Floor</button>"+"<button class='ctlbtn' id='"+vbanner_id+"'>Banner</button>";if(confMan.canvasCount>1){html+="<br><br><b>Canvas Controls</b><hr noshade>"+"<button class='ctlbtn' id='"+canvas_in_set_id+"'>Set Input Canvas</button>"+"<button class='ctlbtn' id='"+canvas_in_prev_id+"'>Prev Input Canvas</button>"+"<button class='ctlbtn' id='"+canvas_in_next_id+"'>Next Input Canvas</button>"+"<br>"+"<button class='ctlbtn' id='"+canvas_out_set_id+"'>Set Watching Canvas</button>"+"<button class='ctlbtn' id='"+canvas_out_prev_id+"'>Prev Watching Canvas</button>"+"<button class='ctlbtn' id='"+canvas_out_next_id+"'>Next Watching Canvas</button>";}
+html+="<br>"+"<button class='ctlbtn' id='"+layer_set_id+"'>Set Layer</button>"+"<button class='ctlbtn' id='"+layer_prev_id+"'>Prev Layer</button>"+"<button class='ctlbtn' id='"+layer_next_id+"'>Next Layer</button>"+"</div>";}
+jq.html(html);if(!jq.data("mouse")){$("#"+box_id).hide();}
+jq.mouseover(function(e){jq.data({"mouse":true});$("#"+box_id).show();});jq.mouseout(function(e){jq.data({"mouse":false});$("#"+box_id).hide();});$("#"+transfer_id).click(function(){var xten=prompt("Enter Extension");if(xten){confMan.modCommand("transfer",x,xten);}});$("#"+kick_id).click(function(){confMan.modCommand("kick",x);});$("#"+layer_set_id).click(function(){var cid=prompt("Please enter layer ID","");if(cid){confMan.modCommand("vid-layer",x,cid);}});$("#"+layer_next_id).click(function(){confMan.modCommand("vid-layer",x,"next");});$("#"+layer_prev_id).click(function(){confMan.modCommand("vid-layer",x,"prev");});$("#"+canvas_in_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-canvas",x,cid);}});$("#"+canvas_out_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-watching-canvas",x,cid);}});$("#"+canvas_in_next_id).click(function(){confMan.modCommand("vid-canvas",x,"next");});$("#"+canvas_in_prev_id).click(function(){confMan.modCommand("vid-canvas",x,"prev");});$("#"+canvas_out_next_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"next");});$("#"+canvas_out_prev_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"prev");});$("#"+tmute_id).click(function(){confMan.modCommand("tmute",x);});if(confMan.params.hasVid){$("#"+tvmute_id).click(function(){confMan.modCommand("tvmute",x);});$("#"+tvpresenter_id).click(function(){confMan.modCommand("vid-res-id",x,"presenter");});$("#"+tvfloor_id).click(function(){confMan.modCommand("vid-floor",x,"force");});$("#"+vbanner_id).click(function(){var text=prompt("Please enter text","");if(text){confMan.modCommand("vid-banner",x,escape(text));}});}
+$("#"+gainup_id).click(function(){confMan.modCommand("volume_in",x,"up");});$("#"+gaindn_id).click(function(){confMan.modCommand("volume_in",x,"down");});$("#"+volup_id).click(function(){confMan.modCommand("volume_out",x,"up");});$("#"+voldn_id).click(function(){confMan.modCommand("volume_out",x,"down");});return html;}
+var atitle="";var awidth=0;verto.subscribe(confMan.params.laData.infoChannel,{handler:function(v,e){if(typeof(confMan.params.infoCallback)==="function"){confMan.params.infoCallback(v,e);}}});verto.subscribe(confMan.params.laData.chatChannel,{handler:function(v,e){if(typeof(confMan.params.chatCallback)==="function"){confMan.params.chatCallback(v,e);}}});if(confMan.params.laData.role==="moderator"){atitle="Action";awidth=600;if(confMan.params.mainModID){genMainMod($(confMan.params.mainModID));$(confMan.params.displayID).html("Moderator Controls Ready<br><br>");}else{$(confMan.params.mainModID).html("");}
+verto.subscribe(confMan.params.laData.modChannel,{handler:function(v,e){if(confMan.params.onBroadcast){confMan.params.onBroadcast(verto,confMan,e.data);}
+if(e.data["conf-command"]==="list-videoLayouts"){for(var j=0;j<confMan.canvasCount;j++){var vlselect_id="#confman_vl_select_"+j+"_"+confMan.serno;var vlayout_id="#confman_vid_layout_"+j+"_"+confMan.serno;var x=0;var options;$(vlselect_id).selectmenu({});$(vlselect_id).selectmenu("enable");$(vlselect_id).empty();$(vlselect_id).append(new Option("Choose a Layout","none"));if(e.data.responseData){var rdata=[];for(var i in e.data.responseData){rdata.push(e.data.responseData[i].name);}
+options=rdata.sort(function(a,b){var ga=a.substring(0,6)=="group:"?true:false;var gb=b.substring(0,6)=="group:"?true:false;if((ga||gb)&&ga!=gb){return ga?-1:1;}
+return((a==b)?0:((a>b)?1:-1));});for(var i in options){$(vlselect_id).append(new Option(options[i],options[i]));x++;}}
+if(x){$(vlselect_id).selectmenu('refresh',true);}else{$(vlayout_id).hide();}}}else{if(!confMan.destroyed&&confMan.params.displayID){$(confMan.params.displayID).html(e.data.response+"<br><br>");if(confMan.lastTimeout){clearTimeout(confMan.lastTimeout);confMan.lastTimeout=0;}
+confMan.lastTimeout=setTimeout(function(){$(confMan.params.displayID).html(confMan.destroyed?"":"Moderator Controls Ready<br><br>");},4000);}}}});if(confMan.params.hasVid){confMan.modCommand("list-videoLayouts",null,null);}}
+var row_callback=null;if(confMan.params.laData.role==="moderator"){row_callback=function(nRow,aData,iDisplayIndex,iDisplayIndexFull){if(!aData[5]){var $row=$('td:eq(5)',nRow);genControls($row,aData);if(confMan.params.onLaRow){confMan.params.onLaRow(verto,confMan,$row,aData);}}};}
+confMan.lt=new $.verto.liveTable(verto,confMan.params.laData.laChannel,confMan.params.laData.laName,$(confMan.params.tableID),{subParams:{callID:confMan.params.dialog?confMan.params.dialog.callID:null},"onChange":function(obj,args){$(confMan.params.statusID).text("Conference Members: "+" ("+obj.arrayLen()+" Total)");if(confMan.params.onLaChange){confMan.params.onLaChange(verto,confMan,$.verto.enum.confEvent.laChange,obj,args);}},"aaData":[],"aoColumns":[{"sTitle":"ID","sWidth":"50"},{"sTitle":"Number","sWidth":"250"},{"sTitle":"Name","sWidth":"250"},{"sTitle":"Codec","sWidth":"100"},{"sTitle":"Status","sWidth":confMan.params.hasVid?"200px":"150px"},{"sTitle":atitle,"sWidth":awidth,}],"bAutoWidth":true,"bDestroy":true,"bSort":false,"bInfo":false,"bFilter":false,"bLengthChange":false,"bPaginate":false,"iDisplayLength":1400,"oLanguage":{"sEmptyTable":"The Conference is Empty....."},"fnRowCallback":row_callback});};$.verto.confMan.prototype.modCommand=function(cmd,id,value){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.confMan.prototype.sendChat=function(message,type){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};$.verto.confMan.prototype.destroy=function(){var confMan=this;confMan.destroyed=true;if(confMan.lt){confMan.lt.destroy();}
+if(confMan.params.laData.chatChannel){confMan.verto.unsubscribe(confMan.params.laData.chatChannel);}
+if(confMan.params.laData.modChannel){confMan.verto.unsubscribe(confMan.params.laData.modChannel);}
+if(confMan.params.mainModID){$(confMan.params.mainModID).html("");}};$.verto.dialog=function(direction,verto,params){var dialog=this;dialog.params=$.extend({useVideo:verto.options.useVideo,useStereo:verto.options.useStereo,screenShare:false,useCamera:false,useMic:verto.options.deviceParams.useMic,useMicLabel:verto.options.deviceParams.useMicLabel,useSpeak:verto.options.deviceParams.useSpeak,tag:verto.options.tag,localTag:verto.options.localTag,login:verto.options.login,videoParams:verto.options.videoParams,useStream:verto.options.useStream,},params);if(!dialog.params.screenShare){dialog.params.useCamera=verto.options.deviceParams.useCamera;dialog.params.useCameraLabel=verto.options.deviceParams.useCameraLabel;}
+dialog.verto=verto;dialog.direction=direction;dialog.lastState=null;dialog.state=dialog.lastState=$.verto.enum.state.new;dialog.callbacks=verto.callbacks;dialog.answered=false;dialog.attach=params.attach||false;dialog.screenShare=params.screenShare||false;dialog.useCamera=dialog.params.useCamera;dialog.useCameraLabel=dialog.params.useCameraLabel;dialog.useMic=dialog.params.useMic;dialog.useMicLabel=dialog.params.useMicLabel;dialog.useSpeak=dialog.params.useSpeak;if(dialog.params.callID){dialog.callID=dialog.params.callID;}else{dialog.callID=dialog.params.callID=generateGUID();}
+if(typeof(dialog.params.tag)==="function"){dialog.params.tag=dialog.params.tag();}
+if(dialog.params.tag){dialog.audioStream=document.getElementById(dialog.params.tag);if(dialog.params.useVideo){dialog.videoStream=dialog.audioStream;}}
+if(dialog.params.localTag){dialog.localVideo=document.getElementById(dialog.params.localTag);}
+dialog.verto.dialogs[dialog.callID]=dialog;var RTCcallbacks={};if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.params.display_direction==="outbound"){dialog.params.remote_caller_id_name=dialog.params.caller_id_name;dialog.params.remote_caller_id_number=dialog.params.caller_id_number;}else{dialog.params.remote_caller_id_name=dialog.params.callee_id_name;dialog.params.remote_caller_id_number=dialog.params.callee_id_number;}
+if(!dialog.params.remote_caller_id_name){dialog.params.remote_caller_id_name="Nobody";}
+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){if(dialog.callbacks.permissionCallback&&typeof dialog.callbacks.permissionCallback.onGranted==='function'){dialog.callbacks.permissionCallback.onGranted(stream);}
+else if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onGranted==='function'){dialog.verto.options.permissionCallback.onGranted(stream);}
+console.log("stream started");};RTCcallbacks.onRemoteStream=function(rtc,stream){if(typeof dialog.callbacks.onRemoteStream==='function'){dialog.callbacks.onRemoteStream(stream,dialog);}
+console.log("remote stream started");};RTCcallbacks.onError=function(e){if(dialog.callbacks.permissionCallback&&typeof dialog.callbacks.permissionCallback.onDenied==='function'){dialog.callbacks.permissionCallback.onDenied();}
+else 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,useCameraLabel:dialog.useCameraLabel,useMic:dialog.useMic,useMicLabel:dialog.useMicLabel,useSpeak:dialog.useSpeak,turnServer:verto.options.turnServer,useStream:dialog.params.useStream});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((obj.noDialogParams&&i!="callID")){continue;}
+obj.dialogParams[i]=dialog.params[i];}
+delete obj.noDialogParams;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;}
+function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}}
+return id;}
+$.verto.dialog.prototype.setAudioPlaybackDevice=function(sinkId,callback,arg){var dialog=this;var element=dialog.audioStream;if(typeof element.sinkId!=='undefined'){var devname=find_name(sinkId);console.info("Dialog: "+dialog.callID+" Setting speaker:",element,devname);element.setSinkId(sinkId).then(function(){console.log("Dialog: "+dialog.callID+' Success, audio output device attached: '+sinkId);if(callback){callback(true,devname,arg);}}).catch(function(error){var errorMessage=error;if(error.name==='SecurityError'){errorMessage="Dialog: "+dialog.callID+' You need to use HTTPS for selecting audio output '+'device: '+error;}
+if(callback){callback(false,null,arg);}
+console.error(errorMessage);});}else{console.warn("Dialog: "+dialog.callID+' Browser does not support output device selection.');if(callback){callback(false,null,arg);}}}
+$.verto.dialog.prototype.setState=function(state){var dialog=this;if(dialog.state==$.verto.enum.state.ringing){dialog.stopRinging();}
+if(dialog.state==state||!checkStateChange(dialog.state,state)){console.error("Dialog "+dialog.callID+": INVALID state change from "+dialog.state.name+" to "+state.name);dialog.hangup();return false;}
+console.log("Dialog "+dialog.callID+": state change from "+dialog.state.name+" to "+state.name);dialog.lastState=dialog.state;dialog.state=state;if(dialog.callbacks.onDialogState){dialog.callbacks.onDialogState(this);}
+switch(dialog.state){case $.verto.enum.state.early:case $.verto.enum.state.active:var speaker=dialog.useSpeak;console.info("Using Speaker: ",speaker);if(speaker&&speaker!=="any"&&speaker!=="none"){setTimeout(function(){dialog.setAudioPlaybackDevice(speaker);},500);}
+break;case $.verto.enum.state.trying:setTimeout(function(){if(dialog.state==$.verto.enum.state.trying){dialog.setState($.verto.enum.state.hangup);}},30000);break;case $.verto.enum.state.purge:dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.hangup:if(dialog.lastState.val>$.verto.enum.state.requesting.val&&dialog.lastState.val<$.verto.enum.state.hangup.val){dialog.sendMethod("verto.bye",{});}
+dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.destroy:if(typeof(dialog.verto.options.tag)==="function"){$('#'+dialog.params.tag).remove();}
+delete dialog.verto.dialogs[dialog.callID];if(dialog.params.screenShare){dialog.rtc.stopPeer();}else{dialog.rtc.stop();}
+break;}
+return true;};$.verto.dialog.prototype.processReply=function(method,success,e){var dialog=this;switch(method){case"verto.answer":case"verto.attach":if(success){dialog.setState($.verto.enum.state.active);}else{dialog.hangup();}
+break;case"verto.invite":if(success){dialog.setState($.verto.enum.state.trying);}else{dialog.setState($.verto.enum.state.destroy);}
+break;case"verto.bye":dialog.hangup();break;case"verto.modify":if(e.holdState){if(e.holdState=="held"){if(dialog.state!=$.verto.enum.state.held){dialog.setState($.verto.enum.state.held);}}else if(e.holdState=="active"){if(dialog.state!=$.verto.enum.state.active){dialog.setState($.verto.enum.state.active);}}}
+if(success){}
+break;default:break;}};$.verto.dialog.prototype.hangup=function(params){var dialog=this;if(params){if(params.causeCode){dialog.causeCode=params.causeCode;}
+if(params.cause){dialog.cause=params.cause;}}
+if(!dialog.cause&&!dialog.causeCode){dialog.cause="NORMAL_CLEARING";}
+if(dialog.state.val>=$.verto.enum.state.new.val&&dialog.state.val<$.verto.enum.state.hangup.val){dialog.setState($.verto.enum.state.hangup);}else if(dialog.state.val<$.verto.enum.state.destroy){dialog.setState($.verto.enum.state.destroy);}};$.verto.dialog.prototype.stopRinging=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.stop();}};$.verto.dialog.prototype.indicateRing=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.attr("src",dialog.verto.options.ringFile)[0].play();setTimeout(function(){dialog.stopRinging();if(dialog.state==$.verto.enum.state.ringing){dialog.indicateRing();}},dialog.verto.options.ringSleep);}};$.verto.dialog.prototype.ring=function(){var dialog=this;dialog.setState($.verto.enum.state.ringing);dialog.indicateRing();};$.verto.dialog.prototype.useVideo=function(on){var dialog=this;dialog.params.useVideo=on;if(on){dialog.videoStream=dialog.audioStream;}else{dialog.videoStream=null;}
+dialog.rtc.useVideo(dialog.videoStream,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.verto.dialog.prototype.useStereo=function(on){var dialog=this;dialog.params.useStereo=on;dialog.rtc.useStereo(on);};$.verto.dialog.prototype.dtmf=function(digits){var dialog=this;if(digits){dialog.sendMethod("verto.info",{dtmf:digits});}};$.verto.dialog.prototype.rtt=function(obj){var dialog=this;var pobj={};if(!obj){return false;}
+pobj.code=obj.code;pobj.chars=obj.chars;if(pobj.chars||pobj.code){dialog.sendMethod("verto.info",{txt:obj,noDialogParams:true});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.replace=function(replaceCallID,params){var dialog=this;if(replaceCallID){dialog.sendMethod("verto.modify",{action:"replace",replaceCallID:replaceCallID,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;if(!msg.to){console.error("Missing To");err++;}
+if(!msg.body){console.error("Missing Body");err++;}
+if(err){return false;}
+dialog.sendMethod("verto.info",{msg:msg});return true;};$.verto.dialog.prototype.answer=function(params){var dialog=this;if(!dialog.answered){if(!params){params={};}
+params.sdp=dialog.params.sdp;if(params){if(params.useVideo){dialog.useVideo(true);}
+dialog.params.callee_id_name=params.callee_id_name;dialog.params.callee_id_number=params.callee_id_number;if(params.useCamera){dialog.useCamera=params.useCamera;dialog.useCameraLabel=params.useCameraLabel;}
+if(params.useMic){dialog.useMic=params.useMic;dialog.useMic=params.useMicLabel;}
+if(params.useSpeak){dialog.useSpeak=params.useSpeak;}}
+dialog.rtc.createAnswer(params);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(params){var dialog=this;dialog.gotAnswer=true;if(dialog.state.val>=$.verto.enum.state.active.val){return;}
+if(dialog.state.val>=$.verto.enum.state.early.val){dialog.setState($.verto.enum.state.active);}else{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" &lt;":" <")+dialog.params.remote_caller_id_number+(enc?"&gt;":">");return party;};$.verto.dialog.prototype.sendMessage=function(msg,params){var dialog=this;if(dialog.callbacks.onMessage){dialog.callbacks.onMessage(dialog.verto,dialog,msg,params);}};$.verto.dialog.prototype.handleInfo=function(params){var dialog=this;dialog.sendMessage($.verto.enum.message.info,params);};$.verto.dialog.prototype.handleDisplay=function(params){var dialog=this;if(params.display_name){dialog.params.remote_caller_id_name=params.display_name;}
+if(params.display_number){dialog.params.remote_caller_id_number=params.display_number;}
+dialog.sendMessage($.verto.enum.message.display,{});};$.verto.dialog.prototype.handleMedia=function(params){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;}
+dialog.gotEarly=true;dialog.rtc.answer(params.sdp,function(){console.log("Dialog "+dialog.callID+"Establishing early media");dialog.setState($.verto.enum.state.early);if(dialog.gotAnswer){console.log("Dialog "+dialog.callID+"Answering Channel");dialog.setState($.verto.enum.state.active);}},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"EARLY SDP",params.sdp);};$.verto.ENUM=function(s){var i=0,o={};s.split(" ").map(function(x){o[x]={name:x,val:i++};});return Object.freeze(o);};$.verto.enum={};$.verto.enum.states=Object.freeze({new:{requesting:1,recovering:1,ringing:1,destroy:1,answering:1,hangup:1},requesting:{trying:1,hangup:1,active:1},recovering:{answering:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{answering:1,requesting:1,hangup:1,held:1},held:{hangup:1,active:1},early:{hangup:1,active:1},hangup:{destroy:1},destroy:{},purge:{destroy:1}});$.verto.enum.state=$.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent clientReady");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$.verto.unloadJobs=[];var unloadEventName='beforeunload';var iOS=['iPad','iPhone','iPod'].indexOf(navigator.platform)>=0;if(iOS){unloadEventName='pagehide';}
+$(window).bind(unloadEventName,function(){for(var f in $.verto.unloadJobs){$.verto.unloadJobs[f]();}
+if($.verto.haltClosure)
+return $.verto.haltClosure();for(var i in $.verto.saved){var verto=$.verto.saved[i];if(verto){verto.purge();verto.logout();}}
+return $.verto.warnOnUnload;});$.verto.videoDevices=[];$.verto.audioInDevices=[];$.verto.audioOutDevices=[];var checkDevices=function(runtime){console.info("enumerating devices");var aud_in=[],aud_out=[],vid=[];var has_video=0,has_audio=0;var Xstream;function gotDevices(deviceInfos){for(var i=0;i!==deviceInfos.length;++i){var deviceInfo=deviceInfos[i];var text="";console.log(deviceInfo);console.log(deviceInfo.kind+": "+deviceInfo.label+" id = "+deviceInfo.deviceId);if(deviceInfo.kind==='audioinput'){text=deviceInfo.label||'microphone '+(aud_in.length+1);aud_in.push({id:deviceInfo.deviceId,kind:"audio_in",label:text});}else if(deviceInfo.kind==='audiooutput'){text=deviceInfo.label||'speaker '+(aud_out.length+1);aud_out.push({id:deviceInfo.deviceId,kind:"audio_out",label:text});}else if(deviceInfo.kind==='videoinput'){text=deviceInfo.label||'camera '+(vid.length+1);vid.push({id:deviceInfo.deviceId,kind:"video",label:text});}else{console.log('Some other kind of source/device: ',deviceInfo);}}
+$.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);if(Xstream){Xstream.getTracks().forEach(function(track){track.stop();});}
+if(runtime){runtime(true);}}
+function handleError(error){console.log('device enumeration error: ',error);if(runtime)runtime(false);}
+function checkTypes(devs){for(var i=0;i!==devs.length;++i){if(devs[i].kind==='audioinput'){has_audio++;}else if(devs[i].kind==='videoinput'){has_video++;}}
+navigator.getUserMedia({audio:(has_audio>0?true:false),video:(has_video>0?true:false)},function(stream){Xstream=stream;navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);},function(err){console.log("The following error occurred: "+err.name);});}
+navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);};$.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);(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(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){'use strict';var _adapter_factory=require('./adapter_factory.js');var adapter=(0,_adapter_factory.adapterFactory)({window:window});module.exports=adapter;},{"./adapter_factory.js":2}],2:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.adapterFactory=adapterFactory;var _utils=require('./utils');var utils=_interopRequireWildcard(_utils);var _chrome_shim=require('./chrome/chrome_shim');var chromeShim=_interopRequireWildcard(_chrome_shim);var _edge_shim=require('./edge/edge_shim');var edgeShim=_interopRequireWildcard(_edge_shim);var _firefox_shim=require('./firefox/firefox_shim');var firefoxShim=_interopRequireWildcard(_firefox_shim);var _safari_shim=require('./safari/safari_shim');var safariShim=_interopRequireWildcard(_safari_shim);var _common_shim=require('./common_shim');var commonShim=_interopRequireWildcard(_common_shim);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function adapterFactory(){var _ref=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{},window=_ref.window;var options=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{shimChrome:true,shimFirefox:true,shimEdge:true,shimSafari:true};var logging=utils.log;var browserDetails=utils.detectBrowser(window);var adapter={browserDetails:browserDetails,commonShim:commonShim,extractVersion:utils.extractVersion,disableLog:utils.disableLog,disableWarnings:utils.disableWarnings};switch(browserDetails.browser){case'chrome':if(!chromeShim||!chromeShim.shimPeerConnection||!options.shimChrome){logging('Chrome shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming chrome.');adapter.browserShim=chromeShim;chromeShim.shimGetUserMedia(window);chromeShim.shimMediaStream(window);chromeShim.shimPeerConnection(window);chromeShim.shimOnTrack(window);chromeShim.shimAddTrackRemoveTrack(window);chromeShim.shimGetSendersWithDtmf(window);chromeShim.shimSenderReceiverGetStats(window);chromeShim.fixNegotiationNeeded(window);commonShim.shimRTCIceCandidate(window);commonShim.shimConnectionState(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);commonShim.removeAllowExtmapMixed(window);break;case'firefox':if(!firefoxShim||!firefoxShim.shimPeerConnection||!options.shimFirefox){logging('Firefox shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming firefox.');adapter.browserShim=firefoxShim;firefoxShim.shimGetUserMedia(window);firefoxShim.shimPeerConnection(window);firefoxShim.shimOnTrack(window);firefoxShim.shimRemoveStream(window);firefoxShim.shimSenderGetStats(window);firefoxShim.shimReceiverGetStats(window);firefoxShim.shimRTCDataChannel(window);commonShim.shimRTCIceCandidate(window);commonShim.shimConnectionState(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);break;case'edge':if(!edgeShim||!edgeShim.shimPeerConnection||!options.shimEdge){logging('MS edge shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming edge.');adapter.browserShim=edgeShim;edgeShim.shimGetUserMedia(window);edgeShim.shimGetDisplayMedia(window);edgeShim.shimPeerConnection(window);edgeShim.shimReplaceTrack(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);break;case'safari':if(!safariShim||!options.shimSafari){logging('Safari shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming safari.');adapter.browserShim=safariShim;safariShim.shimRTCIceServerUrls(window);safariShim.shimCreateOfferLegacy(window);safariShim.shimCallbacksAPI(window);safariShim.shimLocalStreamsAPI(window);safariShim.shimRemoteStreamsAPI(window);safariShim.shimTrackEventTransceiver(window);safariShim.shimGetUserMedia(window);commonShim.shimRTCIceCandidate(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);commonShim.removeAllowExtmapMixed(window);break;default:logging('Unsupported browser!');break;}
+return adapter;}},{"./chrome/chrome_shim":3,"./common_shim":6,"./edge/edge_shim":7,"./firefox/firefox_shim":11,"./safari/safari_shim":14,"./utils":15}],3:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=exports.shimGetUserMedia=undefined;var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};var _getusermedia=require('./getusermedia');Object.defineProperty(exports,'shimGetUserMedia',{enumerable:true,get:function get(){return _getusermedia.shimGetUserMedia;}});var _getdisplaymedia=require('./getdisplaymedia');Object.defineProperty(exports,'shimGetDisplayMedia',{enumerable:true,get:function get(){return _getdisplaymedia.shimGetDisplayMedia;}});exports.shimMediaStream=shimMediaStream;exports.shimOnTrack=shimOnTrack;exports.shimGetSendersWithDtmf=shimGetSendersWithDtmf;exports.shimSenderReceiverGetStats=shimSenderReceiverGetStats;exports.shimAddTrackRemoveTrackWithNative=shimAddTrackRemoveTrackWithNative;exports.shimAddTrackRemoveTrack=shimAddTrackRemoveTrack;exports.shimPeerConnection=shimPeerConnection;exports.fixNegotiationNeeded=fixNegotiationNeeded;var _utils=require('../utils.js');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function walkStats(stats,base,resultSet){if(!base||resultSet.has(base.id)){return;}
+resultSet.set(base.id,base);Object.keys(base).forEach(function(name){if(name.endsWith('Id')){walkStats(stats,stats.get(base[name]),resultSet);}else if(name.endsWith('Ids')){base[name].forEach(function(id){walkStats(stats,stats.get(id),resultSet);});}});}
+function filterStats(result,track,outbound){var streamStatsType=outbound?'outbound-rtp':'inbound-rtp';var filteredResult=new Map();if(track===null){return filteredResult;}
+var trackStats=[];result.forEach(function(value){if(value.type==='track'&&value.trackIdentifier===track.id){trackStats.push(value);}});trackStats.forEach(function(trackStat){result.forEach(function(stats){if(stats.type===streamStatsType&&stats.trackId===trackStat.id){walkStats(result,stats,filteredResult);}});});return filteredResult;}
+function shimMediaStream(window){window.MediaStream=window.MediaStream||window.webkitMediaStream;}
+function shimOnTrack(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&!('ontrack'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function get(){return this._ontrack;},set:function set(f){if(this._ontrack){this.removeEventListener('track',this._ontrack);}
+this.addEventListener('track',this._ontrack=f);},enumerable:true,configurable:true});var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){var _this=this;if(!this._ontrackpoly){this._ontrackpoly=function(e){e.stream.addEventListener('addtrack',function(te){var receiver=void 0;if(window.RTCPeerConnection.prototype.getReceivers){receiver=_this.getReceivers().find(function(r){return r.track&&r.track.id===te.track.id;});}else{receiver={track:te.track};}
+var event=new Event('track');event.track=te.track;event.receiver=receiver;event.transceiver={receiver:receiver};event.streams=[e.stream];_this.dispatchEvent(event);});e.stream.getTracks().forEach(function(track){var receiver=void 0;if(window.RTCPeerConnection.prototype.getReceivers){receiver=_this.getReceivers().find(function(r){return r.track&&r.track.id===track.id;});}else{receiver={track:track};}
+var event=new Event('track');event.track=track;event.receiver=receiver;event.transceiver={receiver:receiver};event.streams=[e.stream];_this.dispatchEvent(event);});};this.addEventListener('addstream',this._ontrackpoly);}
+return origSetRemoteDescription.apply(this,arguments);};}else{utils.wrapPeerConnectionEvent(window,'track',function(e){if(!e.transceiver){Object.defineProperty(e,'transceiver',{value:{receiver:e.receiver}});}
+return e;});}}
+function shimGetSendersWithDtmf(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&!('getSenders'in window.RTCPeerConnection.prototype)&&'createDTMFSender'in window.RTCPeerConnection.prototype){var shimSenderWithDtmf=function shimSenderWithDtmf(pc,track){return{track:track,get dtmf(){if(this._dtmf===undefined){if(track.kind==='audio'){this._dtmf=pc.createDTMFSender(track);}else{this._dtmf=null;}}
+return this._dtmf;},_pc:pc};};if(!window.RTCPeerConnection.prototype.getSenders){window.RTCPeerConnection.prototype.getSenders=function(){this._senders=this._senders||[];return this._senders.slice();};var origAddTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addTrack=function(track,stream){var sender=origAddTrack.apply(this,arguments);if(!sender){sender=shimSenderWithDtmf(this,track);this._senders.push(sender);}
+return sender;};var origRemoveTrack=window.RTCPeerConnection.prototype.removeTrack;window.RTCPeerConnection.prototype.removeTrack=function(sender){origRemoveTrack.apply(this,arguments);var idx=this._senders.indexOf(sender);if(idx!==-1){this._senders.splice(idx,1);}};}
+var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var _this2=this;this._senders=this._senders||[];origAddStream.apply(this,[stream]);stream.getTracks().forEach(function(track){_this2._senders.push(shimSenderWithDtmf(_this2,track));});};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){var _this3=this;this._senders=this._senders||[];origRemoveStream.apply(this,[stream]);stream.getTracks().forEach(function(track){var sender=_this3._senders.find(function(s){return s.track===track;});if(sender){_this3._senders.splice(_this3._senders.indexOf(sender),1);}});};}else if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&'getSenders'in window.RTCPeerConnection.prototype&&'createDTMFSender'in window.RTCPeerConnection.prototype&&window.RTCRtpSender&&!('dtmf'in window.RTCRtpSender.prototype)){var origGetSenders=window.RTCPeerConnection.prototype.getSenders;window.RTCPeerConnection.prototype.getSenders=function(){var _this4=this;var senders=origGetSenders.apply(this,[]);senders.forEach(function(sender){return sender._pc=_this4;});return senders;};Object.defineProperty(window.RTCRtpSender.prototype,'dtmf',{get:function get(){if(this._dtmf===undefined){if(this.track.kind==='audio'){this._dtmf=this._pc.createDTMFSender(this.track);}else{this._dtmf=null;}}
+return this._dtmf;}});}}
+function shimSenderReceiverGetStats(window){if(!((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&window.RTCRtpSender&&window.RTCRtpReceiver)){return;}
+if(!('getStats'in window.RTCRtpSender.prototype)){var origGetSenders=window.RTCPeerConnection.prototype.getSenders;if(origGetSenders){window.RTCPeerConnection.prototype.getSenders=function(){var _this5=this;var senders=origGetSenders.apply(this,[]);senders.forEach(function(sender){return sender._pc=_this5;});return senders;};}
+var origAddTrack=window.RTCPeerConnection.prototype.addTrack;if(origAddTrack){window.RTCPeerConnection.prototype.addTrack=function(){var sender=origAddTrack.apply(this,arguments);sender._pc=this;return sender;};}
+window.RTCRtpSender.prototype.getStats=function(){var sender=this;return this._pc.getStats().then(function(result){return(filterStats(result,sender.track,true));});};}
+if(!('getStats'in window.RTCRtpReceiver.prototype)){var origGetReceivers=window.RTCPeerConnection.prototype.getReceivers;if(origGetReceivers){window.RTCPeerConnection.prototype.getReceivers=function(){var _this6=this;var receivers=origGetReceivers.apply(this,[]);receivers.forEach(function(receiver){return receiver._pc=_this6;});return receivers;};}
+utils.wrapPeerConnectionEvent(window,'track',function(e){e.receiver._pc=e.srcElement;return e;});window.RTCRtpReceiver.prototype.getStats=function(){var receiver=this;return this._pc.getStats().then(function(result){return filterStats(result,receiver.track,false);});};}
+if(!('getStats'in window.RTCRtpSender.prototype&&'getStats'in window.RTCRtpReceiver.prototype)){return;}
+var origGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(){if(arguments.length>0&&arguments[0]instanceof window.MediaStreamTrack){var track=arguments[0];var sender=void 0;var receiver=void 0;var err=void 0;this.getSenders().forEach(function(s){if(s.track===track){if(sender){err=true;}else{sender=s;}}});this.getReceivers().forEach(function(r){if(r.track===track){if(receiver){err=true;}else{receiver=r;}}
+return r.track===track;});if(err||sender&&receiver){return Promise.reject(new DOMException('There are more than one sender or receiver for the track.','InvalidAccessError'));}else if(sender){return sender.getStats();}else if(receiver){return receiver.getStats();}
+return Promise.reject(new DOMException('There is no sender or receiver for the track.','InvalidAccessError'));}
+return origGetStats.apply(this,arguments);};}
+function shimAddTrackRemoveTrackWithNative(window){window.RTCPeerConnection.prototype.getLocalStreams=function(){var _this7=this;this._shimmedLocalStreams=this._shimmedLocalStreams||{};return Object.keys(this._shimmedLocalStreams).map(function(streamId){return _this7._shimmedLocalStreams[streamId][0];});};var origAddTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(!stream){return origAddTrack.apply(this,arguments);}
+this._shimmedLocalStreams=this._shimmedLocalStreams||{};var sender=origAddTrack.apply(this,arguments);if(!this._shimmedLocalStreams[stream.id]){this._shimmedLocalStreams[stream.id]=[stream,sender];}else if(this._shimmedLocalStreams[stream.id].indexOf(sender)===-1){this._shimmedLocalStreams[stream.id].push(sender);}
+return sender;};var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var _this8=this;this._shimmedLocalStreams=this._shimmedLocalStreams||{};stream.getTracks().forEach(function(track){var alreadyExists=_this8.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}});var existingSenders=this.getSenders();origAddStream.apply(this,arguments);var newSenders=this.getSenders().filter(function(newSender){return existingSenders.indexOf(newSender)===-1;});this._shimmedLocalStreams[stream.id]=[stream].concat(newSenders);};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){this._shimmedLocalStreams=this._shimmedLocalStreams||{};delete this._shimmedLocalStreams[stream.id];return origRemoveStream.apply(this,arguments);};var origRemoveTrack=window.RTCPeerConnection.prototype.removeTrack;window.RTCPeerConnection.prototype.removeTrack=function(sender){var _this9=this;this._shimmedLocalStreams=this._shimmedLocalStreams||{};if(sender){Object.keys(this._shimmedLocalStreams).forEach(function(streamId){var idx=_this9._shimmedLocalStreams[streamId].indexOf(sender);if(idx!==-1){_this9._shimmedLocalStreams[streamId].splice(idx,1);}
+if(_this9._shimmedLocalStreams[streamId].length===1){delete _this9._shimmedLocalStreams[streamId];}});}
+return origRemoveTrack.apply(this,arguments);};}
+function shimAddTrackRemoveTrack(window){if(!window.RTCPeerConnection){return;}
+var browserDetails=utils.detectBrowser(window);if(window.RTCPeerConnection.prototype.addTrack&&browserDetails.version>=65){return shimAddTrackRemoveTrackWithNative(window);}
+var origGetLocalStreams=window.RTCPeerConnection.prototype.getLocalStreams;window.RTCPeerConnection.prototype.getLocalStreams=function(){var _this10=this;var nativeStreams=origGetLocalStreams.apply(this);this._reverseStreams=this._reverseStreams||{};return nativeStreams.map(function(stream){return _this10._reverseStreams[stream.id];});};var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var _this11=this;this._streams=this._streams||{};this._reverseStreams=this._reverseStreams||{};stream.getTracks().forEach(function(track){var alreadyExists=_this11.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}});if(!this._reverseStreams[stream.id]){var newStream=new window.MediaStream(stream.getTracks());this._streams[stream.id]=newStream;this._reverseStreams[newStream.id]=stream;stream=newStream;}
+origAddStream.apply(this,[stream]);};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){this._streams=this._streams||{};this._reverseStreams=this._reverseStreams||{};origRemoveStream.apply(this,[this._streams[stream.id]||stream]);delete this._reverseStreams[this._streams[stream.id]?this._streams[stream.id].id:stream.id];delete this._streams[stream.id];};window.RTCPeerConnection.prototype.addTrack=function(track,stream){var _this12=this;if(this.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');}
+var streams=[].slice.call(arguments,1);if(streams.length!==1||!streams[0].getTracks().find(function(t){return t===track;})){throw new DOMException('The adapter.js addTrack polyfill only supports a single '+' stream which is associated with the specified track.','NotSupportedError');}
+var alreadyExists=this.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}
+this._streams=this._streams||{};this._reverseStreams=this._reverseStreams||{};var oldStream=this._streams[stream.id];if(oldStream){oldStream.addTrack(track);Promise.resolve().then(function(){_this12.dispatchEvent(new Event('negotiationneeded'));});}else{var newStream=new window.MediaStream([track]);this._streams[stream.id]=newStream;this._reverseStreams[newStream.id]=stream;this.addStream(newStream);}
+return this.getSenders().find(function(s){return s.track===track;});};function replaceInternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(internalStream.id,'g'),externalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});}
+function replaceExternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(externalStream.id,'g'),internalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});}
+['createOffer','createAnswer'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var _this13=this;var args=arguments;var isLegacyCall=arguments.length&&typeof arguments[0]==='function';if(isLegacyCall){return nativeMethod.apply(this,[function(description){var desc=replaceInternalStreamId(_this13,description);args[0].apply(null,[desc]);},function(err){if(args[1]){args[1].apply(null,err);}},arguments[2]]);}
+return nativeMethod.apply(this,arguments).then(function(description){return replaceInternalStreamId(_this13,description);});};});var origSetLocalDescription=window.RTCPeerConnection.prototype.setLocalDescription;window.RTCPeerConnection.prototype.setLocalDescription=function(){if(!arguments.length||!arguments[0].type){return origSetLocalDescription.apply(this,arguments);}
+arguments[0]=replaceExternalStreamId(this,arguments[0]);return origSetLocalDescription.apply(this,arguments);};var origLocalDescription=Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype,'localDescription');Object.defineProperty(window.RTCPeerConnection.prototype,'localDescription',{get:function get(){var description=origLocalDescription.get.apply(this);if(description.type===''){return description;}
+return replaceInternalStreamId(this,description);}});window.RTCPeerConnection.prototype.removeTrack=function(sender){var _this14=this;if(this.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');}
+if(!sender._pc){throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.','TypeError');}
+var isLocal=sender._pc===this;if(!isLocal){throw new DOMException('Sender was not created by this connection.','InvalidAccessError');}
+this._streams=this._streams||{};var stream=void 0;Object.keys(this._streams).forEach(function(streamid){var hasTrack=_this14._streams[streamid].getTracks().find(function(track){return sender.track===track;});if(hasTrack){stream=_this14._streams[streamid];}});if(stream){if(stream.getTracks().length===1){this.removeStream(this._reverseStreams[stream.id]);}else{stream.removeTrack(sender.track);}
+this.dispatchEvent(new Event('negotiationneeded'));}};}
+function shimPeerConnection(window){if(!window.RTCPeerConnection&&window.webkitRTCPeerConnection){window.RTCPeerConnection=window.webkitRTCPeerConnection;}
+if(!window.RTCPeerConnection){return;}
+var origGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(selector,successCallback,errorCallback){var _this15=this;var args=arguments;if(arguments.length>0&&typeof selector==='function'){return origGetStats.apply(this,arguments);}
+if(origGetStats.length===0&&(arguments.length===0||typeof arguments[0]!=='function')){return origGetStats.apply(this,[]);}
+var fixChromeStats_=function fixChromeStats_(response){var standardReport={};var reports=response.result();reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:{localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[report.type]||report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name);});standardReport[standardStats.id]=standardStats;});return standardReport;};var makeMapStats=function makeMapStats(stats){return new Map(Object.keys(stats).map(function(key){return[key,stats[key]];}));};if(arguments.length>=2){var successCallbackWrapper_=function successCallbackWrapper_(response){args[1](makeMapStats(fixChromeStats_(response)));};return origGetStats.apply(this,[successCallbackWrapper_,arguments[0]]);}
+return new Promise(function(resolve,reject){origGetStats.apply(_this15,[function(response){resolve(makeMapStats(fixChromeStats_(response)));},reject]);}).then(successCallback,errorCallback);};['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new(method==='addIceCandidate'?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);}
+return Promise.resolve();}
+return nativeAddIceCandidate.apply(this,arguments);};}
+function fixNegotiationNeeded(window){utils.wrapPeerConnectionEvent(window,'negotiationneeded',function(e){var pc=e.target;if(pc.signalingState!=='stable'){return;}
+return e;});}},{"../utils.js":15,"./getdisplaymedia":4,"./getusermedia":5}],4:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=shimGetDisplayMedia;function shimGetDisplayMedia(window,getSourceId){if(window.navigator.mediaDevices&&'getDisplayMedia'in window.navigator.mediaDevices){return;}
+if(!window.navigator.mediaDevices){return;}
+if(typeof getSourceId!=='function'){console.error('shimGetDisplayMedia: getSourceId argument is not '+'a function');return;}
+window.navigator.mediaDevices.getDisplayMedia=function(constraints){return getSourceId(constraints).then(function(sourceId){var widthSpecified=constraints.video&&constraints.video.width;var heightSpecified=constraints.video&&constraints.video.height;var frameRateSpecified=constraints.video&&constraints.video.frameRate;constraints.video={mandatory:{chromeMediaSource:'desktop',chromeMediaSourceId:sourceId,maxFrameRate:frameRateSpecified||3}};if(widthSpecified){constraints.video.mandatory.maxWidth=widthSpecified;}
+if(heightSpecified){constraints.video.mandatory.maxHeight=heightSpecified;}
+return window.navigator.mediaDevices.getUserMedia(constraints);});};}},{}],5:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimGetUserMedia=shimGetUserMedia;var _utils=require('../utils.js');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+var logging=utils.log;function shimGetUserMedia(window){var navigator=window&&window.navigator;if(!navigator.mediaDevices){return;}
+var browserDetails=utils.detectBrowser(window);var constraintsToChrome_=function constraintsToChrome_(c){if((typeof c==='undefined'?'undefined':_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 oldname_(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 shimConstraints_(constraints,func){if(browserDetails.version>=61){return func(constraints);}
+constraints=JSON.parse(JSON.stringify(constraints));if(constraints&&_typeof(constraints.audio)==='object'){var remap=function remap(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};constraints=JSON.parse(JSON.stringify(constraints));remap(constraints.audio,'autoGainControl','googAutoGainControl');remap(constraints.audio,'noiseSuppression','googNoiseSuppression');constraints.audio=constraintsToChrome_(constraints.audio);}
+if(constraints&&_typeof(constraints.video)==='object'){var face=constraints.video.facingMode;face=face&&((typeof face==='undefined'?'undefined':_typeof(face))==='object'?face:{ideal:face});var getSupportedFacingModeLies=browserDetails.version<66;if(face&&(face.exact==='user'||face.exact==='environment'||face.ideal==='user'||face.ideal==='environment')&&!(navigator.mediaDevices.getSupportedConstraints&&navigator.mediaDevices.getSupportedConstraints().facingMode&&!getSupportedFacingModeLies)){delete constraints.video.facingMode;var matches=void 0;if(face.exact==='environment'||face.ideal==='environment'){matches=['back','rear'];}else if(face.exact==='user'||face.ideal==='user'){matches=['front'];}
+if(matches){return navigator.mediaDevices.enumerateDevices().then(function(devices){devices=devices.filter(function(d){return d.kind==='videoinput';});var dev=devices.find(function(d){return matches.some(function(match){return d.label.toLowerCase().includes(match);});});if(!dev&&devices.length&&matches.includes('back')){dev=devices[devices.length-1];}
+if(dev){constraints.video.deviceId=face.exact?{exact:dev.deviceId}:{ideal:dev.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 shimError_(e){if(browserDetails.version>=64){return e;}
+return{name:{PermissionDeniedError:'NotAllowedError',PermissionDismissedError:'NotAllowedError',InvalidStateError:'NotAllowedError',DevicesNotFoundError:'NotFoundError',ConstraintNotSatisfiedError:'OverconstrainedError',TrackStartError:'NotReadableError',MediaDeviceFailedDueToShutdown:'NotAllowedError',MediaDeviceKillSwitchOn:'NotAllowedError',TabCaptureError:'AbortError',ScreenCaptureError:'AbortError',DeviceCaptureError:'AbortError'}[e.name]||e.name,message:e.message,constraint:e.constraint||e.constraintName,toString:function toString(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function getUserMedia_(constraints,onSuccess,onError){shimConstraints_(constraints,function(c){navigator.webkitGetUserMedia(c,onSuccess,function(e){if(onError){onError(shimError_(e));}});});};navigator.getUserMedia=getUserMedia_.bind(navigator);var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(cs){return shimConstraints_(cs,function(c){return origGetUserMedia(c).then(function(stream){if(c.audio&&!stream.getAudioTracks().length||c.video&&!stream.getVideoTracks().length){stream.getTracks().forEach(function(track){track.stop();});throw new DOMException('','NotFoundError');}
+return stream;},function(e){return Promise.reject(shimError_(e));});});};}},{"../utils.js":15}],6:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimRTCIceCandidate=shimRTCIceCandidate;exports.shimMaxMessageSize=shimMaxMessageSize;exports.shimSendThrowTypeError=shimSendThrowTypeError;exports.shimConnectionState=shimConnectionState;exports.removeAllowExtmapMixed=removeAllowExtmapMixed;var _sdp=require('sdp');var _sdp2=_interopRequireDefault(_sdp);var _utils=require('./utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}
+function shimRTCIceCandidate(window){if(!window.RTCIceCandidate||window.RTCIceCandidate&&'foundation'in window.RTCIceCandidate.prototype){return;}
+var NativeRTCIceCandidate=window.RTCIceCandidate;window.RTCIceCandidate=function(args){if((typeof args==='undefined'?'undefined':_typeof(args))==='object'&&args.candidate&&args.candidate.indexOf('a=')===0){args=JSON.parse(JSON.stringify(args));args.candidate=args.candidate.substr(2);}
+if(args.candidate&&args.candidate.length){var nativeCandidate=new NativeRTCIceCandidate(args);var parsedCandidate=_sdp2.default.parseCandidate(args.candidate);var augmentedCandidate=Object.assign(nativeCandidate,parsedCandidate);augmentedCandidate.toJSON=function(){return{candidate:augmentedCandidate.candidate,sdpMid:augmentedCandidate.sdpMid,sdpMLineIndex:augmentedCandidate.sdpMLineIndex,usernameFragment:augmentedCandidate.usernameFragment};};return augmentedCandidate;}
+return new NativeRTCIceCandidate(args);};window.RTCIceCandidate.prototype=NativeRTCIceCandidate.prototype;utils.wrapPeerConnectionEvent(window,'icecandidate',function(e){if(e.candidate){Object.defineProperty(e,'candidate',{value:new window.RTCIceCandidate(e.candidate),writable:'false'});}
+return e;});}
+function shimMaxMessageSize(window){if(window.RTCSctpTransport||!window.RTCPeerConnection){return;}
+var browserDetails=utils.detectBrowser(window);if(!('sctp'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'sctp',{get:function get(){return typeof this._sctp==='undefined'?null:this._sctp;}});}
+var sctpInDescription=function sctpInDescription(description){var sections=_sdp2.default.splitSections(description.sdp);sections.shift();return sections.some(function(mediaSection){var mLine=_sdp2.default.parseMLine(mediaSection);return mLine&&mLine.kind==='application'&&mLine.protocol.indexOf('SCTP')!==-1;});};var getRemoteFirefoxVersion=function getRemoteFirefoxVersion(description){var match=description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);if(match===null||match.length<2){return-1;}
+var version=parseInt(match[1],10);return version!==version?-1:version;};var getCanSendMaxMessageSize=function getCanSendMaxMessageSize(remoteIsFirefox){var canSendMaxMessageSize=65536;if(browserDetails.browser==='firefox'){if(browserDetails.version<57){if(remoteIsFirefox===-1){canSendMaxMessageSize=16384;}else{canSendMaxMessageSize=2147483637;}}else if(browserDetails.version<60){canSendMaxMessageSize=browserDetails.version===57?65535:65536;}else{canSendMaxMessageSize=2147483637;}}
+return canSendMaxMessageSize;};var getMaxMessageSize=function getMaxMessageSize(description,remoteIsFirefox){var maxMessageSize=65536;if(browserDetails.browser==='firefox'&&browserDetails.version===57){maxMessageSize=65535;}
+var match=_sdp2.default.matchPrefix(description.sdp,'a=max-message-size:');if(match.length>0){maxMessageSize=parseInt(match[0].substr(19),10);}else if(browserDetails.browser==='firefox'&&remoteIsFirefox!==-1){maxMessageSize=2147483637;}
+return maxMessageSize;};var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){this._sctp=null;if(sctpInDescription(arguments[0])){var isFirefox=getRemoteFirefoxVersion(arguments[0]);var canSendMMS=getCanSendMaxMessageSize(isFirefox);var remoteMMS=getMaxMessageSize(arguments[0],isFirefox);var maxMessageSize=void 0;if(canSendMMS===0&&remoteMMS===0){maxMessageSize=Number.POSITIVE_INFINITY;}else if(canSendMMS===0||remoteMMS===0){maxMessageSize=Math.max(canSendMMS,remoteMMS);}else{maxMessageSize=Math.min(canSendMMS,remoteMMS);}
+var sctp={};Object.defineProperty(sctp,'maxMessageSize',{get:function get(){return maxMessageSize;}});this._sctp=sctp;}
+return origSetRemoteDescription.apply(this,arguments);};}
+function shimSendThrowTypeError(window){if(!(window.RTCPeerConnection&&'createDataChannel'in window.RTCPeerConnection.prototype)){return;}
+function wrapDcSend(dc,pc){var origDataChannelSend=dc.send;dc.send=function(){var data=arguments[0];var length=data.length||data.size||data.byteLength;if(dc.readyState==='open'&&pc.sctp&&length>pc.sctp.maxMessageSize){throw new TypeError('Message too large (can send a maximum of '+pc.sctp.maxMessageSize+' bytes)');}
+return origDataChannelSend.apply(dc,arguments);};}
+var origCreateDataChannel=window.RTCPeerConnection.prototype.createDataChannel;window.RTCPeerConnection.prototype.createDataChannel=function(){var dataChannel=origCreateDataChannel.apply(this,arguments);wrapDcSend(dataChannel,this);return dataChannel;};utils.wrapPeerConnectionEvent(window,'datachannel',function(e){wrapDcSend(e.channel,e.target);return e;});}
+function shimConnectionState(window){if(!window.RTCPeerConnection||'connectionState'in window.RTCPeerConnection.prototype){return;}
+var proto=window.RTCPeerConnection.prototype;Object.defineProperty(proto,'connectionState',{get:function get(){return{completed:'connected',checking:'connecting'}[this.iceConnectionState]||this.iceConnectionState;},enumerable:true,configurable:true});Object.defineProperty(proto,'onconnectionstatechange',{get:function get(){return this._onconnectionstatechange||null;},set:function set(cb){if(this._onconnectionstatechange){this.removeEventListener('connectionstatechange',this._onconnectionstatechange);delete this._onconnectionstatechange;}
+if(cb){this.addEventListener('connectionstatechange',this._onconnectionstatechange=cb);}},enumerable:true,configurable:true});['setLocalDescription','setRemoteDescription'].forEach(function(method){var origMethod=proto[method];proto[method]=function(){if(!this._connectionstatechangepoly){this._connectionstatechangepoly=function(e){var pc=e.target;if(pc._lastConnectionState!==pc.connectionState){pc._lastConnectionState=pc.connectionState;var newEvent=new Event('connectionstatechange',e);pc.dispatchEvent(newEvent);}
+return e;};this.addEventListener('iceconnectionstatechange',this._connectionstatechangepoly);}
+return origMethod.apply(this,arguments);};});}
+function removeAllowExtmapMixed(window){if(!window.RTCPeerConnection){return;}
+var browserDetails=utils.detectBrowser(window);if(browserDetails.browser==='chrome'&&browserDetails.version>=71){return;}
+var nativeSRD=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(desc){if(desc&&desc.sdp&&desc.sdp.indexOf('\na=extmap-allow-mixed')!==-1){desc.sdp=desc.sdp.split('\n').filter(function(line){return line.trim()!=='a=extmap-allow-mixed';}).join('\n');}
+return nativeSRD.apply(this,arguments);};}},{"./utils":15,"sdp":17}],7:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=exports.shimGetUserMedia=undefined;var _getusermedia=require('./getusermedia');Object.defineProperty(exports,'shimGetUserMedia',{enumerable:true,get:function get(){return _getusermedia.shimGetUserMedia;}});var _getdisplaymedia=require('./getdisplaymedia');Object.defineProperty(exports,'shimGetDisplayMedia',{enumerable:true,get:function get(){return _getdisplaymedia.shimGetDisplayMedia;}});exports.shimPeerConnection=shimPeerConnection;exports.shimReplaceTrack=shimReplaceTrack;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);var _filtericeservers=require('./filtericeservers');var _rtcpeerconnectionShim=require('rtcpeerconnection-shim');var _rtcpeerconnectionShim2=_interopRequireDefault(_rtcpeerconnectionShim);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}
+function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimPeerConnection(window){var browserDetails=utils.detectBrowser(window);if(window.RTCIceGatherer){if(!window.RTCIceCandidate){window.RTCIceCandidate=function(args){return args;};}
+if(!window.RTCSessionDescription){window.RTCSessionDescription=function(args){return args;};}
+if(browserDetails.version<15025){var origMSTEnabled=Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype,'enabled');Object.defineProperty(window.MediaStreamTrack.prototype,'enabled',{set:function set(value){origMSTEnabled.set.call(this,value);var ev=new Event('enabled');ev.enabled=value;this.dispatchEvent(ev);}});}}
+if(window.RTCRtpSender&&!('dtmf'in window.RTCRtpSender.prototype)){Object.defineProperty(window.RTCRtpSender.prototype,'dtmf',{get:function get(){if(this._dtmf===undefined){if(this.track.kind==='audio'){this._dtmf=new window.RTCDtmfSender(this);}else if(this.track.kind==='video'){this._dtmf=null;}}
+return this._dtmf;}});}
+if(window.RTCDtmfSender&&!window.RTCDTMFSender){window.RTCDTMFSender=window.RTCDtmfSender;}
+var RTCPeerConnectionShim=(0,_rtcpeerconnectionShim2.default)(window,browserDetails.version);window.RTCPeerConnection=function(config){if(config&&config.iceServers){config.iceServers=(0,_filtericeservers.filterIceServers)(config.iceServers,browserDetails.version);utils.log('ICE servers after filtering:',config.iceServers);}
+return new RTCPeerConnectionShim(config);};window.RTCPeerConnection.prototype=RTCPeerConnectionShim.prototype;}
+function shimReplaceTrack(window){if(window.RTCRtpSender&&!('replaceTrack'in window.RTCRtpSender.prototype)){window.RTCRtpSender.prototype.replaceTrack=window.RTCRtpSender.prototype.setTrack;}}},{"../utils":15,"./filtericeservers":8,"./getdisplaymedia":9,"./getusermedia":10,"rtcpeerconnection-shim":16}],8:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.filterIceServers=filterIceServers;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function filterIceServers(iceServers,edgeVersion){var hasTurn=false;iceServers=JSON.parse(JSON.stringify(iceServers));return iceServers.filter(function(server){if(server&&(server.urls||server.url)){var urls=server.urls||server.url;if(server.url&&!server.urls){utils.deprecated('RTCIceServer.url','RTCIceServer.urls');}
+var isString=typeof urls==='string';if(isString){urls=[urls];}
+urls=urls.filter(function(url){if(url.indexOf('stun:')===0){return false;}
+var validTurn=url.startsWith('turn')&&!url.startsWith('turn:[')&&url.includes('transport=udp');if(validTurn&&!hasTurn){hasTurn=true;return true;}
+return validTurn&&!hasTurn;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;}});}},{"../utils":15}],9:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=shimGetDisplayMedia;function shimGetDisplayMedia(window){if(!('getDisplayMedia'in window.navigator)){return;}
+if(!window.navigator.mediaDevices){return;}
+if(window.navigator.mediaDevices&&'getDisplayMedia'in window.navigator.mediaDevices){return;}
+window.navigator.mediaDevices.getDisplayMedia=window.navigator.getDisplayMedia.bind(window.navigator.mediaDevices);}},{}],10:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetUserMedia=shimGetUserMedia;function shimGetUserMedia(window){var navigator=window&&window.navigator;var shimError_=function shimError_(e){return{name:{PermissionDeniedError:'NotAllowedError'}[e.name]||e.name,message:e.message,constraint:e.constraint,toString:function toString(){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));});};}},{}],11:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=exports.shimGetUserMedia=undefined;var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};var _getusermedia=require('./getusermedia');Object.defineProperty(exports,'shimGetUserMedia',{enumerable:true,get:function get(){return _getusermedia.shimGetUserMedia;}});var _getdisplaymedia=require('./getdisplaymedia');Object.defineProperty(exports,'shimGetDisplayMedia',{enumerable:true,get:function get(){return _getdisplaymedia.shimGetDisplayMedia;}});exports.shimOnTrack=shimOnTrack;exports.shimPeerConnection=shimPeerConnection;exports.shimSenderGetStats=shimSenderGetStats;exports.shimReceiverGetStats=shimReceiverGetStats;exports.shimRemoveStream=shimRemoveStream;exports.shimRTCDataChannel=shimRTCDataChannel;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimOnTrack(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCTrackEvent&&'receiver'in window.RTCTrackEvent.prototype&&!('transceiver'in window.RTCTrackEvent.prototype)){Object.defineProperty(window.RTCTrackEvent.prototype,'transceiver',{get:function get(){return{receiver:this.receiver};}});}}
+function shimPeerConnection(window){var browserDetails=utils.detectBrowser(window);if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!(window.RTCPeerConnection||window.mozRTCPeerConnection)){return;}
+if(!window.RTCPeerConnection&&window.mozRTCPeerConnection){window.RTCPeerConnection=window.mozRTCPeerConnection;}
+['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new(method==='addIceCandidate'?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);}
+return Promise.resolve();}
+return nativeAddIceCandidate.apply(this,arguments);};var modernStatsTypes={inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'};var nativeGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(selector,onSucc,onErr){return nativeGetStats.apply(this,[selector||null]).then(function(stats){if(browserDetails.version<53&&!onSucc){try{stats.forEach(function(stat){stat.type=modernStatsTypes[stat.type]||stat.type;});}catch(e){if(e.name!=='TypeError'){throw e;}
+stats.forEach(function(stat,i){stats.set(i,Object.assign({},stat,{type:modernStatsTypes[stat.type]||stat.type}));});}}
+return stats;}).then(onSucc,onErr);};}
+function shimSenderGetStats(window){if(!((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&window.RTCRtpSender)){return;}
+if(window.RTCRtpSender&&'getStats'in window.RTCRtpSender.prototype){return;}
+var origGetSenders=window.RTCPeerConnection.prototype.getSenders;if(origGetSenders){window.RTCPeerConnection.prototype.getSenders=function(){var _this=this;var senders=origGetSenders.apply(this,[]);senders.forEach(function(sender){return sender._pc=_this;});return senders;};}
+var origAddTrack=window.RTCPeerConnection.prototype.addTrack;if(origAddTrack){window.RTCPeerConnection.prototype.addTrack=function(){var sender=origAddTrack.apply(this,arguments);sender._pc=this;return sender;};}
+window.RTCRtpSender.prototype.getStats=function(){return this.track?this._pc.getStats(this.track):Promise.resolve(new Map());};}
+function shimReceiverGetStats(window){if(!((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&window.RTCRtpSender)){return;}
+if(window.RTCRtpSender&&'getStats'in window.RTCRtpReceiver.prototype){return;}
+var origGetReceivers=window.RTCPeerConnection.prototype.getReceivers;if(origGetReceivers){window.RTCPeerConnection.prototype.getReceivers=function(){var _this2=this;var receivers=origGetReceivers.apply(this,[]);receivers.forEach(function(receiver){return receiver._pc=_this2;});return receivers;};}
+utils.wrapPeerConnectionEvent(window,'track',function(e){e.receiver._pc=e.srcElement;return e;});window.RTCRtpReceiver.prototype.getStats=function(){return this._pc.getStats(this.track);};}
+function shimRemoveStream(window){if(!window.RTCPeerConnection||'removeStream'in window.RTCPeerConnection.prototype){return;}
+window.RTCPeerConnection.prototype.removeStream=function(stream){var _this3=this;utils.deprecated('removeStream','removeTrack');this.getSenders().forEach(function(sender){if(sender.track&&stream.getTracks().includes(sender.track)){_this3.removeTrack(sender);}});};}
+function shimRTCDataChannel(window){if(window.DataChannel&&!window.RTCDataChannel){window.RTCDataChannel=window.DataChannel;}}},{"../utils":15,"./getdisplaymedia":12,"./getusermedia":13}],12:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=shimGetDisplayMedia;function shimGetDisplayMedia(window,preferredMediaSource){if(window.navigator.mediaDevices&&'getDisplayMedia'in window.navigator.mediaDevices){return;}
+if(!window.navigator.mediaDevices){return;}
+window.navigator.mediaDevices.getDisplayMedia=function(constraints){if(!(constraints&&constraints.video)){var err=new DOMException('getDisplayMedia without video '+'constraints is undefined');err.name='NotFoundError';err.code=8;return Promise.reject(err);}
+if(constraints.video===true){constraints.video={mediaSource:preferredMediaSource};}else{constraints.video.mediaSource=preferredMediaSource;}
+return window.navigator.mediaDevices.getUserMedia(constraints);};}},{}],13:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimGetUserMedia=shimGetUserMedia;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimGetUserMedia(window){var browserDetails=utils.detectBrowser(window);var navigator=window&&window.navigator;var MediaStreamTrack=window&&window.MediaStreamTrack;navigator.getUserMedia=function(constraints,onSuccess,onError){utils.deprecated('navigator.getUserMedia','navigator.mediaDevices.getUserMedia');navigator.mediaDevices.getUserMedia(constraints).then(onSuccess,onError);};if(!(browserDetails.version>55&&'autoGainControl'in navigator.mediaDevices.getSupportedConstraints())){var remap=function remap(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};var nativeGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){if((typeof c==='undefined'?'undefined':_typeof(c))==='object'&&_typeof(c.audio)==='object'){c=JSON.parse(JSON.stringify(c));remap(c.audio,'autoGainControl','mozAutoGainControl');remap(c.audio,'noiseSuppression','mozNoiseSuppression');}
+return nativeGetUserMedia(c);};if(MediaStreamTrack&&MediaStreamTrack.prototype.getSettings){var nativeGetSettings=MediaStreamTrack.prototype.getSettings;MediaStreamTrack.prototype.getSettings=function(){var obj=nativeGetSettings.apply(this,arguments);remap(obj,'mozAutoGainControl','autoGainControl');remap(obj,'mozNoiseSuppression','noiseSuppression');return obj;};}
+if(MediaStreamTrack&&MediaStreamTrack.prototype.applyConstraints){var nativeApplyConstraints=MediaStreamTrack.prototype.applyConstraints;MediaStreamTrack.prototype.applyConstraints=function(c){if(this.kind==='audio'&&(typeof c==='undefined'?'undefined':_typeof(c))==='object'){c=JSON.parse(JSON.stringify(c));remap(c,'autoGainControl','mozAutoGainControl');remap(c,'noiseSuppression','mozNoiseSuppression');}
+return nativeApplyConstraints.apply(this,[c]);};}}}},{"../utils":15}],14:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimLocalStreamsAPI=shimLocalStreamsAPI;exports.shimRemoteStreamsAPI=shimRemoteStreamsAPI;exports.shimCallbacksAPI=shimCallbacksAPI;exports.shimGetUserMedia=shimGetUserMedia;exports.shimConstraints=shimConstraints;exports.shimRTCIceServerUrls=shimRTCIceServerUrls;exports.shimTrackEventTransceiver=shimTrackEventTransceiver;exports.shimCreateOfferLegacy=shimCreateOfferLegacy;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimLocalStreamsAPI(window){if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!window.RTCPeerConnection){return;}
+if(!('getLocalStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getLocalStreams=function(){if(!this._localStreams){this._localStreams=[];}
+return this._localStreams;};}
+if(!('addStream'in window.RTCPeerConnection.prototype)){var _addTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addStream=function(stream){var _this=this;if(!this._localStreams){this._localStreams=[];}
+if(!this._localStreams.includes(stream)){this._localStreams.push(stream);}
+stream.getTracks().forEach(function(track){return _addTrack.call(_this,track,stream);});};window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(stream){if(!this._localStreams){this._localStreams=[stream];}else if(!this._localStreams.includes(stream)){this._localStreams.push(stream);}}
+return _addTrack.call(this,track,stream);};}
+if(!('removeStream'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.removeStream=function(stream){var _this2=this;if(!this._localStreams){this._localStreams=[];}
+var index=this._localStreams.indexOf(stream);if(index===-1){return;}
+this._localStreams.splice(index,1);var tracks=stream.getTracks();this.getSenders().forEach(function(sender){if(tracks.includes(sender.track)){_this2.removeTrack(sender);}});};}}
+function shimRemoteStreamsAPI(window){if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!window.RTCPeerConnection){return;}
+if(!('getRemoteStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[];};}
+if(!('onaddstream'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'onaddstream',{get:function get(){return this._onaddstream;},set:function set(f){var _this3=this;if(this._onaddstream){this.removeEventListener('addstream',this._onaddstream);this.removeEventListener('track',this._onaddstreampoly);}
+this.addEventListener('addstream',this._onaddstream=f);this.addEventListener('track',this._onaddstreampoly=function(e){e.streams.forEach(function(stream){if(!_this3._remoteStreams){_this3._remoteStreams=[];}
+if(_this3._remoteStreams.includes(stream)){return;}
+_this3._remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;_this3.dispatchEvent(event);});});}});var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){var pc=this;if(!this._onaddstreampoly){this.addEventListener('track',this._onaddstreampoly=function(e){e.streams.forEach(function(stream){if(!pc._remoteStreams){pc._remoteStreams=[];}
+if(pc._remoteStreams.indexOf(stream)>=0){return;}
+pc._remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;pc.dispatchEvent(event);});});}
+return origSetRemoteDescription.apply(pc,arguments);};}}
+function shimCallbacksAPI(window){if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!window.RTCPeerConnection){return;}
+var prototype=window.RTCPeerConnection.prototype;var createOffer=prototype.createOffer;var createAnswer=prototype.createAnswer;var setLocalDescription=prototype.setLocalDescription;var setRemoteDescription=prototype.setRemoteDescription;var addIceCandidate=prototype.addIceCandidate;prototype.createOffer=function(successCallback,failureCallback){var options=arguments.length>=2?arguments[2]:arguments[0];var promise=createOffer.apply(this,[options]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.createAnswer=function(successCallback,failureCallback){var options=arguments.length>=2?arguments[2]:arguments[0];var promise=createAnswer.apply(this,[options]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};var withCallback=function withCallback(description,successCallback,failureCallback){var promise=setLocalDescription.apply(this,[description]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setLocalDescription=withCallback;withCallback=function withCallback(description,successCallback,failureCallback){var promise=setRemoteDescription.apply(this,[description]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setRemoteDescription=withCallback;withCallback=function withCallback(candidate,successCallback,failureCallback){var promise=addIceCandidate.apply(this,[candidate]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.addIceCandidate=withCallback;}
+function shimGetUserMedia(window){var navigator=window&&window.navigator;if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){var mediaDevices=navigator.mediaDevices;var _getUserMedia=mediaDevices.getUserMedia.bind(mediaDevices);navigator.mediaDevices.getUserMedia=function(constraints){return _getUserMedia(shimConstraints(constraints));};}
+if(!navigator.getUserMedia&&navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){navigator.getUserMedia=function(constraints,cb,errcb){navigator.mediaDevices.getUserMedia(constraints).then(cb,errcb);}.bind(navigator);}}
+function shimConstraints(constraints){if(constraints&&constraints.video!==undefined){return Object.assign({},constraints,{video:utils.compactObject(constraints.video)});}
+return constraints;}
+function shimRTCIceServerUrls(window){var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i<pcConfig.iceServers.length;i++){var server=pcConfig.iceServers[i];if(!server.hasOwnProperty('urls')&&server.hasOwnProperty('url')){utils.deprecated('RTCIceServer.url','RTCIceServer.urls');server=JSON.parse(JSON.stringify(server));server.urls=server.url;delete server.url;newIceServers.push(server);}else{newIceServers.push(pcConfig.iceServers[i]);}}
+pcConfig.iceServers=newIceServers;}
+return new OrigPeerConnection(pcConfig,pcConstraints);};window.RTCPeerConnection.prototype=OrigPeerConnection.prototype;if('generateCertificate'in window.RTCPeerConnection){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function get(){return OrigPeerConnection.generateCertificate;}});}}
+function shimTrackEventTransceiver(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&'receiver'in window.RTCTrackEvent.prototype&&!window.RTCTransceiver){Object.defineProperty(window.RTCTrackEvent.prototype,'transceiver',{get:function get(){return{receiver:this.receiver};}});}}
+function shimCreateOfferLegacy(window){var origCreateOffer=window.RTCPeerConnection.prototype.createOffer;window.RTCPeerConnection.prototype.createOffer=function(offerOptions){if(offerOptions){if(typeof offerOptions.offerToReceiveAudio!=='undefined'){offerOptions.offerToReceiveAudio=!!offerOptions.offerToReceiveAudio;}
+var audioTransceiver=this.getTransceivers().find(function(transceiver){return transceiver.sender.track&&transceiver.sender.track.kind==='audio';});if(offerOptions.offerToReceiveAudio===false&&audioTransceiver){if(audioTransceiver.direction==='sendrecv'){if(audioTransceiver.setDirection){audioTransceiver.setDirection('sendonly');}else{audioTransceiver.direction='sendonly';}}else if(audioTransceiver.direction==='recvonly'){if(audioTransceiver.setDirection){audioTransceiver.setDirection('inactive');}else{audioTransceiver.direction='inactive';}}}else if(offerOptions.offerToReceiveAudio===true&&!audioTransceiver){this.addTransceiver('audio');}
+if(typeof offerOptions.offerToReceiveVideo!=='undefined'){offerOptions.offerToReceiveVideo=!!offerOptions.offerToReceiveVideo;}
+var videoTransceiver=this.getTransceivers().find(function(transceiver){return transceiver.sender.track&&transceiver.sender.track.kind==='video';});if(offerOptions.offerToReceiveVideo===false&&videoTransceiver){if(videoTransceiver.direction==='sendrecv'){if(videoTransceiver.setDirection){videoTransceiver.setDirection('sendonly');}else{videoTransceiver.direction='sendonly';}}else if(videoTransceiver.direction==='recvonly'){if(videoTransceiver.setDirection){videoTransceiver.setDirection('inactive');}else{videoTransceiver.direction='inactive';}}}else if(offerOptions.offerToReceiveVideo===true&&!videoTransceiver){this.addTransceiver('video');}}
+return origCreateOffer.apply(this,arguments);};}},{"../utils":15}],15:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.extractVersion=extractVersion;exports.wrapPeerConnectionEvent=wrapPeerConnectionEvent;exports.disableLog=disableLog;exports.disableWarnings=disableWarnings;exports.log=log;exports.deprecated=deprecated;exports.detectBrowser=detectBrowser;exports.compactObject=compactObject;function _defineProperty(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true});}else{obj[key]=value;}return obj;}
+var logDisabled_=true;var deprecationWarnings_=true;function extractVersion(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos],10);}
+function wrapPeerConnectionEvent(window,eventNameToWrap,wrapper){if(!window.RTCPeerConnection){return;}
+var proto=window.RTCPeerConnection.prototype;var nativeAddEventListener=proto.addEventListener;proto.addEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap){return nativeAddEventListener.apply(this,arguments);}
+var wrappedCallback=function wrappedCallback(e){var modifiedEvent=wrapper(e);if(modifiedEvent){cb(modifiedEvent);}};this._eventMap=this._eventMap||{};this._eventMap[cb]=wrappedCallback;return nativeAddEventListener.apply(this,[nativeEventName,wrappedCallback]);};var nativeRemoveEventListener=proto.removeEventListener;proto.removeEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap||!this._eventMap||!this._eventMap[cb]){return nativeRemoveEventListener.apply(this,arguments);}
+var unwrappedCb=this._eventMap[cb];delete this._eventMap[cb];return nativeRemoveEventListener.apply(this,[nativeEventName,unwrappedCb]);};Object.defineProperty(proto,'on'+eventNameToWrap,{get:function get(){return this['_on'+eventNameToWrap];},set:function set(cb){if(this['_on'+eventNameToWrap]){this.removeEventListener(eventNameToWrap,this['_on'+eventNameToWrap]);delete this['_on'+eventNameToWrap];}
+if(cb){this.addEventListener(eventNameToWrap,this['_on'+eventNameToWrap]=cb);}},enumerable:true,configurable:true});}
+function disableLog(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+(typeof bool==='undefined'?'undefined':_typeof(bool))+'. Please use a boolean.');}
+logDisabled_=bool;return bool?'adapter.js logging disabled':'adapter.js logging enabled';}
+function disableWarnings(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+(typeof bool==='undefined'?'undefined':_typeof(bool))+'. Please use a boolean.');}
+deprecationWarnings_=!bool;return'adapter.js deprecation warnings '+(bool?'disabled':'enabled');}
+function log(){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'){if(logDisabled_){return;}
+if(typeof console!=='undefined'&&typeof console.log==='function'){console.log.apply(console,arguments);}}}
+function deprecated(oldMethod,newMethod){if(!deprecationWarnings_){return;}
+console.warn(oldMethod+' is deprecated, please use '+newMethod+' instead.');}
+function detectBrowser(window){var navigator=window.navigator;var result={browser:null,version:null};if(typeof window==='undefined'||!window.navigator){result.browser='Not a browser.';return result;}
+if(navigator.mozGetUserMedia){result.browser='firefox';result.version=extractVersion(navigator.userAgent,/Firefox\/(\d+)\./,1);}else if(navigator.webkitGetUserMedia){result.browser='chrome';result.version=extractVersion(navigator.userAgent,/Chrom(e|ium)\/(\d+)\./,2);}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){result.browser='edge';result.version=extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2);}else if(window.RTCPeerConnection&&navigator.userAgent.match(/AppleWebKit\/(\d+)\./)){result.browser='safari';result.version=extractVersion(navigator.userAgent,/AppleWebKit\/(\d+)\./,1);}else{result.browser='Not a supported browser.';return result;}
+return result;}
+function compactObject(data){if((typeof data==='undefined'?'undefined':_typeof(data))!=='object'){return data;}
+return Object.keys(data).reduce(function(accumulator,key){var isObject=_typeof(data[key])==='object';var value=isObject?compactObject(data[key]):data[key];var isEmptyObject=isObject&&!Object.keys(value).length;if(value===undefined||isEmptyObject){return accumulator;}
+return Object.assign(accumulator,_defineProperty({},key,value));},{});}},{}],16:[function(require,module,exports){'use strict';var SDPUtils=require('sdp');function fixStatsType(stat){return{inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[stat.type]||stat.type;}
+function writeMediaSection(transceiver,caps,type,stream,dtlsRole){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),type==='offer'?'actpass':dtlsRole||'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 trackId=transceiver.rtpSender._initialTrackId||transceiver.rtpSender.track.id;transceiver.rtpSender._initialTrackId=trackId;var msid='msid:'+(stream?stream.id:'-')+' '+
+trackId+'\r\n';sdp+='a='+msid;sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' '+msid;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+
+transceiver.sendEncodingParameters[0].ssrc+' '+
+transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}}
+sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' cname:'+SDPUtils.localCName+'\r\n';}
+return sdp;}
+function filterIceServers(iceServers,edgeVersion){var hasTurn=false;iceServers=JSON.parse(JSON.stringify(iceServers));return iceServers.filter(function(server){if(server&&(server.urls||server.url)){var urls=server.urls||server.url;if(server.url&&!server.urls){console.warn('RTCIceServer.url is deprecated! Use urls instead.');}
+var isString=typeof urls==='string';if(isString){urls=[urls];}
+urls=urls.filter(function(url){var validTurn=url.indexOf('turn:')===0&&url.indexOf('transport=udp')!==-1&&url.indexOf('turn:[')===-1&&!hasTurn;if(validTurn){hasTurn=true;return true;}
+return url.indexOf('stun:')===0&&edgeVersion>=14393&&url.indexOf('?transport=udp')===-1;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;}});}
+function getCommonCapabilities(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};var findCodecByPayloadType=function(pt,codecs){pt=parseInt(pt,10);for(var i=0;i<codecs.length;i++){if(codecs[i].payloadType===pt||codecs[i].preferredPayloadType===pt){return codecs[i];}}};var rtxCapabilityMatches=function(lRtx,rRtx,lCodecs,rCodecs){var lCodec=findCodecByPayloadType(lRtx.parameters.apt,lCodecs);var rCodec=findCodecByPayloadType(rRtx.parameters.apt,rCodecs);return lCodec&&rCodec&&lCodec.name.toLowerCase()===rCodec.name.toLowerCase();};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){if(lCodec.name.toLowerCase()==='rtx'&&lCodec.parameters&&rCodec.parameters.apt){if(!rtxCapabilityMatches(lCodec,rCodec,localCapabilities.codecs,remoteCapabilities.codecs)){continue;}}
+rCodec=JSON.parse(JSON.stringify(rCodec));rCodec.numChannels=Math.min(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;}
+function isActionAllowedInSignalingState(action,type,signalingState){return{offer:{setLocalDescription:['stable','have-local-offer'],setRemoteDescription:['stable','have-remote-offer']},answer:{setLocalDescription:['have-remote-offer','have-local-pranswer'],setRemoteDescription:['have-local-offer','have-remote-pranswer']}}[type][action].indexOf(signalingState)!==-1;}
+function maybeAddCandidate(iceTransport,candidate){var alreadyAdded=iceTransport.getRemoteCandidates().find(function(remoteCandidate){return candidate.foundation===remoteCandidate.foundation&&candidate.ip===remoteCandidate.ip&&candidate.port===remoteCandidate.port&&candidate.priority===remoteCandidate.priority&&candidate.protocol===remoteCandidate.protocol&&candidate.type===remoteCandidate.type;});if(!alreadyAdded){iceTransport.addRemoteCandidate(candidate);}
+return!alreadyAdded;}
+function makeError(name,description){var e=new Error(description);e.name=name;e.code={NotSupportedError:9,InvalidStateError:11,InvalidAccessError:15,TypeError:undefined,OperationError:undefined}[name];return e;}
+module.exports=function(window,edgeVersion){function addTrackToStreamAndFireEvent(track,stream){stream.addTrack(track);stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack',{track:track}));}
+function removeTrackFromStreamAndFireEvent(track,stream){stream.removeTrack(track);stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack',{track:track}));}
+function fireAddTrack(pc,track,receiver,streams){var trackEvent=new Event('track');trackEvent.track=track;trackEvent.receiver=receiver;trackEvent.transceiver={receiver:receiver};trackEvent.streams=streams;window.setTimeout(function(){pc._dispatchEvent('track',trackEvent);});}
+var RTCPeerConnection=function(config){var pc=this;var _eventTarget=document.createDocumentFragment();['addEventListener','removeEventListener','dispatchEvent'].forEach(function(method){pc[method]=_eventTarget[method].bind(_eventTarget);});this.canTrickleIceCandidates=null;this.needNegotiation=false;this.localStreams=[];this.remoteStreams=[];this._localDescription=null;this._remoteDescription=null;this.signalingState='stable';this.iceConnectionState='new';this.connectionState='new';this.iceGatheringState='new';config=JSON.parse(JSON.stringify(config||{}));this.usingBundle=config.bundlePolicy==='max-bundle';if(config.rtcpMuxPolicy==='negotiate'){throw(makeError('NotSupportedError','rtcpMuxPolicy \'negotiate\' is not supported'));}else if(!config.rtcpMuxPolicy){config.rtcpMuxPolicy='require';}
+switch(config.iceTransportPolicy){case'all':case'relay':break;default:config.iceTransportPolicy='all';break;}
+switch(config.bundlePolicy){case'balanced':case'max-compat':case'max-bundle':break;default:config.bundlePolicy='balanced';break;}
+config.iceServers=filterIceServers(config.iceServers||[],edgeVersion);this._iceGatherers=[];if(config.iceCandidatePoolSize){for(var i=config.iceCandidatePoolSize;i>0;i--){this._iceGatherers.push(new window.RTCIceGatherer({iceServers:config.iceServers,gatherPolicy:config.iceTransportPolicy}));}}else{config.iceCandidatePoolSize=0;}
+this._config=config;this.transceivers=[];this._sdpSessionId=SDPUtils.generateSessionId();this._sdpSessionVersion=0;this._dtlsRole=undefined;this._isClosed=false;};Object.defineProperty(RTCPeerConnection.prototype,'localDescription',{configurable:true,get:function(){return this._localDescription;}});Object.defineProperty(RTCPeerConnection.prototype,'remoteDescription',{configurable:true,get:function(){return this._remoteDescription;}});RTCPeerConnection.prototype.onicecandidate=null;RTCPeerConnection.prototype.onaddstream=null;RTCPeerConnection.prototype.ontrack=null;RTCPeerConnection.prototype.onremovestream=null;RTCPeerConnection.prototype.onsignalingstatechange=null;RTCPeerConnection.prototype.oniceconnectionstatechange=null;RTCPeerConnection.prototype.onconnectionstatechange=null;RTCPeerConnection.prototype.onicegatheringstatechange=null;RTCPeerConnection.prototype.onnegotiationneeded=null;RTCPeerConnection.prototype.ondatachannel=null;RTCPeerConnection.prototype._dispatchEvent=function(name,event){if(this._isClosed){return;}
+this.dispatchEvent(event);if(typeof this['on'+name]==='function'){this['on'+name](event);}};RTCPeerConnection.prototype._emitGatheringStateChange=function(){var event=new Event('icegatheringstatechange');this._dispatchEvent('icegatheringstatechange',event);};RTCPeerConnection.prototype.getConfiguration=function(){return this._config;};RTCPeerConnection.prototype.getLocalStreams=function(){return this.localStreams;};RTCPeerConnection.prototype.getRemoteStreams=function(){return this.remoteStreams;};RTCPeerConnection.prototype._createTransceiver=function(kind,doNotAdd){var hasBundleTransport=this.transceivers.length>0;var transceiver={track:null,iceGatherer:null,iceTransport:null,dtlsTransport:null,localCapabilities:null,remoteCapabilities:null,rtpSender:null,rtpReceiver:null,kind:kind,mid:null,sendEncodingParameters:null,recvEncodingParameters:null,stream:null,associatedRemoteMediaStreams:[],wantReceive:true};if(this.usingBundle&&hasBundleTransport){transceiver.iceTransport=this.transceivers[0].iceTransport;transceiver.dtlsTransport=this.transceivers[0].dtlsTransport;}else{var transports=this._createIceAndDtlsTransports();transceiver.iceTransport=transports.iceTransport;transceiver.dtlsTransport=transports.dtlsTransport;}
+if(!doNotAdd){this.transceivers.push(transceiver);}
+return transceiver;};RTCPeerConnection.prototype.addTrack=function(track,stream){if(this._isClosed){throw makeError('InvalidStateError','Attempted to call addTrack on a closed peerconnection.');}
+var alreadyExists=this.transceivers.find(function(s){return s.track===track;});if(alreadyExists){throw makeError('InvalidAccessError','Track already exists.');}
+var transceiver;for(var i=0;i<this.transceivers.length;i++){if(!this.transceivers[i].track&&this.transceivers[i].kind===track.kind){transceiver=this.transceivers[i];}}
+if(!transceiver){transceiver=this._createTransceiver(track.kind);}
+this._maybeFireNegotiationNeeded();if(this.localStreams.indexOf(stream)===-1){this.localStreams.push(stream);}
+transceiver.track=track;transceiver.stream=stream;transceiver.rtpSender=new window.RTCRtpSender(track,transceiver.dtlsTransport);return transceiver.rtpSender;};RTCPeerConnection.prototype.addStream=function(stream){var pc=this;if(edgeVersion>=15025){stream.getTracks().forEach(function(track){pc.addTrack(track,stream);});}else{var clonedStream=stream.clone();stream.getTracks().forEach(function(track,idx){var clonedTrack=clonedStream.getTracks()[idx];track.addEventListener('enabled',function(event){clonedTrack.enabled=event.enabled;});});clonedStream.getTracks().forEach(function(track){pc.addTrack(track,clonedStream);});}};RTCPeerConnection.prototype.removeTrack=function(sender){if(this._isClosed){throw makeError('InvalidStateError','Attempted to call removeTrack on a closed peerconnection.');}
+if(!(sender instanceof window.RTCRtpSender)){throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.');}
+var transceiver=this.transceivers.find(function(t){return t.rtpSender===sender;});if(!transceiver){throw makeError('InvalidAccessError','Sender was not created by this connection.');}
+var stream=transceiver.stream;transceiver.rtpSender.stop();transceiver.rtpSender=null;transceiver.track=null;transceiver.stream=null;var localStreams=this.transceivers.map(function(t){return t.stream;});if(localStreams.indexOf(stream)===-1&&this.localStreams.indexOf(stream)>-1){this.localStreams.splice(this.localStreams.indexOf(stream),1);}
+this._maybeFireNegotiationNeeded();};RTCPeerConnection.prototype.removeStream=function(stream){var pc=this;stream.getTracks().forEach(function(track){var sender=pc.getSenders().find(function(s){return s.track===track;});if(sender){pc.removeTrack(sender);}});};RTCPeerConnection.prototype.getSenders=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpSender;}).map(function(transceiver){return transceiver.rtpSender;});};RTCPeerConnection.prototype.getReceivers=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpReceiver;}).map(function(transceiver){return transceiver.rtpReceiver;});};RTCPeerConnection.prototype._createIceGatherer=function(sdpMLineIndex,usingBundle){var pc=this;if(usingBundle&&sdpMLineIndex>0){return this.transceivers[0].iceGatherer;}else if(this._iceGatherers.length){return this._iceGatherers.shift();}
+var iceGatherer=new window.RTCIceGatherer({iceServers:this._config.iceServers,gatherPolicy:this._config.iceTransportPolicy});Object.defineProperty(iceGatherer,'state',{value:'new',writable:true});this.transceivers[sdpMLineIndex].bufferedCandidateEvents=[];this.transceivers[sdpMLineIndex].bufferCandidates=function(event){var end=!event.candidate||Object.keys(event.candidate).length===0;iceGatherer.state=end?'completed':'gathering';if(pc.transceivers[sdpMLineIndex].bufferedCandidateEvents!==null){pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event);}};iceGatherer.addEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);return iceGatherer;};RTCPeerConnection.prototype._gather=function(mid,sdpMLineIndex){var pc=this;var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer.onlocalcandidate){return;}
+var bufferedCandidateEvents=this.transceivers[sdpMLineIndex].bufferedCandidateEvents;this.transceivers[sdpMLineIndex].bufferedCandidateEvents=null;iceGatherer.removeEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);iceGatherer.onlocalcandidate=function(evt){if(pc.usingBundle&&sdpMLineIndex>0){return;}
+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==='new'||iceGatherer.state==='gathering'){iceGatherer.state='completed';}}else{if(iceGatherer.state==='new'){iceGatherer.state='gathering';}
+cand.component=1;cand.ufrag=iceGatherer.getLocalParameters().usernameFragment;var serializedCandidate=SDPUtils.writeCandidate(cand);event.candidate=Object.assign(event.candidate,SDPUtils.parseCandidate(serializedCandidate));event.candidate.candidate=serializedCandidate;event.candidate.toJSON=function(){return{candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex,usernameFragment:event.candidate.usernameFragment};};}
+var sections=SDPUtils.getMediaSections(pc._localDescription.sdp);if(!end){sections[event.candidate.sdpMLineIndex]+='a='+event.candidate.candidate+'\r\n';}else{sections[event.candidate.sdpMLineIndex]+='a=end-of-candidates\r\n';}
+pc._localDescription.sdp=SDPUtils.getDescription(pc._localDescription.sdp)+
+sections.join('');var complete=pc.transceivers.every(function(transceiver){return transceiver.iceGatherer&&transceiver.iceGatherer.state==='completed';});if(pc.iceGatheringState!=='gathering'){pc.iceGatheringState='gathering';pc._emitGatheringStateChange();}
+if(!end){pc._dispatchEvent('icecandidate',event);}
+if(complete){pc._dispatchEvent('icecandidate',new Event('icecandidate'));pc.iceGatheringState='complete';pc._emitGatheringStateChange();}};window.setTimeout(function(){bufferedCandidateEvents.forEach(function(e){iceGatherer.onlocalcandidate(e);});},0);};RTCPeerConnection.prototype._createIceAndDtlsTransports=function(){var pc=this;var iceTransport=new window.RTCIceTransport(null);iceTransport.onicestatechange=function(){pc._updateIceConnectionState();pc._updateConnectionState();};var dtlsTransport=new window.RTCDtlsTransport(iceTransport);dtlsTransport.ondtlsstatechange=function(){pc._updateConnectionState();};dtlsTransport.onerror=function(){Object.defineProperty(dtlsTransport,'state',{value:'failed',writable:true});pc._updateConnectionState();};return{iceTransport:iceTransport,dtlsTransport:dtlsTransport};};RTCPeerConnection.prototype._disposeIceAndDtlsTransports=function(sdpMLineIndex){var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer){delete iceGatherer.onlocalcandidate;delete this.transceivers[sdpMLineIndex].iceGatherer;}
+var iceTransport=this.transceivers[sdpMLineIndex].iceTransport;if(iceTransport){delete iceTransport.onicestatechange;delete this.transceivers[sdpMLineIndex].iceTransport;}
+var dtlsTransport=this.transceivers[sdpMLineIndex].dtlsTransport;if(dtlsTransport){delete dtlsTransport.ondtlsstatechange;delete dtlsTransport.onerror;delete this.transceivers[sdpMLineIndex].dtlsTransport;}};RTCPeerConnection.prototype._transceive=function(transceiver,send,recv){var params=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);if(send&&transceiver.rtpSender){params.encodings=transceiver.sendEncodingParameters;params.rtcp={cname:SDPUtils.localCName,compound:transceiver.rtcpParameters.compound};if(transceiver.recvEncodingParameters.length){params.rtcp.ssrc=transceiver.recvEncodingParameters[0].ssrc;}
+transceiver.rtpSender.send(params);}
+if(recv&&transceiver.rtpReceiver&&params.codecs.length>0){if(transceiver.kind==='video'&&transceiver.recvEncodingParameters&&edgeVersion<15019){transceiver.recvEncodingParameters.forEach(function(p){delete p.rtx;});}
+if(transceiver.recvEncodingParameters.length){params.encodings=transceiver.recvEncodingParameters;}else{params.encodings=[{}];}
+params.rtcp={compound:transceiver.rtcpParameters.compound};if(transceiver.rtcpParameters.cname){params.rtcp.cname=transceiver.rtcpParameters.cname;}
+if(transceiver.sendEncodingParameters.length){params.rtcp.ssrc=transceiver.sendEncodingParameters[0].ssrc;}
+transceiver.rtpReceiver.receive(params);}};RTCPeerConnection.prototype.setLocalDescription=function(description){var pc=this;if(['offer','answer'].indexOf(description.type)===-1){return Promise.reject(makeError('TypeError','Unsupported type "'+description.type+'"'));}
+if(!isActionAllowedInSignalingState('setLocalDescription',description.type,pc.signalingState)||pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set local '+description.type+' in state '+pc.signalingState));}
+var sections;var sessionpart;if(description.type==='offer'){sections=SDPUtils.splitSections(description.sdp);sessionpart=sections.shift();sections.forEach(function(mediaSection,sdpMLineIndex){var caps=SDPUtils.parseRtpParameters(mediaSection);pc.transceivers[sdpMLineIndex].localCapabilities=caps;});pc.transceivers.forEach(function(transceiver,sdpMLineIndex){pc._gather(transceiver.mid,sdpMLineIndex);});}else if(description.type==='answer'){sections=SDPUtils.splitSections(pc._remoteDescription.sdp);sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver=pc.transceivers[sdpMLineIndex];var iceGatherer=transceiver.iceGatherer;var iceTransport=transceiver.iceTransport;var dtlsTransport=transceiver.dtlsTransport;var localCapabilities=transceiver.localCapabilities;var remoteCapabilities=transceiver.remoteCapabilities;var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;if(!rejected&&!transceiver.rejected){var remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);var remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);if(isIceLite){remoteDtlsParameters.role='server';}
+if(!pc.usingBundle||sdpMLineIndex===0){pc._gather(transceiver.mid,sdpMLineIndex);if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,isIceLite?'controlling':'controlled');}
+if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}}
+var params=getCommonCapabilities(localCapabilities,remoteCapabilities);pc._transceive(transceiver,params.codecs.length>0,false);}});}
+pc._localDescription={type:description.type,sdp:description.sdp};if(description.type==='offer'){pc._updateSignalingState('have-local-offer');}else{pc._updateSignalingState('stable');}
+return Promise.resolve();};RTCPeerConnection.prototype.setRemoteDescription=function(description){var pc=this;if(['offer','answer'].indexOf(description.type)===-1){return Promise.reject(makeError('TypeError','Unsupported type "'+description.type+'"'));}
+if(!isActionAllowedInSignalingState('setRemoteDescription',description.type,pc.signalingState)||pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set remote '+description.type+' in state '+pc.signalingState));}
+var streams={};pc.remoteStreams.forEach(function(stream){streams[stream.id]=stream;});var receiverList=[];var sections=SDPUtils.splitSections(description.sdp);var sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;var usingBundle=SDPUtils.matchPrefix(sessionpart,'a=group:BUNDLE ').length>0;pc.usingBundle=usingBundle;var iceOptions=SDPUtils.matchPrefix(sessionpart,'a=ice-options:')[0];if(iceOptions){pc.canTrickleIceCandidates=iceOptions.substr(14).split(' ').indexOf('trickle')>=0;}else{pc.canTrickleIceCandidates=false;}
+sections.forEach(function(mediaSection,sdpMLineIndex){var lines=SDPUtils.splitLines(mediaSection);var kind=SDPUtils.getKind(mediaSection);var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;var protocol=lines[0].substr(2).split(' ')[2];var direction=SDPUtils.getDirection(mediaSection,sessionpart);var remoteMsid=SDPUtils.parseMsid(mediaSection);var mid=SDPUtils.getMid(mediaSection)||SDPUtils.generateIdentifier();if(rejected||(kind==='application'&&(protocol==='DTLS/SCTP'||protocol==='UDP/DTLS/SCTP'))){pc.transceivers[sdpMLineIndex]={mid:mid,kind:kind,protocol:protocol,rejected:true};return;}
+if(!rejected&&pc.transceivers[sdpMLineIndex]&&pc.transceivers[sdpMLineIndex].rejected){pc.transceivers[sdpMLineIndex]=pc._createTransceiver(kind,true);}
+var transceiver;var iceGatherer;var iceTransport;var dtlsTransport;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 rtcpParameters=SDPUtils.parseRtcpParameters(mediaSection);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'||description.type==='answer')&&!rejected&&usingBundle&&sdpMLineIndex>0&&pc.transceivers[sdpMLineIndex]){pc._disposeIceAndDtlsTransports(sdpMLineIndex);pc.transceivers[sdpMLineIndex].iceGatherer=pc.transceivers[0].iceGatherer;pc.transceivers[sdpMLineIndex].iceTransport=pc.transceivers[0].iceTransport;pc.transceivers[sdpMLineIndex].dtlsTransport=pc.transceivers[0].dtlsTransport;if(pc.transceivers[sdpMLineIndex].rtpSender){pc.transceivers[sdpMLineIndex].rtpSender.setTransport(pc.transceivers[0].dtlsTransport);}
+if(pc.transceivers[sdpMLineIndex].rtpReceiver){pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(pc.transceivers[0].dtlsTransport);}}
+if(description.type==='offer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex]||pc._createTransceiver(kind);transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,usingBundle);}
+if(cands.length&&transceiver.iceTransport.state==='new'){if(isComplete&&(!usingBundle||sdpMLineIndex===0)){transceiver.iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}}
+localCapabilities=window.RTCRtpReceiver.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});}
+sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+2)*1001}];var isNewTrack=false;if(direction==='sendrecv'||direction==='sendonly'){isNewTrack=!transceiver.rtpReceiver;rtpReceiver=transceiver.rtpReceiver||new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);if(isNewTrack){var stream;track=rtpReceiver.track;if(remoteMsid&&remoteMsid.stream==='-'){}else if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();Object.defineProperty(streams[remoteMsid.stream],'id',{get:function(){return remoteMsid.stream;}});}
+Object.defineProperty(track,'id',{get:function(){return remoteMsid.track;}});stream=streams[remoteMsid.stream];}else{if(!streams.default){streams.default=new window.MediaStream();}
+stream=streams.default;}
+if(stream){addTrackToStreamAndFireEvent(track,stream);transceiver.associatedRemoteMediaStreams.push(stream);}
+receiverList.push([track,rtpReceiver,stream]);}}else if(transceiver.rtpReceiver&&transceiver.rtpReceiver.track){transceiver.associatedRemoteMediaStreams.forEach(function(s){var nativeTrack=s.getTracks().find(function(t){return t.id===transceiver.rtpReceiver.track.id;});if(nativeTrack){removeTrackFromStreamAndFireEvent(nativeTrack,s);}});transceiver.associatedRemoteMediaStreams=[];}
+transceiver.localCapabilities=localCapabilities;transceiver.remoteCapabilities=remoteCapabilities;transceiver.rtpReceiver=rtpReceiver;transceiver.rtcpParameters=rtcpParameters;transceiver.sendEncodingParameters=sendEncodingParameters;transceiver.recvEncodingParameters=recvEncodingParameters;pc._transceive(pc.transceivers[sdpMLineIndex],false,isNewTrack);}else if(description.type==='answer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex];iceGatherer=transceiver.iceGatherer;iceTransport=transceiver.iceTransport;dtlsTransport=transceiver.dtlsTransport;rtpReceiver=transceiver.rtpReceiver;sendEncodingParameters=transceiver.sendEncodingParameters;localCapabilities=transceiver.localCapabilities;pc.transceivers[sdpMLineIndex].recvEncodingParameters=recvEncodingParameters;pc.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities;pc.transceivers[sdpMLineIndex].rtcpParameters=rtcpParameters;if(cands.length&&iceTransport.state==='new'){if((isIceLite||isComplete)&&(!usingBundle||sdpMLineIndex===0)){iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}}
+if(!usingBundle||sdpMLineIndex===0){if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,'controlling');}
+if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}}
+var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;}
+pc._transceive(transceiver,direction==='sendrecv'||direction==='recvonly',direction==='sendrecv'||direction==='sendonly');if(rtpReceiver&&(direction==='sendrecv'||direction==='sendonly')){track=rtpReceiver.track;if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();}
+addTrackToStreamAndFireEvent(track,streams[remoteMsid.stream]);receiverList.push([track,rtpReceiver,streams[remoteMsid.stream]]);}else{if(!streams.default){streams.default=new window.MediaStream();}
+addTrackToStreamAndFireEvent(track,streams.default);receiverList.push([track,rtpReceiver,streams.default]);}}else{delete transceiver.rtpReceiver;}}});if(pc._dtlsRole===undefined){pc._dtlsRole=description.type==='offer'?'active':'passive';}
+pc._remoteDescription={type:description.type,sdp:description.sdp};if(description.type==='offer'){pc._updateSignalingState('have-remote-offer');}else{pc._updateSignalingState('stable');}
+Object.keys(streams).forEach(function(sid){var stream=streams[sid];if(stream.getTracks().length){if(pc.remoteStreams.indexOf(stream)===-1){pc.remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;window.setTimeout(function(){pc._dispatchEvent('addstream',event);});}
+receiverList.forEach(function(item){var track=item[0];var receiver=item[1];if(stream.id!==item[2].id){return;}
+fireAddTrack(pc,track,receiver,[stream]);});}});receiverList.forEach(function(item){if(item[2]){return;}
+fireAddTrack(pc,item[0],item[1],[]);});window.setTimeout(function(){if(!(pc&&pc.transceivers)){return;}
+pc.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.iceTransport.state==='new'&&transceiver.iceTransport.getRemoteCandidates().length>0){console.warn('Timeout for addRemoteCandidate. Consider sending '+'an end-of-candidates notification');transceiver.iceTransport.addRemoteCandidate({});}});},4000);return Promise.resolve();};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._isClosed=true;this._updateSignalingState('closed');};RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState;var event=new Event('signalingstatechange');this._dispatchEvent('signalingstatechange',event);};RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){var pc=this;if(this.signalingState!=='stable'||this.needNegotiation===true){return;}
+this.needNegotiation=true;window.setTimeout(function(){if(pc.needNegotiation){pc.needNegotiation=false;var event=new Event('negotiationneeded');pc._dispatchEvent('negotiationneeded',event);}},0);};RTCPeerConnection.prototype._updateIceConnectionState=function(){var newState;var states={'new':0,closed:0,checking:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&!transceiver.rejected){states[transceiver.iceTransport.state]++;}});newState='new';if(states.failed>0){newState='failed';}else if(states.checking>0){newState='checking';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0){newState='connected';}else if(states.completed>0){newState='completed';}
+if(newState!==this.iceConnectionState){this.iceConnectionState=newState;var event=new Event('iceconnectionstatechange');this._dispatchEvent('iceconnectionstatechange',event);}};RTCPeerConnection.prototype._updateConnectionState=function(){var newState;var states={'new':0,closed:0,connecting:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.dtlsTransport&&!transceiver.rejected){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){newState='connecting';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0){newState='connected';}
+if(newState!==this.connectionState){this.connectionState=newState;var event=new Event('connectionstatechange');this._dispatchEvent('connectionstatechange',event);}};RTCPeerConnection.prototype.createOffer=function(){var pc=this;if(pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createOffer after close'));}
+var numAudioTracks=pc.transceivers.filter(function(t){return t.kind==='audio';}).length;var numVideoTracks=pc.transceivers.filter(function(t){return t.kind==='video';}).length;var offerOptions=arguments[0];if(offerOptions){if(offerOptions.mandatory||offerOptions.optional){throw new TypeError('Legacy mandatory/optional constraints not supported.');}
+if(offerOptions.offerToReceiveAudio!==undefined){if(offerOptions.offerToReceiveAudio===true){numAudioTracks=1;}else if(offerOptions.offerToReceiveAudio===false){numAudioTracks=0;}else{numAudioTracks=offerOptions.offerToReceiveAudio;}}
+if(offerOptions.offerToReceiveVideo!==undefined){if(offerOptions.offerToReceiveVideo===true){numVideoTracks=1;}else if(offerOptions.offerToReceiveVideo===false){numVideoTracks=0;}else{numVideoTracks=offerOptions.offerToReceiveVideo;}}}
+pc.transceivers.forEach(function(transceiver){if(transceiver.kind==='audio'){numAudioTracks--;if(numAudioTracks<0){transceiver.wantReceive=false;}}else if(transceiver.kind==='video'){numVideoTracks--;if(numVideoTracks<0){transceiver.wantReceive=false;}}});while(numAudioTracks>0||numVideoTracks>0){if(numAudioTracks>0){pc._createTransceiver('audio');numAudioTracks--;}
+if(numVideoTracks>0){pc._createTransceiver('video');numVideoTracks--;}}
+var sdp=SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,pc._sdpSessionVersion++);pc.transceivers.forEach(function(transceiver,sdpMLineIndex){var track=transceiver.track;var kind=transceiver.kind;var mid=transceiver.mid||SDPUtils.generateIdentifier();transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,pc.usingBundle);}
+var localCapabilities=window.RTCRtpSender.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});}
+localCapabilities.codecs.forEach(function(codec){if(codec.name==='H264'&&codec.parameters['level-asymmetry-allowed']===undefined){codec.parameters['level-asymmetry-allowed']='1';}
+if(transceiver.remoteCapabilities&&transceiver.remoteCapabilities.codecs){transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec){if(codec.name.toLowerCase()===remoteCodec.name.toLowerCase()&&codec.clockRate===remoteCodec.clockRate){codec.preferredPayloadType=remoteCodec.payloadType;}});}});localCapabilities.headerExtensions.forEach(function(hdrExt){var remoteExtensions=transceiver.remoteCapabilities&&transceiver.remoteCapabilities.headerExtensions||[];remoteExtensions.forEach(function(rHdrExt){if(hdrExt.uri===rHdrExt.uri){hdrExt.id=rHdrExt.id;}});});var sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+1)*1001}];if(track){if(edgeVersion>=15019&&kind==='video'&&!sendEncodingParameters[0].rtx){sendEncodingParameters[0].rtx={ssrc:sendEncodingParameters[0].ssrc+1};}}
+if(transceiver.wantReceive){transceiver.rtpReceiver=new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);}
+transceiver.localCapabilities=localCapabilities;transceiver.sendEncodingParameters=sendEncodingParameters;});if(pc._config.bundlePolicy!=='max-compat'){sdp+='a=group:BUNDLE '+pc.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';}
+sdp+='a=ice-options:trickle\r\n';pc.transceivers.forEach(function(transceiver,sdpMLineIndex){sdp+=writeMediaSection(transceiver,transceiver.localCapabilities,'offer',transceiver.stream,pc._dtlsRole);sdp+='a=rtcp-rsize\r\n';if(transceiver.iceGatherer&&pc.iceGatheringState!=='new'&&(sdpMLineIndex===0||!pc.usingBundle)){transceiver.iceGatherer.getLocalCandidates().forEach(function(cand){cand.component=1;sdp+='a='+SDPUtils.writeCandidate(cand)+'\r\n';});if(transceiver.iceGatherer.state==='completed'){sdp+='a=end-of-candidates\r\n';}}});var desc=new window.RTCSessionDescription({type:'offer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.createAnswer=function(){var pc=this;if(pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createAnswer after close'));}
+if(!(pc.signalingState==='have-remote-offer'||pc.signalingState==='have-local-pranswer')){return Promise.reject(makeError('InvalidStateError','Can not call createAnswer in signalingState '+pc.signalingState));}
+var sdp=SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,pc._sdpSessionVersion++);if(pc.usingBundle){sdp+='a=group:BUNDLE '+pc.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';}
+sdp+='a=ice-options:trickle\r\n';var mediaSectionsInOffer=SDPUtils.getMediaSections(pc._remoteDescription.sdp).length;pc.transceivers.forEach(function(transceiver,sdpMLineIndex){if(sdpMLineIndex+1>mediaSectionsInOffer){return;}
+if(transceiver.rejected){if(transceiver.kind==='application'){if(transceiver.protocol==='DTLS/SCTP'){sdp+='m=application 0 DTLS/SCTP 5000\r\n';}else{sdp+='m=application 0 '+transceiver.protocol+' webrtc-datachannel\r\n';}}else if(transceiver.kind==='audio'){sdp+='m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n'+'a=rtpmap:0 PCMU/8000\r\n';}else if(transceiver.kind==='video'){sdp+='m=video 0 UDP/TLS/RTP/SAVPF 120\r\n'+'a=rtpmap:120 VP8/90000\r\n';}
+sdp+='c=IN IP4 0.0.0.0\r\n'+'a=inactive\r\n'+'a=mid:'+transceiver.mid+'\r\n';return;}
+if(transceiver.stream){var localTrack;if(transceiver.kind==='audio'){localTrack=transceiver.stream.getAudioTracks()[0];}else if(transceiver.kind==='video'){localTrack=transceiver.stream.getVideoTracks()[0];}
+if(localTrack){if(edgeVersion>=15019&&transceiver.kind==='video'&&!transceiver.sendEncodingParameters[0].rtx){transceiver.sendEncodingParameters[0].rtx={ssrc:transceiver.sendEncodingParameters[0].ssrc+1};}}}
+var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;}
+sdp+=writeMediaSection(transceiver,commonCapabilities,'answer',transceiver.stream,pc._dtlsRole);if(transceiver.rtcpParameters&&transceiver.rtcpParameters.reducedSize){sdp+='a=rtcp-rsize\r\n';}});var desc=new window.RTCSessionDescription({type:'answer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.addIceCandidate=function(candidate){var pc=this;var sections;if(candidate&&!(candidate.sdpMLineIndex!==undefined||candidate.sdpMid)){return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required'));}
+return new Promise(function(resolve,reject){if(!pc._remoteDescription){return reject(makeError('InvalidStateError','Can not add ICE candidate without a remote description'));}else if(!candidate||candidate.candidate===''){for(var j=0;j<pc.transceivers.length;j++){if(pc.transceivers[j].rejected){continue;}
+pc.transceivers[j].iceTransport.addRemoteCandidate({});sections=SDPUtils.getMediaSections(pc._remoteDescription.sdp);sections[j]+='a=end-of-candidates\r\n';pc._remoteDescription.sdp=SDPUtils.getDescription(pc._remoteDescription.sdp)+
+sections.join('');if(pc.usingBundle){break;}}}else{var sdpMLineIndex=candidate.sdpMLineIndex;if(candidate.sdpMid){for(var i=0;i<pc.transceivers.length;i++){if(pc.transceivers[i].mid===candidate.sdpMid){sdpMLineIndex=i;break;}}}
+var transceiver=pc.transceivers[sdpMLineIndex];if(transceiver){if(transceiver.rejected){return resolve();}
+var cand=Object.keys(candidate.candidate).length>0?SDPUtils.parseCandidate(candidate.candidate):{};if(cand.protocol==='tcp'&&(cand.port===0||cand.port===9)){return resolve();}
+if(cand.component&&cand.component!==1){return resolve();}
+if(sdpMLineIndex===0||(sdpMLineIndex>0&&transceiver.iceTransport!==pc.transceivers[0].iceTransport)){if(!maybeAddCandidate(transceiver.iceTransport,cand)){return reject(makeError('OperationError','Can not add ICE candidate'));}}
+var candidateString=candidate.candidate.trim();if(candidateString.indexOf('a=')===0){candidateString=candidateString.substr(2);}
+sections=SDPUtils.getMediaSections(pc._remoteDescription.sdp);sections[sdpMLineIndex]+='a='+
+(cand.type?candidateString:'end-of-candidates')
++'\r\n';pc._remoteDescription.sdp=SDPUtils.getDescription(pc._remoteDescription.sdp)+
+sections.join('');}else{return reject(makeError('OperationError','Can not add ICE candidate'));}}
+resolve();});};RTCPeerConnection.prototype.getStats=function(selector){if(selector&&selector instanceof window.MediaStreamTrack){var senderOrReceiver=null;this.transceivers.forEach(function(transceiver){if(transceiver.rtpSender&&transceiver.rtpSender.track===selector){senderOrReceiver=transceiver.rtpSender;}else if(transceiver.rtpReceiver&&transceiver.rtpReceiver.track===selector){senderOrReceiver=transceiver.rtpReceiver;}});if(!senderOrReceiver){throw makeError('InvalidAccessError','Invalid selector.');}
+return senderOrReceiver.getStats();}
+var promises=[];this.transceivers.forEach(function(transceiver){['rtpSender','rtpReceiver','iceGatherer','iceTransport','dtlsTransport'].forEach(function(method){if(transceiver[method]){promises.push(transceiver[method].getStats());}});});return Promise.all(promises).then(function(allStats){var results=new Map();allStats.forEach(function(stats){stats.forEach(function(stat){results.set(stat.id,stat);});});return results;});};var ortcObjects=['RTCRtpSender','RTCRtpReceiver','RTCIceGatherer','RTCIceTransport','RTCDtlsTransport'];ortcObjects.forEach(function(ortcObjectName){var obj=window[ortcObjectName];if(obj&&obj.prototype&&obj.prototype.getStats){var nativeGetstats=obj.prototype.getStats;obj.prototype.getStats=function(){return nativeGetstats.apply(this).then(function(nativeStats){var mapStats=new Map();Object.keys(nativeStats).forEach(function(id){nativeStats[id].type=fixStatsType(nativeStats[id]);mapStats.set(id,nativeStats[id]);});return mapStats;});};}});var methods=['createOffer','createAnswer'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[0]==='function'||typeof args[1]==='function'){return nativeMethod.apply(this,[arguments[2]]).then(function(description){if(typeof args[0]==='function'){args[0].apply(null,[description]);}},function(error){if(typeof args[1]==='function'){args[1].apply(null,[error]);}});}
+return nativeMethod.apply(this,arguments);};});methods=['setLocalDescription','setRemoteDescription','addIceCandidate'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'||typeof args[2]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}},function(error){if(typeof args[2]==='function'){args[2].apply(null,[error]);}});}
+return nativeMethod.apply(this,arguments);};});['getStats'].forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}});}
+return nativeMethod.apply(this,arguments);};});return RTCPeerConnection;};},{"sdp":17}],17:[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.getDescription=function(blob){var sections=SDPUtils.splitSections(blob);return sections&&sections[0];};SDPUtils.getMediaSections=function(blob){var sections=SDPUtils.splitSections(blob);sections.shift();return sections;};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:parseInt(parts[1],10),protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],address: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;case'ufrag':candidate.ufrag=parts[i+1];candidate.usernameFragment=parts[i+1];break;default:candidate[parts[i]]=parts[i+1];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.address||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);}
+if(candidate.usernameFragment||candidate.ufrag){sdp.push('ufrag');sdp.push(candidate.usernameFragment||candidate.ufrag);}
+return'candidate:'+sdp.join(' ');};SDPUtils.parseIceOptions=function(line){return line.substr(14).split(' ');};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.channels=parts.length===3?parseInt(parts[2],10):1;parsed.numChannels=parsed.channels;return parsed;};SDPUtils.writeRtpMap=function(codec){var pt=codec.payloadType;if(codec.preferredPayloadType!==undefined){pt=codec.preferredPayloadType;}
+var channels=codec.channels||codec.numChannels||1;return'a=rtpmap:'+pt+' '+codec.name+'/'+codec.clockRate+
+(channels!==1?'/'+channels:'')+'\r\n';};SDPUtils.parseExtmap=function(line){var parts=line.substr(9).split(' ');return{id:parseInt(parts[0],10),direction:parts[0].indexOf('/')>0?parts[0].split('/')[1]:'sendrecv',uri:parts[1]};};SDPUtils.writeExtmap=function(headerExtension){return'a=extmap:'+(headerExtension.id||headerExtension.preferredId)+
+(headerExtension.direction&&headerExtension.direction!=='sendrecv'?'/'+headerExtension.direction:'')+' '+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){if(codec.parameters[param]){params.push(param+'='+codec.parameters[param]);}else{params.push(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.parseSsrcGroup=function(line){var parts=line.substr(13).split(' ');return{semantics:parts.shift(),ssrcs:parts.map(function(ssrc){return parseInt(ssrc,10);})};};SDPUtils.getMid=function(mediaSection){var mid=SDPUtils.matchPrefix(mediaSection,'a=mid:')[0];if(mid){return mid.substr(6);}};SDPUtils.parseFingerprint=function(line){var parts=line.substr(14).split(' ');return{algorithm:parts[0].toLowerCase(),value:parts[1]};};SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.matchPrefix(mediaSection+sessionpart,'a=fingerprint:');return{role:'auto',fingerprints:lines.map(SDPUtils.parseFingerprint)};};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);});var maxptime=0;caps.codecs.forEach(function(codec){if(codec.maxptime>maxptime){maxptime=codec.maxptime;}});if(maxptime>0){sdp+='a=maxptime:'+maxptime+'\r\n';}
+sdp+='a=rtcp-mux\r\n';if(caps.headerExtensions){caps.headerExtensions.forEach(function(extension){sdp+=SDPUtils.writeExtmap(extension);});}
+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.substr(17).split(' ');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)};if(primarySsrc&&secondarySsrc){encParam.rtx={ssrc:secondarySsrc};}
+encodingParameters.push(encParam);if(hasRed){encParam=JSON.parse(JSON.stringify(encParam));encParam.fec={ssrc:primarySsrc,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)*1000*0.95
+-(50*40*8);}else{bandwidth=undefined;}
+encodingParameters.forEach(function(params){params.maxBitrate=bandwidth;});}
+return encodingParameters;};SDPUtils.parseRtcpParameters=function(mediaSection){var rtcpParameters={};var remoteSsrc=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(obj){return obj.attribute==='cname';})[0];if(remoteSsrc){rtcpParameters.cname=remoteSsrc.value;rtcpParameters.ssrc=remoteSsrc.ssrc;}
+var rsize=SDPUtils.matchPrefix(mediaSection,'a=rtcp-rsize');rtcpParameters.reducedSize=rsize.length>0;rtcpParameters.compound=rsize.length===0;var mux=SDPUtils.matchPrefix(mediaSection,'a=rtcp-mux');rtcpParameters.mux=mux.length>0;return rtcpParameters;};SDPUtils.parseMsid=function(mediaSection){var parts;var spec=SDPUtils.matchPrefix(mediaSection,'a=msid:');if(spec.length===1){parts=spec[0].substr(7).split(' ');return{stream:parts[0],track:parts[1]};}
+var planB=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(msidParts){return msidParts.attribute==='msid';});if(planB.length>0){parts=planB[0].value.split(' ');return{stream:parts[0],track:parts[1]};}};SDPUtils.generateSessionId=function(){return Math.random().toString().substr(2,21);};SDPUtils.writeSessionBoilerplate=function(sessId,sessVer,sessUser){var sessionId;var version=sessVer!==undefined?sessVer:2;if(sessId){sessionId=sessId;}else{sessionId=SDPUtils.generateSessionId();}
+var user=sessUser||'thisisadapterortc';return'v=0\r\n'+'o='+user+' '+sessionId+' '+version+' 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.direction){sdp+='a='+transceiver.direction+'\r\n';}else 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;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+
+transceiver.sendEncodingParameters[0].ssrc+' '+
+transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}}
+sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.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';};SDPUtils.getKind=function(mediaSection){var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');return mline[0].substr(2);};SDPUtils.isRejected=function(mediaSection){return mediaSection.split(' ',2)[1]==='0';};SDPUtils.parseMLine=function(mediaSection){var lines=SDPUtils.splitLines(mediaSection);var parts=lines[0].substr(2).split(' ');return{kind:parts[0],port:parseInt(parts[1],10),protocol:parts[2],fmt:parts.slice(3).join(' ')};};SDPUtils.parseOLine=function(mediaSection){var line=SDPUtils.matchPrefix(mediaSection,'o=')[0];var parts=line.substr(2).split(' ');return{username:parts[0],sessionId:parts[1],sessionVersion:parseInt(parts[2],10),netType:parts[3],addressType:parts[4],address:parts[5]};};SDPUtils.isValidSDP=function(blob){if(typeof blob!=='string'||blob.length===0){return false;}
+var lines=SDPUtils.splitLines(blob);for(var i=0;i<lines.length;i++){if(lines[i].length<2||lines[i].charAt(1)!=='='){return false;}}
+return true;};if(typeof module==='object'){module.exports=SDPUtils;}},{}]},{},[1])(1)});
\ No newline at end of file
index 2ac8e89e7ed7c5777e3265cf4f52814ae155b29b..7a6a42b8a0c26694ecf3dc65c8ecbd01ce5a4906 100644 (file)
-/*
- * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
- *
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- *
- * The Initial Developer of the Original Code is
- * Anthony Minessale II <anthm@freeswitch.org>
- * Portions created by the Initial Developer are Copyright (C)
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Anthony Minessale II <anthm@freeswitch.org>
- *
- * jquery.FSRTC.js - WebRTC Glue code
- *
- */
 
-(function($) {
-
-    // Find the line in sdpLines that starts with |prefix|, and, if specified,
-    // contains |substr| (case-insensitive search).
-    function findLine(sdpLines, prefix, substr) {
-        return findLineInRange(sdpLines, 0, -1, prefix, substr);
-    }
-
-    // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
-    // and, if specified, contains |substr| (case-insensitive search).
-    function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
-        var realEndLine = (endLine != -1) ? endLine : sdpLines.length;
-        for (var i = startLine; i < realEndLine; ++i) {
-            if (sdpLines[i].indexOf(prefix) === 0) {
-                if (!substr || sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
-                    return i;
-                }
-            }
-        }
-        return null;
-    }
-
-    // Gets the codec payload type from an a=rtpmap:X line.
-    function getCodecPayloadType(sdpLine) {
-        var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
-        var result = sdpLine.match(pattern);
-        return (result && result.length == 2) ? result[1] : null;
-    }
-    
-    // Returns a new m= line with the specified codec as the first one.
-    function setDefaultCodec(mLine, payload) {
-        var elements = mLine.split(' ');
-        var newLine = [];
-        var index = 0;
-        for (var i = 0; i < elements.length; i++) {
-            if (index === 3) { // Format of media starts from the fourth.
-                newLine[index++] = payload; // Put target payload to the first.
-            }
-            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() {}
-            },
-      useStream: null,
-        }, 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;
-    };
-
-    // Sets Opus in stereo if stereo is enabled, by adding the stereo=1 fmtp param.
-    $.FSRTC.prototype.stereoHack = function(sdp) {
-        var self = this;
-
-        if (!self.options.useStereo) {
-            return sdp;
-        }
-
-        var sdpLines = sdp.split('\r\n');
-
-        // Find opus payload.
-        var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'), opusPayload;
-
-        if (!opusIndex) {
-           return sdp;
-       } else {
-            opusPayload = getCodecPayloadType(sdpLines[opusIndex]);
-        }
-
-        // Find the payload in fmtp line.
-        var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString());
-
-        if (fmtpLineIndex === null) {
-           // create an fmtp line
-           sdpLines[opusIndex] = sdpLines[opusIndex] + '\r\na=fmtp:' + opusPayload.toString() + " stereo=1; sprop-stereo=1"
-       } else {
-            // Append stereo=1 to fmtp line.
-            sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1');
-       }
-
-        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 onRemoteStreamSuccess(self, stream) {
-      console.log("Remote Stream Success");
-      doCallback(self, "onRemoteStream", stream);
-    }
-
-    function onICE(self, candidate) {
-        self.mediaData.candidate = candidate;
-        self.mediaData.candidateList.push(self.mediaData.candidate);
-
-        doCallback(self, "onICE");
-    }
-
-    function doCallback(self, func, arg) {
-        if (func in self.options.callbacks) {
-            self.options.callbacks[func](self, arg);
-        }
-    }
-
-    function onICEComplete(self, candidate) {
-        console.log("ICE Complete");
-        doCallback(self, "onICEComplete");
-    }
-
-    function onChannelError(self, e) {
-        console.error("Channel Error", e);
-        doCallback(self, "onError", e);
-    }
-
-    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 (typeof element.srcObject !== 'undefined') {
-           element.srcObject = stream;
-       } else {
-           console.error('Error attaching stream to element.');
-       }
-    }
-    
-    function onRemoteStream(self, stream) {
-        if (self.options.useVideo) {
-            self.options.useVideo.style.display = 'block';
-
-           // Hacks for Mobile Safari
-           var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
-
-           if (iOS) {
-               self.options.useVideo.setAttribute("playsinline", true);
-           }
-        }
-
-        var element = self.options.useAudio;
-        console.log("REMOTE STREAM", stream, element);
-
-       FSRTCattachMediaStream(element, stream);
-       
-
-       
-        //self.options.useAudio.play();
-        self.remoteStream = stream;
-        onRemoteStreamSuccess(self, 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';
-            self.options.useVideo['src'] = '';
-        }
-
-        if (self.localStream && !self.options.useStream) {
-            if(typeof self.localStream.stop == 'function') {
-                self.localStream.stop();
-            } else {
-               if (self.localStream.active){
-                    var tracks = self.localStream.getTracks();
-                    console.log(tracks);
-                   tracks.forEach(function(track, index){
-                       console.log(track);
-                       track.stop();
-                   })
-                }
-            }
-            self.localStream = null;
-        }
-
-        if (self.options.localVideo) {
-      deactivateLocalVideo(self.options.localVideo);
-        }
-
-       if (self.options.localVideoStream && !self.options.useStream) {
-            if(typeof self.options.localVideoStream.stop == 'function') {
-               self.options.localVideoStream.stop();
-            } else {
-               if (self.options.localVideoStream.active){
-                    var tracks = self.options.localVideoStream.getTracks();
-                    console.log(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;
-    if (!self.localStream) {
-        return false;
-    }
-       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;
-           }
-
-           self.audioEnabled = audioTracks[i].enabled;
-       }
-
-       return !self.audioEnabled;
-    }
-
-    $.FSRTC.prototype.getVideoMute = function() {
-       var self = this;
-       return self.videoEnabled;
-    }
-
-    $.FSRTC.prototype.setVideoMute = function(what) {
-       var self = this;
-    if (!self.localStream) {
-        return false;
-    }
-       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 = 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
-                },
-                turnServer: self.options.turnServer
-            });
-
-            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 (self.options.useVideo && self.options.localVideo && !self.options.useStream) {
-            getUserMedia({
-               constraints: {
-                    audio: false,
-                    video: { deviceId: params.useCamera },
-               },
-               localVideo: self.options.localVideo,
-               onsuccess: function(e) {self.options.localVideoStream = e; console.log("local video ready");},
-               onerror: function(e) {console.error("local video error!");}
-            });
-       }
-
-    if (self.options.useStream) {
-      if (self.options.useVideo) {
-        self.options.localVideoStream = self.options.useStream;
-        if (self.options.localVideo) {
-          activateLocalVideo(self.options.localVideo, self.options.useStream);
-        }
-      }
-      onSuccess(self.options.useStream);
-    }
-    else {
-      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) {//obj.options.videoParams.chromeMediaSource == 'desktop') {
-           console.error("SCREEN SHARE", obj.options.videoParams);
-           audio = false;
-       } else {
-           audio = {
-           };
-
-           if (obj.options.audioParams) {
-               audio = obj.options.audioParams;
-            }
-
-           if (obj.options.useMic !== "any") {
-               //audio.optional = [{sourceId: obj.options.useMic}];
-               audio.deviceId = {exact: obj.options.useMic};
-           }
-       }
-
-    if (obj.options.useVideo && obj.options.localVideo && !obj.options.useStream) {
-            getUserMedia({
-               constraints: {
-                    audio: false,
-                    video: { deviceId: obj.options.useCamera },
-               },
-               localVideo: obj.options.localVideo,
-               onsuccess: function(e) {obj.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) {
-           if (!obj.options.useCamera && !!navigator.mozGetUserMedia) {
-               //This is an issue, only FireFox needs to ask this additional question if its screen or window we need a better way
-               var dowin = window.confirm("Do you want to share an application window?  If not you can share an entire screen.");
-
-               video = {
-                   width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth},
-                   height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight},
-                   mediaSource: dowin ? "window" : "screen"
-               }
-           } else {
-               var opt = [];
-               if (obj.options.useCamera) {
-                   opt.push({sourceId: obj.options.useCamera});
-               }
-               
-               if (bestFrameRate) {
-                   opt.push({minFrameRate: bestFrameRate});
-                   opt.push({maxFrameRate: bestFrameRate});
-               }
-               
-               video = {
-                   mandatory: obj.options.videoParams,
-                   optional: opt               
-               };
-          // NOTE: This is a workaround for
-          // https://bugs.chromium.org/p/chromium/issues/detail?id=862325
-          if (!!navigator.userAgent.match(/Android/i)) {
-            delete video.frameRate.min;
-          }
-           }
-       } else {
-
-           video = {
-               //mandatory: obj.options.videoParams,
-               width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth},
-               height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight}
-           };
-           
-            
-           
-           var useVideo = obj.options.useVideo;
-
-           if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") {
-               //if (!video.optional) {
-               //video.optional = [];
-               //}
-
-               
-               if (obj.options.useCamera !== "any") {
-                   //video.optional.push({sourceId: obj.options.useCamera});
-        video.deviceId = {
-          exact: obj.options.useCamera,
-        };
-               }
-
-               if (bestFrameRate) {
-                   //video.optional.push({minFrameRate: bestFrameRate});
-                   //video.optional.push({maxFrameRate: bestFrameRate});
-                   video.frameRate = {ideal: bestFrameRate, min: minFrameRate, max: 30};
-               }
-
-           } else {
-               console.log("Camera Disabled");
-               video = false;
-               useVideo = false;
-           }
-       }
-
-       return {audio: audio, video: video, useVideo: useVideo};
-    }
-    
-    $.FSRTC.prototype.call = function(profile) {
-        checkCompat();
-       
-        var self = this;
-       var screen = false;
-
-        self.type = "offer";
-
-       if (self.options.videoParams && self.options.screenShare) { //self.options.videoParams.chromeMediaSource == 'desktop') {
-           screen = true;
-       }
-
-        function onSuccess(stream) {
-           self.localStream = stream;
-           
-           if (screen) {
-               self.constraints.offerToReceiveVideo = false;
-               self.constraints.offerToReceiveAudio = false;
-               self.constraints.offerToSendAudio = 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,
-                turnServer: self.options.turnServer
-            });
-
-            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 (self.options.useStream) {
-      if (self.options.useVideo) {
-        self.options.localVideoStream = self.options.useStream;
-        if (self.options.localVideo) {
-          activateLocalVideo(self.options.localVideo, self.options.useStream);
-        }
-      }
-      onSuccess(self.options.useStream);
-    }
-    else if (mediaParams.audio || mediaParams.video) {
-
-            getUserMedia({
-               constraints: {
-                    audio: mediaParams.audio,
-                    video: mediaParams.video
-               },
-               video: mediaParams.useVideo,
-               onsuccess: onSuccess,
-               onerror: onError
-            });
-
-       } else {
-           onSuccess(null);
-       }
-
-
-
-        /*
-        navigator.getUserMedia({
-            video: self.options.useVideo,
-            audio: true
-        }, onSuccess, onError);
-        */
-
-    };
-
-    // DERIVED from RTCPeerConnection-v1.5
-    // 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
-    
-    function FSRTCPeerConnection(options) {
-       var gathering = false, done = false;
-       var config = {};
-        var default_ice = [{ urls: ['stun:stun.l.google.com:19302'] }];
-
-        if (self.options.turnServer) {
-          default_ice.push(self.options.turnServer)
-        }
-
-        if (options.iceServers) {
-            if (typeof(options.iceServers) === "boolean") {
-               config.iceServers = default_ice;
-            } else {
-               config.iceServers = options.iceServers;
-           }
-        }
-
-       config.bundlePolicy = "max-compat";
-
-        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();
-           }
-        };
-
-        // attachStream = MediaStream;
-        if (options.attachStream) peer.addStream(options.attachStream);
-
-        // attachStreams[0] = audio-stream;
-        // attachStreams[1] = video-stream;
-        // attachStreams[2] = screen-capturing-stream;
-        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;
-
-            // onRemoteStreamEnded(MediaStream)
-            remoteMediaStream.oninactive = function () {
-                if (options.onRemoteStreamEnded) options.onRemoteStreamEnded(remoteMediaStream);
-            };
-
-            // onRemoteStream(MediaStream)
-            if (options.onRemoteStream) options.onRemoteStream(remoteMediaStream);
-
-            //console.debug('on:add:stream', remoteMediaStream);
-        };
-
-        //var constraints = options.constraints || {
-         //  offerToReceiveAudio: true,
-           //offerToReceiveVideo: true   
-        //};
-
-        // onOfferSDP(RTCSessionDescription)
-        function createOffer() {
-            if (!options.onOfferSDP) return;
-
-            peer.createOffer(function(sessionDescription) {
-                sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
-                peer.setLocalDescription(sessionDescription);
-                options.onOfferSDP(sessionDescription);
-            },
-                               onSdpError, options.constraints);
-        }
-
-        // onAnswerSDP(RTCSessionDescription)
-        function createAnswer() {
-            if (options.type != "answer") return;
-
-            //options.offerSDP.sdp = addStereo(options.offerSDP.sdp);
-            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();
-        }
-
-        // DataChannel Bandwidth
-        function setBandwidth(sdp) {
-            // remove existing bandwidth lines
-            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;
-        }
-
-        // old: FF<>Chrome interoperability management
-        function getInteropSDP(sdp) {
-            var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
-            extractedChars = '';
-
-            function getChars() {
-                extractedChars += chars[parseInt(Math.random() * 40)] || '';
-                if (extractedChars.length < 40) getChars();
-
-                return extractedChars;
-            }
-
-            // usually audio-only streaming failure occurs out of audio-specific crypto line
-            // a=crypto:1 AES_CM_128_HMAC_SHA1_32 --------- kAttributeCryptoVoice
-            if (options.onAnswerSDP) sdp = sdp.replace(/(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g, '');
-
-            // video-specific crypto line i.e. SHA1_80
-            // a=crypto:1 AES_CM_128_HMAC_SHA1_80 --------- kAttributeCryptoVideo
-            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;
-        }
-
-        // DataChannel management
-        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);
-            };
-        }
-
-        function openAnswererChannel() {
-            peer.ondatachannel = function(event) {
-                channel = event.channel;
-                channel.binaryType = 'blob';
-                setChannelEvents();
-            };
-
-            return;
-        }
-
-        // fake:true is also available on chrome under a flag!
-        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 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;
-                  }
-                }
-            }
-
-        };
-    }
-
-    // getUserMedia
-    var video_constraints = {
-        //mandatory: {},
-        //optional: []
-    };
-
-  function activateLocalVideo(el, stream) {
-    el.srcObject = stream;
-    el.style.display = 'block';
-  }
-
-  function deactivateLocalVideo(el) {
-    el.srcObject = null;
-    el.style.display = 'none';
-  }
-
-    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) {
-        activateLocalVideo(options.localVideo, stream);
-            }
-
-            if (options.onsuccess) {
-                options.onsuccess(stream);
-            }
-
-            media = stream;
-        }
-
-        return media;
-    }
-
-    $.FSRTC.resSupported = function(w, h) {
-       for (var i in $.FSRTC.validRes) {
-           if ($.FSRTC.validRes[i][0] == w && $.FSRTC.validRes[i][1] == h) {
-               return true;
-           }
-       }
-
-       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 = [[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;
-       }
-
-       w = resList[resI][0];
-       h = resList[resI][1];
-       resI++;
-
-       var video = {
-           width: {exact: w},
-           height: {exact: h}
-       };
-
-  if (cam !== "any") {
-    video.deviceId = {
-      exact: cam,
-    };
-  }
-
-       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.warn( 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);
-    }
-
-    $.FSRTC.checkPerms = function (runtime, check_audio, check_video) {
-       getUserMedia({
-           constraints: {
-               audio: check_audio,
-               video: check_video,
-           },
-           onsuccess: function(e) {
-
-               e.getTracks().forEach(function(track) {track.stop();});
-               
-               console.info("media perm init complete"); 
-               if (runtime) {
-                    setTimeout(runtime, 100, true);
-               }
-            },
-           onerror: function(e) {
-               if (check_video && check_audio) {
-                   console.error("error, retesting with audio params only");
-                   return $.FSRTC.checkPerms(runtime, check_audio, false);
-               }
-
-               console.error("media perm init error");
-
-               if (runtime) {
-                   runtime(false)
-               }
-           }
-       });
-    }
-
-})(jQuery);
-/*
- * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
- *
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is jquery.jsonrpclient.js modified for Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- *
- * The Initial Developer of the Original Code is
- * Textalk AB http://textalk.se/
- * Portions created by the Initial Developer are Copyright (C)
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Anthony Minessale II <anthm@freeswitch.org>
- *
- * jquery.jsonrpclient.js - JSON RPC client code
- *
- */
-/**
- * This plugin requires jquery.json.js to be available, or at least the methods $.toJSON and
- * $.parseJSON.
- *
- * The plan is to make use of websockets if they are available, but work just as well with only
- * http if not.
- *
- * Usage example:
- *
- *   var foo = new $.JsonRpcClient({ ajaxUrl: '/backend/jsonrpc' });
- *   foo.call(
- *     'bar', [ 'A parameter', 'B parameter' ],
- *     function(result) { alert('Foo bar answered: ' + result.my_answer); },
- *     function(error)  { console.log('There was an error', error); }
- *   );
- *
- * More examples are available in README.md
- */
-(function($) {
-  /**
-   * @fn new
-   * @memberof $.JsonRpcClient
-   *
-   * @param options An object stating the backends:
-   *                ajaxUrl    A url (relative or absolute) to a http(s) backend.
-   *                socketUrl  A url (relative of absolute) to a ws(s) backend.
-   *                onmessage  A socket message handler for other messages (non-responses).
-   *                getSocket  A function returning a WebSocket or null.
-   *                           It must take an onmessage_cb and bind it to the onmessage event
-   *                           (or chain it before/after some other onmessage handler).
-   *                           Or, it could return null if no socket is available.
-   *                           The returned instance must have readyState <= 1, and if less than 1,
-   *                           react to onopen binding.
-   */
-    $.JsonRpcClient = function(options) {
-        var self = this;
-        this.options = $.extend({
-            ajaxUrl       : null,
-            socketUrl     : null, ///< The ws-url for default getSocket.
-            onmessage     : null, ///< Other onmessage-handler.
-            login         : null, /// auth login
-            passwd        : null, /// auth passwd
-            sessid        : null,
-           loginParams   : null,
-           userVariables : null,
-            getSocket     : function(onmessage_cb) { return self._getSocket(onmessage_cb); }
-        }, options);
-
-        self.ws_cnt = 0;
-
-        // Declare an instance version of the onmessage callback to wrap 'this'.
-        this.wsOnMessage = function(event) { self._wsOnMessage(event); };
-    };
-
-    /// Holding the WebSocket on default getsocket.
-    $.JsonRpcClient.prototype._ws_socket = null;
-
-    /// Object <id>: { success_cb: cb, error_cb: cb }
-    $.JsonRpcClient.prototype._ws_callbacks = {};
-
-    /// The next JSON-RPC request id.
-    $.JsonRpcClient.prototype._current_id = 1;
-
-
-    $.JsonRpcClient.prototype.speedTest = function (bytes, cb) {
-        var socket = this.options.getSocket(this.wsOnMessage);
-       if (socket !== null) {
-           this.speedCB = cb;
-           this.speedBytes = bytes;
-           socket.send("#SPU " + bytes);
-
-           var loops = bytes / 1024;
-           var rem = bytes % 1024;
-           var i;
-           var data = new Array(1024).join(".");
-           for (i = 0; i < loops; i++) {
-               socket.send("#SPB " + data);
-           }
-           
-           if (rem) {
-               socket.send("#SPB " + data);
-           }
-
-           socket.send("#SPE");
-       }
-    };
-
-
-
-    /**
-     * @fn call
-     * @memberof $.JsonRpcClient
-     *
-     * @param method     The method to run on JSON-RPC server.
-     * @param params     The params; an array or object.
-     * @param success_cb A callback for successful request.
-     * @param error_cb   A callback for error.
-     */
-    $.JsonRpcClient.prototype.call = function(method, params, success_cb, error_cb) {
-    // Construct the JSON-RPC 2.0 request.
-
-        if (!params) {
-            params = {};
-        }
-
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        var request = {
-            jsonrpc : '2.0',
-            method  : method,
-            params  : params,
-            id      : this._current_id++  // Increase the id counter to match request/response
-        };
-
-        if (!success_cb) {
-            success_cb = function(e){console.log("Success: ", e);};
-        }
-
-        if (!error_cb) {
-            error_cb = function(e){console.log("Error: ", e);};
-        }
-
-        // Try making a WebSocket call.
-        var socket = this.options.getSocket(this.wsOnMessage);
-        if (socket !== null) {
-            this._wsCall(socket, request, success_cb, error_cb);
-            return;
-        }
-
-        // No WebSocket, and no HTTP backend?  This won't work.
-        if (this.options.ajaxUrl === null) {
-            throw "$.JsonRpcClient.call used with no websocket and no http endpoint.";
-        }
-
-        $.ajax({
-            type     : 'POST',
-            url      : this.options.ajaxUrl,
-            data     : $.toJSON(request),
-            dataType : 'json',
-            cache    : false,
-
-            success  : function(data) {
-                if ('error' in data) error_cb(data.error, this);
-                success_cb(data.result, this);
-            },
-
-            // JSON-RPC Server could return non-200 on error
-            error    : function(jqXHR, textStatus, errorThrown) {
-                try {
-                    var response = $.parseJSON(jqXHR.responseText);
-
-                    if ('console' in window) console.log(response);
-
-                    error_cb(response.error, this);
-                } catch (err) {
-                    // Perhaps the responseText wasn't really a jsonrpc-error.
-                    error_cb({ error: jqXHR.responseText }, this);
-                }
-            }
-        });
-    };
-
-    /**
-     * Notify sends a command to the server that won't need a response.  In http, there is probably
-     * an empty response - that will be dropped, but in ws there should be no response at all.
-     *
-     * This is very similar to call, but has no id and no handling of callbacks.
-     *
-     * @fn notify
-     * @memberof $.JsonRpcClient
-     *
-     * @param method     The method to run on JSON-RPC server.
-     * @param params     The params; an array or object.
-     */
-    $.JsonRpcClient.prototype.notify = function(method, params) {
-        // Construct the JSON-RPC 2.0 request.
-
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        var request = {
-            jsonrpc: '2.0',
-            method:  method,
-            params:  params
-        };
-
-        // Try making a WebSocket call.
-        var socket = this.options.getSocket(this.wsOnMessage);
-        if (socket !== null) {
-            this._wsCall(socket, request);
-            return;
-        }
-
-        // No WebSocket, and no HTTP backend?  This won't work.
-        if (this.options.ajaxUrl === null) {
-            throw "$.JsonRpcClient.notify used with no websocket and no http endpoint.";
-        }
-
-        $.ajax({
-            type     : 'POST',
-            url      : this.options.ajaxUrl,
-            data     : $.toJSON(request),
-            dataType : 'json',
-            cache    : false
-        });
-    };
-
-    /**
-     * Make a batch-call by using a callback.
-     *
-     * The callback will get an object "batch" as only argument.  On batch, you can call the methods
-     * "call" and "notify" just as if it was a normal $.JsonRpcClient object, and all calls will be
-     * sent as a batch call then the callback is done.
-     *
-     * @fn batch
-     * @memberof $.JsonRpcClient
-     *
-     * @param callback    The main function which will get a batch handler to run call and notify on.
-     * @param all_done_cb A callback function to call after all results have been handled.
-     * @param error_cb    A callback function to call if there is an error from the server.
-     *                    Note, that batch calls should always get an overall success, and the
-     *                    only error
-     */
-    $.JsonRpcClient.prototype.batch = function(callback, all_done_cb, error_cb) {
-        var batch = new $.JsonRpcClient._batchObject(this, all_done_cb, error_cb);
-        callback(batch);
-        batch._execute();
-    };
-
-    /**
-     * The default getSocket handler.
-     *
-     * @param onmessage_cb The callback to be bound to onmessage events on the socket.
-     *
-     * @fn _getSocket
-     * @memberof $.JsonRpcClient
-     */
-
-    $.JsonRpcClient.prototype.socketReady = function() {
-        if (this._ws_socket === null || this._ws_socket.readyState > 1) {
-            return false;
-        }
-
-        return true;
-    };
-
-    $.JsonRpcClient.prototype.closeSocket = function() {
-       var self = this;
-        if (self.socketReady()) {
-            self._ws_socket.onclose = function (w) {console.log("Closing Socket");};
-            self._ws_socket.close();
-        }
-    };
-
-    $.JsonRpcClient.prototype.loginData = function(params) {
-       var self = this;
-        self.options.login = params.login;
-        self.options.passwd = params.passwd;
-       self.options.loginParams = params.loginParams;
-       self.options.userVariables = params.userVariables;
-    };
-
-    $.JsonRpcClient.prototype.connectSocket = function(onmessage_cb) {
-        var self = this;
-
-        if (self.to) {
-            clearTimeout(self.to);
-        }
-
-        if (!self.socketReady()) {
-            self.authing = false;
-
-            if (self._ws_socket) {
-                delete self._ws_socket;
-            }
-
-            // No socket, or dying socket, let's get a new one.
-            self._ws_socket = new WebSocket(self.options.socketUrl);
-
-            if (self._ws_socket) {
-                // Set up onmessage handler.
-                self._ws_socket.onmessage = onmessage_cb;
-                self._ws_socket.onclose = function (w) {
-                    if (!self.ws_sleep) {
-                        self.ws_sleep = 1000;
-                    }
-
-                    if (self.options.onWSClose) {
-                        self.options.onWSClose(self);
-                    }
-
-                    if (self.ws_cnt > 10 && self.options.wsFallbackURL) {
-                      self.options.socketUrl = self.options.wsFallbackURL;
-                    }
-
-                    console.error("Websocket Lost " + self.ws_cnt + " sleep: " + self.ws_sleep + "msec");
-
-                    self.to = setTimeout(function() {
-                        console.log("Attempting Reconnection....");
-                        self.connectSocket(onmessage_cb);
-                    }, self.ws_sleep);
-
-                    self.ws_cnt++;
-
-                    if (self.ws_sleep < 3000 && (self.ws_cnt % 10) === 0) {
-                        self.ws_sleep += 1000;
-                    }
-                };
-
-                // Set up sending of message for when the socket is open.
-                self._ws_socket.onopen = function() {
-                    if (self.to) {
-                        clearTimeout(self.to);
-                    }
-                    self.ws_sleep = 1000;
-                    self.ws_cnt = 0;
-                    if (self.options.onWSConnect) {
-                        self.options.onWSConnect(self);
-                    }
-
-                    var req;
-                    // Send the requests.
-                    while ((req = $.JsonRpcClient.q.pop())) {
-                        self._ws_socket.send(req);
-                    }
-                };
-            }
-        }
-
-        return self._ws_socket ? true : false;
-    };
-
-    $.JsonRpcClient.prototype.stopRetrying = function() {
-      if (self.to)
-        clearTimeout(self.to);
-    }
-
-    $.JsonRpcClient.prototype._getSocket = function(onmessage_cb) {
-        // If there is no ws url set, we don't have a socket.
-        // Likewise, if there is no window.WebSocket.
-        if (this.options.socketUrl === null || !("WebSocket" in window)) return null;
-
-        this.connectSocket(onmessage_cb);
-
-        return this._ws_socket;
-    };
-
-    /**
-     * Queue to save messages delivered when websocket is not ready
-     */
-    $.JsonRpcClient.q = [];
-
-    /**
-     * Internal handler to dispatch a JRON-RPC request through a websocket.
-     *
-     * @fn _wsCall
-     * @memberof $.JsonRpcClient
-     */
-    $.JsonRpcClient.prototype._wsCall = function(socket, request, success_cb, error_cb) {
-        var request_json = $.toJSON(request);
-
-        if (socket.readyState < 1) {
-            // The websocket is not open yet; we have to set sending of the message in onopen.
-            self = this; // In closure below, this is set to the WebSocket.  Use self instead.
-            $.JsonRpcClient.q.push(request_json);
-        } else {
-            // We have a socket and it should be ready to send on.
-            socket.send(request_json);
-        }
-
-        // Setup callbacks.  If there is an id, this is a call and not a notify.
-        if ('id' in request && typeof success_cb !== 'undefined') {
-            this._ws_callbacks[request.id] = { request: request_json, request_obj: request, success_cb: success_cb, error_cb: error_cb };
-        }
-    };
-
-    /**
-     * Internal handler for the websocket messages.  It determines if the message is a JSON-RPC
-     * response, and if so, tries to couple it with a given callback.  Otherwise, it falls back to
-     * given external onmessage-handler, if any.
-     *
-     * @param event The websocket onmessage-event.
-     */
-    $.JsonRpcClient.prototype._wsOnMessage = function(event) {
-        // Check if this could be a JSON RPC message.
-        var response;
-
-       // Special sub proto
-       if (event.data[0] == "#" && event.data[1] == "S" && event.data[2] == "P") {
-           if (event.data[3] == "U") {
-               this.up_dur = parseInt(event.data.substring(4));
-           } else if (this.speedCB && event.data[3] == "D") {
-               this.down_dur = parseInt(event.data.substring(4));
-
-               var up_kps = (((this.speedBytes * 8) / (this.up_dur / 1000)) / 1024).toFixed(0);
-               var down_kps = (((this.speedBytes * 8) / (this.down_dur / 1000)) / 1024).toFixed(0);
-               
-               console.info("Speed Test: Up: " + up_kps + " Down: " + down_kps);
-               var cb = this.speedCB;
-               this.speedCB = null;
-               cb(event, {
-                       upDur: this.up_dur,
-                       downDur: this.down_dur,
-                       upKPS: up_kps,
-                       downKPS: down_kps
-               });
-           }
-           
-           return;
-       }
-
-
-        try {
-            response = $.parseJSON(event.data);
-
-            /// @todo Make using the jsonrcp 2.0 check optional, to use this on JSON-RPC 1 backends.
-
-            if (typeof response === 'object' &&
-                'jsonrpc' in response &&
-                response.jsonrpc === '2.0') {
-
-                /// @todo Handle bad response (without id).
-
-                // If this is an object with result, it is a response.
-                if ('result' in response && this._ws_callbacks[response.id]) {
-                    // Get the success callback.
-                    var success_cb = this._ws_callbacks[response.id].success_cb;
-
-    /*
-                    // set the sessid if present
-                    if ('sessid' in response.result && !this.options.sessid || (this.options.sessid != response.result.sessid)) {
-                        this.options.sessid = response.result.sessid;
-                        if (this.options.sessid) {
-                            console.log("setting session UUID to: " + this.options.sessid);
-                        }
-                    }
-    */
-                    // Delete the callback from the storage.
-                    delete this._ws_callbacks[response.id];
-
-                    // Run callback with result as parameter.
-                    success_cb(response.result, this);
-                    return;
-                } else if ('error' in response && this._ws_callbacks[response.id]) {
-                // If this is an object with error, it is an error response.
-
-                // Get the error callback.
-                    var error_cb = this._ws_callbacks[response.id].error_cb;
-                    var orig_req = this._ws_callbacks[response.id].request;
-
-                    // if this is an auth request, send the credentials and resend the failed request
-                    if (!self.authing && response.error.code == -32000 && self.options.login && self.options.passwd) {
-                        self.authing = true;
-
-                        this.call("login", { login: self.options.login, passwd: self.options.passwd, loginParams: self.options.loginParams,
-                                            userVariables: self.options.userVariables},
-                            this._ws_callbacks[response.id].request_obj.method == "login" ?
-                            function(e) {
-                                self.authing = false;
-                                console.log("logged in");
-                                delete self._ws_callbacks[response.id];
-
-                                if (self.options.onWSLogin) {
-                                    self.options.onWSLogin(true, self);
-                                }
-                            }
-
-                            :
-
-                            function(e) {
-                                self.authing = false;
-                                console.log("logged in, resending request id: " + response.id);
-                                var socket = self.options.getSocket(self.wsOnMessage);
-                                if (socket !== null) {
-                                    socket.send(orig_req);
-                                }
-                                if (self.options.onWSLogin) {
-                                    self.options.onWSLogin(true, self);
-                                }
-                            },
-
-                            function(e) {
-                                console.log("error logging in, request id:", response.id);
-                                delete self._ws_callbacks[response.id];
-                                error_cb(response.error, this);
-                                if (self.options.onWSLogin) {
-                                self.options.onWSLogin(false, self);
-                                }
-                            });
-                            return;
-                        }
-
-                        // Delete the callback from the storage.
-                        delete this._ws_callbacks[response.id];
-
-                        // Run callback with the error object as parameter.
-                        error_cb(response.error, this);
-                        return;
-                    }
-                }
-            } catch (err) {
-            // Probably an error while parsing a non json-string as json.  All real JSON-RPC cases are
-            // handled above, and the fallback method is called below.
-            console.log("ERROR: "+ err);
-            return;
-        }
-
-        // This is not a JSON-RPC response.  Call the fallback message handler, if given.
-        if (typeof this.options.onmessage === 'function') {
-            event.eventData = response;
-            if (!event.eventData) {
-                event.eventData = {};
-            }
-
-            var reply = this.options.onmessage(event);
-
-            if (reply && typeof reply === "object" && event.eventData.id) {
-                var msg = {
-                    jsonrpc: "2.0",
-                    id: event.eventData.id,
-                    result: reply
-                };
-
-                var socket = self.options.getSocket(self.wsOnMessage);
-                if (socket !== null) {
-                    socket.send($.toJSON(msg));
-                }
-            }
-        }
-    };
-
-
-    /************************************************************************************************
-     * Batch object with methods
-     ************************************************************************************************/
-
-    /**
-     * Handling object for batch calls.
-     */
-    $.JsonRpcClient._batchObject = function(jsonrpcclient, all_done_cb, error_cb) {
-        // Array of objects to hold the call and notify requests.  Each objects will have the request
-        // object, and unless it is a notify, success_cb and error_cb.
-        this._requests   = [];
-
-        this.jsonrpcclient = jsonrpcclient;
-        this.all_done_cb = all_done_cb;
-        this.error_cb    = typeof error_cb === 'function' ? error_cb : function() {};
-
-    };
-
-    /**
-     * @sa $.JsonRpcClient.prototype.call
-     */
-    $.JsonRpcClient._batchObject.prototype.call = function(method, params, success_cb, error_cb) {
-
-        if (!params) {
-            params = {};
-        }
-
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        if (!success_cb) {
-            success_cb = function(e){console.log("Success: ", e);};
-        }
-
-        if (!error_cb) {
-        error_cb = function(e){console.log("Error: ", e);};
-        }
-
-        this._requests.push({
-            request    : {
-            jsonrpc : '2.0',
-            method  : method,
-            params  : params,
-            id      : this.jsonrpcclient._current_id++  // Use the client's id series.
-        },
-            success_cb : success_cb,
-            error_cb   : error_cb
-        });
-    };
-
-    /**
-     * @sa $.JsonRpcClient.prototype.notify
-     */
-    $.JsonRpcClient._batchObject.prototype.notify = function(method, params) {
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        this._requests.push({
-            request    : {
-                jsonrpc : '2.0',
-                method  : method,
-                params  : params
-            }
-        });
-    };
-
-    /**
-     * Executes the batched up calls.
-     */
-    $.JsonRpcClient._batchObject.prototype._execute = function() {
-        var self = this;
-
-        if (this._requests.length === 0) return; // All done :P
-
-        // Collect all request data and sort handlers by request id.
-        var batch_request = [];
-        var handlers = {};
-        var i = 0;
-        var call;
-        var success_cb;
-        var error_cb;
-
-        // If we have a WebSocket, just send the requests individually like normal calls.
-        var socket = self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);
-        if (socket !== null) {
-            for (i = 0; i < this._requests.length; i++) {
-                call = this._requests[i];
-                success_cb = ('success_cb' in call) ? call.success_cb : undefined;
-                error_cb   = ('error_cb'   in call) ? call.error_cb   : undefined;
-                self.jsonrpcclient._wsCall(socket, call.request, success_cb, error_cb);
-            }
-
-            if (typeof all_done_cb === 'function') all_done_cb(result);
-            return;
-        }
-
-        for (i = 0; i < this._requests.length; i++) {
-            call = this._requests[i];
-            batch_request.push(call.request);
-
-            // If the request has an id, it should handle returns (otherwise it's a notify).
-            if ('id' in call.request) {
-                handlers[call.request.id] = {
-                    success_cb : call.success_cb,
-                    error_cb   : call.error_cb
-                };
-            }
-        }
-
-        success_cb = function(data) { self._batchCb(data, handlers, self.all_done_cb); };
-
-        // No WebSocket, and no HTTP backend?  This won't work.
-        if (self.jsonrpcclient.options.ajaxUrl === null) {
-            throw "$.JsonRpcClient.batch used with no websocket and no http endpoint.";
-        }
-
-        // Send request
-        $.ajax({
-            url      : self.jsonrpcclient.options.ajaxUrl,
-            data     : $.toJSON(batch_request),
-            dataType : 'json',
-            cache    : false,
-            type     : 'POST',
-
-            // Batch-requests should always return 200
-            error    : function(jqXHR, textStatus, errorThrown) {
-                self.error_cb(jqXHR, textStatus, errorThrown);
-            },
-            success  : success_cb
-        });
-    };
-
-    /**
-     * Internal helper to match the result array from a batch call to their respective callbacks.
-     *
-     * @fn _batchCb
-     * @memberof $.JsonRpcClient
-     */
-    $.JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, all_done_cb) {
-        for (var i = 0; i < result.length; i++) {
-            var response = result[i];
-
-            // Handle error
-            if ('error' in response) {
-                if (response.id === null || !(response.id in handlers)) {
-                    // An error on a notify?  Just log it to the console.
-                    if ('console' in window) console.log(response);
-                } else {
-                    handlers[response.id].error_cb(response.error, this);
-                }
-            } else {
-                // Here we should always have a correct id and no error.
-                if (!(response.id in handlers) && 'console' in window) {
-                    console.log(response);
-                } else {
-                    handlers[response.id].success_cb(response.result, this);
-                }
-            }
-        }
-
-        if (typeof all_done_cb === 'function') all_done_cb(result);
-    };
-
-})(jQuery);
-
-/*
- * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
- *
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- *
- * The Initial Developer of the Original Code is
- * Anthony Minessale II <anthm@freeswitch.org>
- * Portions created by the Initial Developer are Copyright (C)
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Anthony Minessale II <anthm@freeswitch.org>
- *
- * jquery.verto.js - Main interface
- *
- */
-
-(function($) {
-    var sources = [];
-
-    var generateGUID = (typeof(window.crypto) !== 'undefined' && typeof(window.crypto.getRandomValues) !== 'undefined') ?
-    function() {
-        // If we have a cryptographically secure PRNG, use that
-        // http://stackoverflow.com/questions/6906916/collisions-when-generating-uuids-in-javascript
-        var buf = new Uint16Array(8);
-        window.crypto.getRandomValues(buf);
-        var S4 = function(num) {
-            var ret = num.toString(16);
-            while (ret.length < 4) {
-                ret = "0" + ret;
-            }
-            return ret;
-        };
-        return (S4(buf[0]) + S4(buf[1]) + "-" + S4(buf[2]) + "-" + S4(buf[3]) + "-" + S4(buf[4]) + "-" + S4(buf[5]) + S4(buf[6]) + S4(buf[7]));
-    }
-
-    :
-
-    function() {
-        // Otherwise, just use Math.random
-        // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
-        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
-            var r = Math.random() * 16 | 0,
-            v = c == 'x' ? r : (r & 0x3 | 0x8);
-            return v.toString(16);
-        });
-    };
-
-    /// MASTER OBJ
-    $.verto = function(options, callbacks) {
-        var verto = this;
-
-        $.verto.saved.push(verto);
-
-        verto.options = $.extend({
-            login: null,
-            passwd: null,
-            socketUrl: null,
-            tag: null,
-           localTag: null,
-            videoParams: {},
-            audioParams: {},
-           loginParams: {},
-           deviceParams: {onResCheck: null},
-           userVariables: {},
-            iceServers: false,
-            ringSleep: 6000,
-      sessid: null,
-      useStream: null
-        }, options);
-
-       if (verto.options.deviceParams.useCamera) {
-           $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, verto.options.deviceParams.onResCheck);
-       }
-
-       if (!verto.options.deviceParams.useMic) {
-           verto.options.deviceParams.useMic = "any";
-       }
-
-       if (!verto.options.deviceParams.useSpeak) {
-           verto.options.deviceParams.useSpeak = "any";
-       }
-
-       if (verto.options.sessid) {
-           verto.sessid = verto.options.sessid;
-       } else {
-            verto.sessid = localStorage.getItem("verto_session_uuid") || generateGUID();
-           localStorage.setItem("verto_session_uuid", verto.sessid);
-       }
-
-        verto.dialogs = {};
-        verto.callbacks = callbacks || {};
-        verto.eventSUBS = {};
-
-        verto.rpcClient = new $.JsonRpcClient({
-            login: verto.options.login,
-            passwd: verto.options.passwd,
-            socketUrl: verto.options.socketUrl,
-            wsFallbackURL: verto.options.wsFallbackURL,
-            turnServer: verto.options.turnServer,
-           loginParams: verto.options.loginParams,
-           userVariables: verto.options.userVariables,
-            sessid: verto.sessid,
-            onmessage: function(e) {
-                return verto.handleMessage(e.eventData);
-            },
-            onWSConnect: function(o) {
-                o.call('login', {});
-            },
-            onWSLogin: function(success) {
-                if (verto.callbacks.onWSLogin) {
-                    verto.callbacks.onWSLogin(verto, success);
-                }
-            },
-            onWSClose: function(success) {
-                if (verto.callbacks.onWSClose) {
-                    verto.callbacks.onWSClose(verto, success);
-                }
-                verto.purge();
-            }
-        });
-
-        var tag = verto.options.tag;
-        if (typeof(tag) === "function") {
-          tag = tag();
-        }
-
-        if (verto.options.ringFile && verto.options.tag) {
-            verto.ringer = $("#" + tag);
-        }
-
-        verto.rpcClient.call('login', {});
-
-    };
-
-
-    $.verto.prototype.deviceParams = function(obj) {
-        var verto = this;
-
-       for (var i in obj) {
-            verto.options.deviceParams[i] = obj[i];
-       }
-
-       if (obj.useCamera) {
-           $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, obj ? obj.onResCheck : undefined);
-       }
-    };
-
-    $.verto.prototype.videoParams = function(obj) {
-        var verto = this;
-
-       for (var i in obj) {
-            verto.options.videoParams[i] = obj[i];
-       }
-    };
-
-    $.verto.prototype.iceServers = function(obj) {
-        var verto = this;
-        verto.options.iceServers = obj;
-    };
-
-    $.verto.prototype.loginData = function(params) {
-       var verto = this;
-        verto.options.login = params.login;
-        verto.options.passwd = params.passwd;
-        verto.rpcClient.loginData(params);
-    };
-
-    $.verto.prototype.logout = function(msg) {
-        var verto = this;
-        verto.rpcClient.closeSocket();
-        if (verto.callbacks.onWSClose) {
-            verto.callbacks.onWSClose(verto, false);
-        }
-        verto.purge();
-    };
-
-    $.verto.prototype.login = function(msg) {
-        var verto = this;
-        verto.logout();
-        verto.rpcClient.call('login', {});
-    };
-
-    $.verto.prototype.message = function(msg) {
-        var verto = this;
-        var err = 0;
-
-        if (!msg.to) {
-            console.error("Missing To");
-            err++;
-        }
-
-        if (!msg.body) {
-            console.error("Missing Body");
-            err++;
-        }
-
-        if (err) {
-            return false;
-        }
-
-        verto.sendMethod("verto.info", {
-            msg: msg
-        });
-
-        return true;
-    };
-
-    $.verto.prototype.processReply = function(method, success, e) {
-        var verto = this;
-        var i;
-
-        //console.log("Response: " + method, success, e);
-
-        switch (method) {
-        case "verto.subscribe":
-            for (i in e.unauthorizedChannels) {
-                drop_bad(verto, e.unauthorizedChannels[i]);
-            }
-            for (i in e.subscribedChannels) {
-                mark_ready(verto, e.subscribedChannels[i]);
-            }
-
-            break;
-        case "verto.unsubscribe":
-            //console.error(e);
-            break;
-        }
-    };
-
-    $.verto.prototype.sendMethod = function(method, params) {
-        var verto = this;
-
-        verto.rpcClient.call(method, params,
-
-        function(e) {
-            /* Success */
-            verto.processReply(method, true, e);
-        },
-
-        function(e) {
-            /* Error */
-            verto.processReply(method, false, e);
-        });
-    };
-
-    function do_sub(verto, channel, obj) {
-
-    }
-
-    function drop_bad(verto, channel) {
-        console.error("drop unauthorized channel: " + channel);
-        delete verto.eventSUBS[channel];
-    }
-
-    function mark_ready(verto, channel) {
-        for (var j in verto.eventSUBS[channel]) {
-            verto.eventSUBS[channel][j].ready = true;
-            console.log("subscribed to channel: " + channel);
-            if (verto.eventSUBS[channel][j].readyHandler) {
-                verto.eventSUBS[channel][j].readyHandler(verto, channel);
-            }
-        }
-    }
-
-    var SERNO = 1;
-
-    function do_subscribe(verto, channel, subChannels, sparams) {
-        var params = sparams || {};
-
-        var local = params.local;
-
-        var obj = {
-            eventChannel: channel,
-            userData: params.userData,
-            handler: params.handler,
-            ready: false,
-            readyHandler: params.readyHandler,
-            serno: SERNO++
-        };
-
-        var isnew = false;
-
-        if (!verto.eventSUBS[channel]) {
-            verto.eventSUBS[channel] = [];
-            subChannels.push(channel);
-            isnew = true;
-        }
-
-        verto.eventSUBS[channel].push(obj);
-
-        if (local) {
-            obj.ready = true;
-            obj.local = true;
-        }
-
-        if (!isnew && verto.eventSUBS[channel][0].ready) {
-            obj.ready = true;
-            if (obj.readyHandler) {
-                obj.readyHandler(verto, channel);
-            }
-        }
-
-        return {
-            serno: obj.serno,
-            eventChannel: channel
-        };
-
-    }
-
-    $.verto.prototype.subscribe = function(channel, sparams) {
-        var verto = this;
-        var r = [];
-        var subChannels = [];
-        var params = sparams || {};
-
-        if (typeof(channel) === "string") {
-            r.push(do_subscribe(verto, channel, subChannels, params));
-        } else {
-            for (var i in channel) {
-                r.push(do_subscribe(verto, channel, subChannels, params));
-            }
-        }
-
-        if (subChannels.length) {
-            verto.sendMethod("verto.subscribe", {
-                eventChannel: subChannels.length == 1 ? subChannels[0] : subChannels,
-                subParams: params.subParams
-            });
-        }
-
-        return r;
-    };
-
-    $.verto.prototype.unsubscribe = function(handle) {
-        var verto = this;
-        var i;
-
-        if (!handle) {
-            for (i in verto.eventSUBS) {
-                if (verto.eventSUBS[i]) {
-                    verto.unsubscribe(verto.eventSUBS[i]);
-                }
-            }
-        } else {
-            var unsubChannels = {};
-            var sendChannels = [];
-            var channel;
-
-            if (typeof(handle) == "string") {
-                delete verto.eventSUBS[handle];
-                unsubChannels[handle]++;
-            } else {
-                for (i in handle) {
-                    if (typeof(handle[i]) == "string") {
-                        channel = handle[i];
-                        delete verto.eventSUBS[channel];
-                        unsubChannels[channel]++;
-                    } else {
-                        var repl = [];
-                        channel = handle[i].eventChannel;
-
-                        for (var j in verto.eventSUBS[channel]) {
-                            if (verto.eventSUBS[channel][j].serno == handle[i].serno) {} else {
-                                repl.push(verto.eventSUBS[channel][j]);
-                            }
-                        }
-
-                        verto.eventSUBS[channel] = repl;
-
-                        if (verto.eventSUBS[channel].length === 0) {
-                            delete verto.eventSUBS[channel];
-                            unsubChannels[channel]++;
-                        }
-                    }
-                }
-            }
-
-            for (var u in unsubChannels) {
-                console.log("Sending Unsubscribe for: ", u);
-                sendChannels.push(u);
-            }
-
-            if (sendChannels.length) {
-                verto.sendMethod("verto.unsubscribe", {
-                    eventChannel: sendChannels.length == 1 ? sendChannels[0] : sendChannels
-                });
-            }
-        }
-    };
-
-    $.verto.prototype.broadcast = function(channel, params) {
-        var verto = this;
-        var msg = {
-            eventChannel: channel,
-            data: {}
-        };
-        for (var i in params) {
-            msg.data[i] = params[i];
-        }
-        verto.sendMethod("verto.broadcast", msg);
-    };
-
-    $.verto.prototype.purge = function(callID) {
-        var verto = this;
-        var x = 0;
-        var i;
-
-        for (i in verto.dialogs) {
-            if (!x) {
-                console.log("purging dialogs");
-            }
-            x++;
-            verto.dialogs[i].setState($.verto.enum.state.purge);
-        }
-
-        for (i in verto.eventSUBS) {
-            if (verto.eventSUBS[i]) {
-                console.log("purging subscription: " + i);
-                delete verto.eventSUBS[i];
-            }
-        }
-    };
-
-    $.verto.prototype.hangup = function(callID) {
-        var verto = this;
-        if (callID) {
-            var dialog = verto.dialogs[callID];
-
-            if (dialog) {
-                dialog.hangup();
-            }
-        } else {
-            for (var i in verto.dialogs) {
-                verto.dialogs[i].hangup();
-            }
-        }
-    };
-
-    $.verto.prototype.newCall = function(args, callbacks) {
-        var verto = this;
-
-        if (!verto.rpcClient.socketReady()) {
-            console.error("Not Connected...");
-            return;
-        }
-
-        if (args["useCamera"]) {
-            verto.options.deviceParams["useCamera"] = args["useCamera"];
-        }
-
-        var dialog = new $.verto.dialog($.verto.enum.direction.outbound, this, args);
-
-        if (callbacks) {
-            dialog.callbacks = callbacks;
-        }
-
-        dialog.invite();
-
-        return dialog;
-    };
-
-    $.verto.prototype.handleMessage = function(data) {
-        var verto = this;
-
-        if (!(data && data.method)) {
-            console.error("Invalid Data", data);
-            return;
-        }
-
-        if (data.params.callID) {
-            var dialog = verto.dialogs[data.params.callID];
-           
-           if (data.method === "verto.attach" && dialog) {
-               delete dialog.verto.dialogs[dialog.callID];
-               dialog.rtc.stop();
-               dialog = null;
-           }
-
-            if (dialog) {
-
-                switch (data.method) {
-                case 'verto.bye':
-                    dialog.hangup(data.params);
-                    break;
-                case 'verto.answer':
-                    dialog.handleAnswer(data.params);
-                    break;
-                case 'verto.media':
-                    dialog.handleMedia(data.params);
-                    break;
-                case 'verto.display':
-                    dialog.handleDisplay(data.params);
-                    break;
-                case 'verto.info':
-                    dialog.handleInfo(data.params);
-                    break;
-                default:
-                    console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED", dialog, data.method);
-                    break;
-                }
-            } else {
-
-                switch (data.method) {
-                case 'verto.attach':
-                    data.params.attach = true;
-
-                    if (data.params.sdp && data.params.sdp.indexOf("m=video") > 0) {
-                        data.params.useVideo = true;
-                    }
-
-                    if (data.params.sdp && data.params.sdp.indexOf("stereo=1") > 0) {
-                        data.params.useStereo = true;
-                    }
-
-                    dialog = new $.verto.dialog($.verto.enum.direction.inbound, verto, data.params);
-                    dialog.setState($.verto.enum.state.recovering);
-
-                    break;
-                case 'verto.invite':
-
-                    if (data.params.sdp && data.params.sdp.indexOf("m=video") > 0) {
-                        data.params.wantVideo = true;
-                    }
-
-                    if (data.params.sdp && data.params.sdp.indexOf("stereo=1") > 0) {
-                        data.params.useStereo = true;
-                    }
-
-                    dialog = new $.verto.dialog($.verto.enum.direction.inbound, verto, data.params);
-                    break;
-                default:
-                    console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED");
-                    break;
-                }
-            }
-
-            return {
-                method: data.method
-            };
-        } else {
-            switch (data.method) {
-            case 'verto.punt':
-                verto.purge();
-               verto.logout();
-               break;
-            case 'verto.event':
-                var list = null;
-                var key = null;
-
-                if (data.params) {
-                    key = data.params.eventChannel;
-                }
-
-                if (key) {
-                    list = verto.eventSUBS[key];
-
-                    if (!list) {
-                        list = verto.eventSUBS[key.split(".")[0]];
-                    }
-                }
-
-                if (!list && key && key === verto.sessid) {
-                    if (verto.callbacks.onMessage) {
-                        verto.callbacks.onMessage(verto, null, $.verto.enum.message.pvtEvent, data.params);
-                    }
-                } else if (!list && key && verto.dialogs[key]) {
-                    verto.dialogs[key].sendMessage($.verto.enum.message.pvtEvent, data.params);
-                } else if (!list) {
-                    if (!key) {
-                        key = "UNDEFINED";
-                    }
-                    console.error("UNSUBBED or invalid EVENT " + key + " IGNORED");
-                } else {
-                    for (var i in list) {
-                        var sub = list[i];
-
-                        if (!sub || !sub.ready) {
-                            console.error("invalid EVENT for " + key + " IGNORED");
-                        } else if (sub.handler) {
-                            sub.handler(verto, data.params, sub.userData);
-                        } else if (verto.callbacks.onEvent) {
-                            verto.callbacks.onEvent(verto, data.params, sub.userData);
-                        } else {
-                            console.log("EVENT:", data.params);
-                        }
-                    }
-                }
-
-                break;
-
-            case "verto.info":
-                if (verto.callbacks.onMessage) {
-                    verto.callbacks.onMessage(verto, null, $.verto.enum.message.info, data.params.msg);
-                }
-                //console.error(data);
-                console.debug("MESSAGE from: " + data.params.msg.from, data.params.msg.body);
-
-                break;
-
-            case 'verto.clientReady':
-                if (verto.callbacks.onMessage) {
-                    verto.callbacks.onMessage(verto, null, $.verto.enum.message.clientReady, data.params);
-                }
-                console.debug("CLIENT READY", data.params);
-                break;
-
-            default:
-                console.error("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED", data.method);
-                break;
-            }
-        }
-    };
-
-    var del_array = function(array, name) {
-        var r = [];
-        var len = array.length;
-
-        for (var i = 0; i < len; i++) {
-            if (array[i] != name) {
-                r.push(array[i]);
-            }
-        }
-
-        return r;
-    };
-
-    var hashArray = function() {
-        var vha = this;
-
-        var hash = {};
-        var array = [];
-
-        vha.reorder = function(a) {
-            array = a;
-            var h = hash;
-            hash = {};
-
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                var key = array[i];
-                if (h[key]) {
-                    hash[key] = h[key];
-                    delete h[key];
-                }
-            }
-            h = undefined;
-        };
-
-        vha.clear = function() {
-            hash = undefined;
-            array = undefined;
-            hash = {};
-            array = [];
-        };
-
-        vha.add = function(name, val, insertAt) {
-            var redraw = false;
-
-            if (!hash[name]) {
-                if (insertAt === undefined || insertAt < 0 || insertAt >= array.length) {
-                    array.push(name);
-                } else {
-                    var x = 0;
-                    var n = [];
-                    var len = array.length;
-
-                    for (var i = 0; i < len; i++) {
-                        if (x++==insertAt) {
-                            n.push(name);
-                        }
-                        n.push(array[i]);
-                    }
-
-                    array = undefined;
-                    array = n;
-                    n = undefined;
-                    redraw = true;
-                }
-            }
-
-            hash[name] = val;
-
-            return redraw;
-        };
-
-        vha.del = function(name) {
-            var r = false;
-
-            if (hash[name]) {
-                array = del_array(array, name);
-                delete hash[name];
-                r = true;
-            } else {
-                console.error("can't del nonexistant key " + name);
-            }
-
-            return r;
-        };
-
-        vha.get = function(name) {
-            return hash[name];
-        };
-
-        vha.order = function() {
-            return array;
-        };
-
-        vha.hash = function() {
-            return hash;
-        };
-
-        vha.indexOf = function(name) {
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                if (array[i] == name) {
-                    return i;
-                }
-            }
-        };
-
-        vha.arrayLen = function() {
-            return array.length;
-        };
-
-        vha.asArray = function() {
-            var r = [];
-
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                var key = array[i];
-                r.push(hash[key]);
-            }
-
-            return r;
-        };
-
-        vha.each = function(cb) {
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                cb(array[i], hash[array[i]]);
-            }
-        };
-
-        vha.dump = function(html) {
-            var str = "";
-
-            vha.each(function(name, val) {
-                str += "name: " + name + " val: " + JSON.stringify(val) + (html ? "<br>" : "\n");
-            });
-
-            return str;
-        };
-
-    };
-
-    $.verto.liveArray = function(verto, context, name, config) {
-        var la = this;
-        var lastSerno = 0;
-        var binding = null;
-        var user_obj = config.userObj;
-        var local = false;
-
-        // Inherit methods of hashArray
-        hashArray.call(la);
-
-        // Save the hashArray add, del, reorder, clear methods so we can make our own.
-        la._add = la.add;
-        la._del = la.del;
-        la._reorder = la.reorder;
-        la._clear = la.clear;
-
-        la.context = context;
-        la.name = name;
-        la.user_obj = user_obj;
-
-        la.verto = verto;
-        la.broadcast = function(channel, obj) {
-            verto.broadcast(channel, obj);
-        };
-        la.errs = 0;
-
-        la.clear = function() {
-            la._clear();
-            lastSerno = 0;
-
-            if (la.onChange) {
-                la.onChange(la, {
-                    action: "clear"
-                });
-            }
-        };
-
-        la.checkSerno = function(serno) {
-            if (serno < 0) {
-                return true;
-            }
-
-            if (lastSerno > 0 && serno != (lastSerno + 1)) {
-                if (la.onErr) {
-                    la.onErr(la, {
-                        lastSerno: lastSerno,
-                        serno: serno
-                    });
-                }
-                la.errs++;
-                console.debug(la.errs);
-                if (la.errs < 3) {
-                    la.bootstrap(la.user_obj);
-                }
-                return false;
-            } else {
-                lastSerno = serno;
-                return true;
-            }
-        };
-
-        la.reorder = function(serno, a) {
-            if (la.checkSerno(serno)) {
-                la._reorder(a);
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "reorder"
-                    });
-                }
-            }
-        };
-
-        la.init = function(serno, val, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "init",
-                        index: index,
-                        key: key,
-                        data: val
-                    });
-                }
-            }
-        };
-
-        la.bootObj = function(serno, val) {
-            if (la.checkSerno(serno)) {
-
-                //la.clear();
-                for (var i in val) {
-                    la._add(val[i][0], val[i][1]);
-                }
-
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "bootObj",
-                        data: val,
-                        redraw: true
-                    });
-                }
-            }
-        };
-
-        // @param serno  La is the serial number for la particular request.
-        // @param key    If looking at it as a hash table, la represents the key in the hashArray object where you want to store the val object.
-        // @param index  If looking at it as an array, la represents the position in the array where you want to store the val object.
-        // @param val    La is the object you want to store at the key or index location in the hash table / array.
-        la.add = function(serno, val, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                var redraw = la._add(key, val, index);
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "add",
-                        index: index,
-                        key: key,
-                        data: val,
-                        redraw: redraw
-                    });
-                }
-            }
-        };
-
-        la.modify = function(serno, val, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                la._add(key, val, index);
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "modify",
-                        key: key,
-                        data: val,
-                        index: index
-                    });
-                }
-            }
-        };
-
-        la.del = function(serno, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                if (index === null || index < 0 || index === undefined) {
-                    index = la.indexOf(key);
-                }
-                var ok = la._del(key);
-
-                if (ok && la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "del",
-                        key: key,
-                        index: index
-                    });
-                }
-            }
-        };
-
-        var eventHandler = function(v, e, la) {
-            var packet = e.data;
-
-            //console.error("READ:", packet);
-
-            if (packet.name != la.name) {
-                return;
-            }
-
-            switch (packet.action) {
-
-            case "init":
-                la.init(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex);
-                break;
-
-            case "bootObj":
-                la.bootObj(packet.wireSerno, packet.data);
-                break;
-            case "add":
-                la.add(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex);
-                break;
-
-            case "modify":
-                if (! (packet.arrIndex || packet.hashKey)) {
-                    console.error("Invalid Packet", packet);
-                } else {
-                    la.modify(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex);
-                }
-                break;
-            case "del":
-                if (! (packet.arrIndex || packet.hashKey)) {
-                    console.error("Invalid Packet", packet);
-                } else {
-                    la.del(packet.wireSerno, packet.hashKey, packet.arrIndex);
-                }
-                break;
-
-            case "clear":
-                la.clear();
-                break;
-
-            case "reorder":
-                la.reorder(packet.wireSerno, packet.order);
-                break;
-
-            default:
-                if (la.checkSerno(packet.wireSerno)) {
-                    if (la.onChange) {
-                        la.onChange(la, {
-                            serno: packet.wireSerno,
-                            action: packet.action,
-                            data: packet.data
-                        });
-                    }
-                }
-                break;
-            }
-        };
-
-        if (la.context) {
-            binding = la.verto.subscribe(la.context, {
-                handler: eventHandler,
-                userData: la,
-                subParams: config.subParams
-            });
-        }
-
-        la.destroy = function() {
-            la._clear();
-            la.verto.unsubscribe(binding);
-        };
-
-        la.sendCommand = function(cmd, obj) {
-            var self = la;
-            self.broadcast(self.context, {
-                liveArray: {
-                    command: cmd,
-                    context: self.context,
-                    name: self.name,
-                    obj: obj
-                }
-            });
-        };
-
-        la.bootstrap = function(obj) {
-            var self = la;
-            la.sendCommand("bootstrap", obj);
-            //self.heartbeat();
-        };
-
-        la.changepage = function(obj) {
-            var self = la;
-            self.clear();
-            self.broadcast(self.context, {
-                liveArray: {
-                    command: "changepage",
-                    context: la.context,
-                    name: la.name,
-                    obj: obj
-                }
-            });
-        };
-
-        la.heartbeat = function(obj) {
-            var self = la;
-
-            var callback = function() {
-                self.heartbeat.call(self, obj);
-            };
-            self.broadcast(self.context, {
-                liveArray: {
-                    command: "heartbeat",
-                    context: self.context,
-                    name: self.name,
-                    obj: obj
-                }
-            });
-            self.hb_pid = setTimeout(callback, 30000);
-        };
-
-        la.bootstrap(la.user_obj);
-    };
-
-    $.verto.liveTable = function(verto, context, name, jq, config) {
-        var dt;
-        var la = new $.verto.liveArray(verto, context, name, {
-            subParams: config.subParams
-        });
-        var lt = this;
-
-        lt.liveArray = la;
-        lt.dataTable = dt;
-        lt.verto = verto;
-
-        lt.destroy = function() {
-            if (dt) {
-                dt.fnDestroy();
-            }
-            if (la) {
-                la.destroy();
-            }
-
-            dt = null;
-            la = null;
-        };
-
-        la.onErr = function(obj, args) {
-            console.error("Error: ", obj, args);
-        };
-
-       /* back compat so jsonstatus can always be enabled */
-       function genRow(data) {
-           if (typeof(data[4]) === "string" && data[4].indexOf("{") > -1) {
-               var tmp = $.parseJSON(data[4]);
-               data[4] = tmp.oldStatus;
-               data[5] = null;
-           }
-           return data;
-       }
-
-       function genArray(obj) {
-           var data = obj.asArray();
-
-            for (var i in data) {
-               data[i] = genRow(data[i]);
-           }
-
-           return data;
-       }
-
-
-        la.onChange = function(obj, args) {
-            var index = 0;
-            var iserr = 0;
-
-            if (!dt) {
-                if (!config.aoColumns) {
-                    if (args.action != "init") {
-                        return;
-                    }
-
-                    config.aoColumns = [];
-
-                    for (var i in args.data) {
-                        config.aoColumns.push({
-                            "sTitle": args.data[i]
-                        });
-                    }
-                }
-
-                dt = jq.dataTable(config);
-            }
-
-            if (dt && (args.action == "del" || args.action == "modify")) {
-                index = args.index;
-
-                if (index === undefined && args.key) {
-                    index = la.indexOf(args.key);
-                }
-
-                if (index === undefined) {
-                    console.error("INVALID PACKET Missing INDEX\n", args);
-                    return;
-                }
-            }
-
-            if (config.onChange) {
-                config.onChange(obj, args);
-            }
-
-            try {
-                switch (args.action) {
-                case "bootObj":
-                    if (!args.data) {
-                        console.error("missing data");
-                        return;
-                    }
-                    dt.fnClearTable();
-                    dt.fnAddData(genArray(obj));
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "add":
-                    if (!args.data) {
-                        console.error("missing data");
-                        return;
-                    }
-                    if (args.redraw > -1) {
-                        // specific position, more costly
-                        dt.fnClearTable();
-                        dt.fnAddData(genArray(obj));
-                    } else {
-                        dt.fnAddData(genRow(args.data));
-                    }
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "modify":
-                    if (!args.data) {
-                        return;
-                    }
-                    //console.debug(args, index);
-                    dt.fnUpdate(genRow(args.data), index);
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "del":
-                    dt.fnDeleteRow(index);
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "clear":
-                    dt.fnClearTable();
-                    break;
-                case "reorder":
-                    // specific position, more costly
-                    dt.fnClearTable();
-                    dt.fnAddData(genArray(obj));
-                    break;
-                case "hide":
-                    jq.hide();
-                    break;
-
-                case "show":
-                    jq.show();
-                    break;
-
-                }
-            } catch(err) {
-                console.error("ERROR: " + err);
-                iserr++;
-            }
-
-            if (iserr) {
-                obj.errs++;
-                if (obj.errs < 3) {
-                    obj.bootstrap(obj.user_obj);
-                }
-            } else {
-                obj.errs = 0;
-            }
-
-        };
-
-        la.onChange(la, {
-            action: "init"
-        });
-
-    };
-
-    var CONFMAN_SERNO = 1;
-
-    /*
-        Conference Manager without jQuery table.
-     */
-
-    $.verto.conf = function(verto, params) {
-        var conf = this;
-
-        conf.params = $.extend({
-            dialog: null,
-            hasVid: false,
-            laData: null,
-            onBroadcast: null,
-            onLaChange: null,
-            onLaRow: null
-        }, params);
-
-        conf.verto = verto;
-        conf.serno = CONFMAN_SERNO++;
-
-        createMainModeratorMethods();
-
-        verto.subscribe(conf.params.laData.modChannel, {
-            handler: function(v, e) {
-                if (conf.params.onBroadcast) {
-                    conf.params.onBroadcast(verto, conf, e.data);
-                }
-            }
-        });
-
-        verto.subscribe(conf.params.laData.infoChannel, {
-            handler: function(v, e) {
-                if (typeof(conf.params.infoCallback) === "function") {
-                    conf.params.infoCallback(v,e);
-                }
-            }
-        });
-
-        verto.subscribe(conf.params.laData.chatChannel, {
-            handler: function(v, e) {
-                if (typeof(conf.params.chatCallback) === "function") {
-                    conf.params.chatCallback(v,e);
-                }
-            }
-        });
-    };
-
-    $.verto.conf.prototype.modCommand = function(cmd, id, value) {
-        var conf = this;
-
-        conf.verto.rpcClient.call("verto.broadcast", {
-            "eventChannel": conf.params.laData.modChannel,
-            "data": {
-                "application": "conf-control",
-                "command": cmd,
-                "id": id,
-                "value": value
-            }
-        });
-    };
-
-    $.verto.conf.prototype.destroy = function() {
-        var conf = this;
-
-        conf.destroyed = true;
-        conf.params.onBroadcast(conf.verto, conf, 'destroy');
-
-        if (conf.params.laData.modChannel) {
-            conf.verto.unsubscribe(conf.params.laData.modChannel);
-        }
-
-        if (conf.params.laData.chatChannel) {
-            conf.verto.unsubscribe(conf.params.laData.chatChannel);
-        }
-
-        if (conf.params.laData.infoChannel) {
-            conf.verto.unsubscribe(conf.params.laData.infoChannel);
-        }
-    };
-
-    function createMainModeratorMethods() {
-        $.verto.conf.prototype.listVideoLayouts = function() {
-            this.modCommand("list-videoLayouts", null, null);
-        };
-
-        $.verto.conf.prototype.play = function(file) {
-            this.modCommand("play", null, file);
-        };
-
-        $.verto.conf.prototype.stop = function() {
-            this.modCommand("stop", null, "all");
-        };
-
-        $.verto.conf.prototype.deaf = function(memberID) {
-            this.modCommand("deaf", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.undeaf = function(memberID) {
-            this.modCommand("undeaf", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.record = function(file) {
-            this.modCommand("recording", null, ["start", file]);
-        };
-
-        $.verto.conf.prototype.stopRecord = function() {
-            this.modCommand("recording", null, ["stop", "all"]);
-        };
-
-        $.verto.conf.prototype.snapshot = function(file) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-write-png", null, file);
-        };
-
-        $.verto.conf.prototype.setVideoLayout = function(layout, canvasID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-           if (canvasID) {
-               this.modCommand("vid-layout", null, [layout, canvasID]);
-           } else {
-               this.modCommand("vid-layout", null, layout);
-           }
-        };
-
-        $.verto.conf.prototype.kick = function(memberID) {
-            this.modCommand("kick", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.muteMic = function(memberID) {
-            this.modCommand("tmute", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.muteVideo = function(memberID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("tvmute", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.presenter = function(memberID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-res-id", parseInt(memberID), "presenter");
-        };
-
-        $.verto.conf.prototype.videoFloor = function(memberID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-floor", parseInt(memberID), "force");
-        };
-
-        $.verto.conf.prototype.banner = function(memberID, text) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-banner", parseInt(memberID), escape(text));
-        };
-
-        $.verto.conf.prototype.volumeDown = function(memberID) {
-            this.modCommand("volume_out", parseInt(memberID), "down");
-        };
-
-        $.verto.conf.prototype.volumeUp = function(memberID) {
-            this.modCommand("volume_out", parseInt(memberID), "up");
-        };
-
-        $.verto.conf.prototype.gainDown = function(memberID) {
-            this.modCommand("volume_in", parseInt(memberID), "down");
-        };
-
-        $.verto.conf.prototype.gainUp = function(memberID) {
-            this.modCommand("volume_in", parseInt(memberID), "up");
-        };
-
-        $.verto.conf.prototype.transfer = function(memberID, exten) {
-            this.modCommand("transfer", parseInt(memberID), exten);
-        };
-
-        $.verto.conf.prototype.sendChat = function(message, type) {
-            var conf = this;
-            conf.verto.rpcClient.call("verto.broadcast", {
-                "eventChannel": conf.params.laData.chatChannel,
-                "data": {
-                    "action": "send",
-                    "message": message,
-                    "type": type
-                }
-            });
-        };
-            
-    }
-
-    $.verto.modfuncs = {};
-
-    $.verto.confMan = function(verto, params) {
-        var confMan = this;
-
-        confMan.params = $.extend({
-            tableID: null,
-            statusID: null,
-            mainModID: null,
-            dialog: null,
-            hasVid: false,
-            laData: null,
-            onBroadcast: null,
-            onLaChange: null,
-            onLaRow: null
-        }, params);
-
-        confMan.verto = verto;
-        confMan.serno = CONFMAN_SERNO++;
-       confMan.canvasCount = confMan.params.laData.canvasCount;
-       
-        function genMainMod(jq) {
-            var play_id = "play_" + confMan.serno;
-            var stop_id = "stop_" + confMan.serno;
-            var recording_id = "recording_" + confMan.serno;
-            var snapshot_id = "snapshot_" + confMan.serno;
-            var rec_stop_id = "recording_stop" + confMan.serno;
-            var div_id = "confman_" + confMan.serno;
-
-            var html =  "<div id='" + div_id + "'><br>" +
-               "<button class='ctlbtn' id='" + play_id + "'>Play</button>" +
-               "<button class='ctlbtn' id='" + stop_id + "'>Stop</button>" +
-               "<button class='ctlbtn' id='" + recording_id + "'>Record</button>" +
-               "<button class='ctlbtn' id='" + rec_stop_id + "'>Record Stop</button>" +
-               (confMan.params.hasVid ? "<button class='ctlbtn' id='" + snapshot_id + "'>PNG Snapshot</button>" : "") +
-               "<br><br></div>";
-
-            jq.html(html);
-
-           $.verto.modfuncs.change_video_layout = function(id, canvas_id) {        
-               var val = $("#" + id + " option:selected").text();
-               if (val !== "none") {
-                    confMan.modCommand("vid-layout", null, [val, canvas_id]);
-               }
-           };
-
-           if (confMan.params.hasVid) {
-               for (var j = 0; j < confMan.canvasCount; j++) {
-                   var vlayout_id = "confman_vid_layout_" + j + "_" + confMan.serno;
-                   var vlselect_id = "confman_vl_select_" + j + "_" + confMan.serno;
-               
-
-                   var vlhtml =  "<div id='" + vlayout_id + "'><br>" +
-                       "<b>Video Layout Canvas " + (j+1) + 
-                       "</b> <select onChange='$.verto.modfuncs.change_video_layout(\"" + vlayout_id + "\", \"" + (j+1) + "\")' id='" + vlselect_id + "'></select> " +
-                       "<br><br></div>";
-                   jq.append(vlhtml);
-               }
-
-               $("#" + snapshot_id).click(function() {
-                    var file = prompt("Please enter file name", "");
-                   if (file) {
-                       confMan.modCommand("vid-write-png", null, file);
-                   }
-               });
-           }
-
-            $("#" + play_id).click(function() {
-                var file = prompt("Please enter file name", "");
-               if (file) {
-                    confMan.modCommand("play", null, file);
-               }
-            });
-
-            $("#" + stop_id).click(function() {
-                confMan.modCommand("stop", null, "all");
-            });
-
-            $("#" + recording_id).click(function() {
-                var file = prompt("Please enter file name", "");
-               if (file) {
-                    confMan.modCommand("recording", null, ["start", file]);
-               }
-            });
-
-            $("#" + rec_stop_id).click(function() {
-                confMan.modCommand("recording", null, ["stop", "all"]);
-            });
-
-        }
-
-        function genControls(jq, rowid) {
-            var x = parseInt(rowid);
-            var kick_id = "kick_" + x;
-            var canvas_in_next_id = "canvas_in_next_" + x;
-            var canvas_in_prev_id = "canvas_in_prev_" + x;
-            var canvas_out_next_id = "canvas_out_next_" + x;
-            var canvas_out_prev_id = "canvas_out_prev_" + x;
-
-            var canvas_in_set_id = "canvas_in_set_" + x;
-            var canvas_out_set_id = "canvas_out_set_" + x;
-
-            var layer_set_id = "layer_set_" + x;
-            var layer_next_id = "layer_next_" + x;
-            var layer_prev_id = "layer_prev_" + x;
-           
-            var tmute_id = "tmute_" + x;
-            var tvmute_id = "tvmute_" + x;
-            var vbanner_id = "vbanner_" + x;
-            var tvpresenter_id = "tvpresenter_" + x;
-            var tvfloor_id = "tvfloor_" + x;
-            var box_id = "box_" + x;
-            var gainup_id = "gain_in_up" + x;
-            var gaindn_id = "gain_in_dn" + x;
-            var volup_id = "vol_in_up" + x;
-            var voldn_id = "vol_in_dn" + x;
-            var transfer_id = "transfer" + x;
-           
-
-            var html = "<div id='" + box_id + "'>";
-
-           html += "<b>General Controls</b><hr noshade>";
-
-            html += "<button class='ctlbtn' id='" + kick_id + "'>Kick</button>" +
-                "<button class='ctlbtn' id='" + tmute_id + "'>Mute</button>" +
-                "<button class='ctlbtn' id='" + gainup_id + "'>Gain -</button>" +
-                "<button class='ctlbtn' id='" + gaindn_id + "'>Gain +</button>" +
-                "<button class='ctlbtn' id='" + voldn_id + "'>Vol -</button>" +
-                "<button class='ctlbtn' id='" + volup_id + "'>Vol +</button>" +
-                "<button class='ctlbtn' id='" + transfer_id + "'>Transfer</button>";
-               
-           if (confMan.params.hasVid) {
-               html += "<br><br><b>Video Controls</b><hr noshade>";
-
-
-                html += "<button class='ctlbtn' id='" + tvmute_id + "'>VMute</button>" +
-                    "<button class='ctlbtn' id='" + tvpresenter_id + "'>Presenter</button>" +
-                    "<button class='ctlbtn' id='" + tvfloor_id + "'>Vid Floor</button>" +
-                    "<button class='ctlbtn' id='" + vbanner_id + "'>Banner</button>";
-
-               if (confMan.canvasCount > 1) {
-                    html += "<br><br><b>Canvas Controls</b><hr noshade>" +
-                       "<button class='ctlbtn' id='" + canvas_in_set_id + "'>Set Input Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_in_prev_id + "'>Prev Input Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_in_next_id + "'>Next Input Canvas</button>" +
-                       
-                   "<br>" +
-                       
-                   "<button class='ctlbtn' id='" + canvas_out_set_id + "'>Set Watching Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_out_prev_id + "'>Prev Watching Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_out_next_id + "'>Next Watching Canvas</button>";
-               }
-               
-               html += "<br>" +
-
-                "<button class='ctlbtn' id='" + layer_set_id + "'>Set Layer</button>" +
-                    "<button class='ctlbtn' id='" + layer_prev_id + "'>Prev Layer</button>" +
-                    "<button class='ctlbtn' id='" + layer_next_id + "'>Next Layer</button>" +
-
-
-
-                    "</div>";
-            }
-
-            jq.html(html);
-
-
-            if (!jq.data("mouse")) {
-                $("#" + box_id).hide();
-            }
-
-            jq.mouseover(function(e) {
-                jq.data({"mouse": true});
-                $("#" + box_id).show();
-            });
-
-            jq.mouseout(function(e) {
-                jq.data({"mouse": false});
-                $("#" + box_id).hide();
-            });
-
-            $("#" + transfer_id).click(function() {
-                var xten = prompt("Enter Extension");
-               if (xten) {
-                    confMan.modCommand("transfer", x, xten);
-               }
-            });
-
-            $("#" + kick_id).click(function() {
-                confMan.modCommand("kick", x);
-            });
-
-
-            $("#" + layer_set_id).click(function() {
-                var cid = prompt("Please enter layer ID", "");
-               if (cid) {
-                    confMan.modCommand("vid-layer", x, cid);
-               }
-            });
-
-            $("#" + layer_next_id).click(function() {
-                confMan.modCommand("vid-layer", x, "next");
-            });
-            $("#" + layer_prev_id).click(function() {
-                confMan.modCommand("vid-layer", x, "prev");
-            });
-
-            $("#" + canvas_in_set_id).click(function() {
-                var cid = prompt("Please enter canvas ID", "");
-               if (cid) {
-                    confMan.modCommand("vid-canvas", x, cid);
-               }
-            });
-
-            $("#" + canvas_out_set_id).click(function() {
-                var cid = prompt("Please enter canvas ID", "");
-               if (cid) {
-                    confMan.modCommand("vid-watching-canvas", x, cid);
-               }
-            });
-
-            $("#" + canvas_in_next_id).click(function() {
-                confMan.modCommand("vid-canvas", x, "next");
-            });
-            $("#" + canvas_in_prev_id).click(function() {
-                confMan.modCommand("vid-canvas", x, "prev");
-            });
-
-
-            $("#" + canvas_out_next_id).click(function() {
-                confMan.modCommand("vid-watching-canvas", x, "next");
-            });
-            $("#" + canvas_out_prev_id).click(function() {
-                confMan.modCommand("vid-watching-canvas", x, "prev");
-            });
-           
-            $("#" + tmute_id).click(function() {
-                confMan.modCommand("tmute", x);
-            });
-
-           if (confMan.params.hasVid) {
-               $("#" + tvmute_id).click(function() {
-                    confMan.modCommand("tvmute", x);
-               });
-               $("#" + tvpresenter_id).click(function() {
-                    confMan.modCommand("vid-res-id", x, "presenter");
-               });
-               $("#" + tvfloor_id).click(function() {
-                    confMan.modCommand("vid-floor", x, "force");
-               });
-               $("#" + vbanner_id).click(function() {
-                    var text = prompt("Please enter text", "");
-                   if (text) {
-                       confMan.modCommand("vid-banner", x, escape(text));
-                   }
-               });
-           }
-
-            $("#" + gainup_id).click(function() {
-                confMan.modCommand("volume_in", x, "up");
-            });
-
-            $("#" + gaindn_id).click(function() {
-                confMan.modCommand("volume_in", x, "down");
-            });
-
-            $("#" + volup_id).click(function() {
-                confMan.modCommand("volume_out", x, "up");
-            });
-
-            $("#" + voldn_id).click(function() {
-                confMan.modCommand("volume_out", x, "down");
-            });
-
-            return html;
-        }
-
-        var atitle = "";
-        var awidth = 0;
-
-        //$(".jsDataTable").width(confMan.params.hasVid ? "900px" : "800px");
-
-        verto.subscribe(confMan.params.laData.infoChannel, {
-            handler: function(v, e) {
-                if (typeof(confMan.params.infoCallback) === "function") {
-                    confMan.params.infoCallback(v,e);
-                }
-            }
-        });
-
-       verto.subscribe(confMan.params.laData.chatChannel, {
-           handler: function(v, e) {
-               if (typeof(confMan.params.chatCallback) === "function") {
-                   confMan.params.chatCallback(v,e);
-               }
-           }
-       });
-
-        if (confMan.params.laData.role === "moderator") {
-            atitle = "Action";
-            awidth = 600;
-
-            if (confMan.params.mainModID) {
-                genMainMod($(confMan.params.mainModID));
-                $(confMan.params.displayID).html("Moderator Controls Ready<br><br>");
-            } else {
-                $(confMan.params.mainModID).html("");
-            }
-
-            verto.subscribe(confMan.params.laData.modChannel, {
-                handler: function(v, e) {
-                    //console.error("MODDATA:", e.data);
-                    if (confMan.params.onBroadcast) {
-                        confMan.params.onBroadcast(verto, confMan, e.data);
-                    }
-
-                   if (e.data["conf-command"] === "list-videoLayouts") {
-                       for (var j = 0; j < confMan.canvasCount; j++) {
-                           var vlselect_id = "#confman_vl_select_" + j + "_" + confMan.serno;
-                           var vlayout_id = "#confman_vid_layout_" + j + "_" + confMan.serno;
-                           
-                           var x = 0;
-                           var options;
-                           
-                           $(vlselect_id).selectmenu({});
-                           $(vlselect_id).selectmenu("enable");
-                           $(vlselect_id).empty();
-                           
-                           $(vlselect_id).append(new Option("Choose a Layout", "none"));
-
-                           if (e.data.responseData) {
-                               var rdata = [];
-
-                               for (var i in e.data.responseData) {
-                                   rdata.push(e.data.responseData[i].name);
-                               }
-                               
-                               options = rdata.sort(function(a, b) {
-                                   var ga = a.substring(0, 6) == "group:" ? true : false;
-                                   var gb = b.substring(0, 6) == "group:" ? true : false;
-                                   
-                                   if ((ga || gb) && ga != gb) {
-                                       return ga ? -1 : 1;
-                                   }
-                                   
-                                   return ( ( a == b ) ? 0 : ( ( a > b ) ? 1 : -1 ) );
-                               });
-
-                               for (var i in options) {
-                                   $(vlselect_id).append(new Option(options[i], options[i]));
-                                   x++;
-                               }
-                           }
-
-                           if (x) {
-                               $(vlselect_id).selectmenu('refresh', true);
-                           } else {
-                               $(vlayout_id).hide();
-                           }
-                       }
-                   } else {
-
-                       if (!confMan.destroyed && confMan.params.displayID) {
-                            $(confMan.params.displayID).html(e.data.response + "<br><br>");
-                            if (confMan.lastTimeout) {
-                               clearTimeout(confMan.lastTimeout);
-                               confMan.lastTimeout = 0;
-                            }
-                            confMan.lastTimeout = setTimeout(function() { $(confMan.params.displayID).html(confMan.destroyed ? "" : "Moderator Controls Ready<br><br>");}, 4000);
-                       }
-                   }
-                }
-            });
-
-
-           if (confMan.params.hasVid) {
-               confMan.modCommand("list-videoLayouts", null, null);
-           }
-        }
-
-        var row_callback = null;
-
-        if (confMan.params.laData.role === "moderator") {
-            row_callback = function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
-                if (!aData[5]) {
-                    var $row = $('td:eq(5)', nRow);
-                    genControls($row, aData);
-
-                    if (confMan.params.onLaRow) {
-                        confMan.params.onLaRow(verto, confMan, $row, aData);
-                    }
-                }
-            };
-        }
-
-        confMan.lt = new $.verto.liveTable(verto, confMan.params.laData.laChannel, confMan.params.laData.laName, $(confMan.params.tableID), {
-            subParams: {
-                callID: confMan.params.dialog ? confMan.params.dialog.callID : null
-            },
-
-            "onChange": function(obj, args) {
-                $(confMan.params.statusID).text("Conference Members: " + " (" + obj.arrayLen() + " Total)");
-                if (confMan.params.onLaChange) {
-                    confMan.params.onLaChange(verto, confMan, $.verto.enum.confEvent.laChange, obj, args);
-                }
-            },
-
-            "aaData": [],
-            "aoColumns": [
-                {
-                    "sTitle": "ID",
-                    "sWidth": "50"
-                },
-                {
-                    "sTitle": "Number",
-                   "sWidth": "250"
-                },
-                {
-                    "sTitle": "Name",
-                   "sWidth": "250"
-                },
-                {
-                    "sTitle": "Codec",
-                    "sWidth": "100"
-                },
-                {
-                    "sTitle": "Status",
-                    "sWidth": confMan.params.hasVid ? "200px" : "150px"
-                },
-                {
-                    "sTitle": atitle,
-                    "sWidth": awidth,
-                }
-            ],
-            "bAutoWidth": true,
-            "bDestroy": true,
-            "bSort": false,
-            "bInfo": false,
-            "bFilter": false,
-            "bLengthChange": false,
-            "bPaginate": false,
-            "iDisplayLength": 1400,
-
-            "oLanguage": {
-                "sEmptyTable": "The Conference is Empty....."
-            },
-
-            "fnRowCallback": row_callback
-
-        });
-    };
-
-    $.verto.confMan.prototype.modCommand = function(cmd, id, value) {
-        var confMan = this;
-
-        confMan.verto.rpcClient.call("verto.broadcast", {
-            "eventChannel": confMan.params.laData.modChannel,
-            "data": {
-               "application": "conf-control",
-               "command": cmd,
-               "id": id,
-               "value": value
-            }
-       });
-    };
-
-    $.verto.confMan.prototype.sendChat = function(message, type) {
-        var confMan = this;
-        confMan.verto.rpcClient.call("verto.broadcast", {
-            "eventChannel": confMan.params.laData.chatChannel,
-            "data": {
-               "action": "send",
-               "message": message,
-               "type": type
-            }
-       });
-    };
-
-
-    $.verto.confMan.prototype.destroy = function() {
-        var confMan = this;
-
-        confMan.destroyed = true;
-
-        if (confMan.lt) {
-            confMan.lt.destroy();
-        }
-
-        if (confMan.params.laData.chatChannel) {
-            confMan.verto.unsubscribe(confMan.params.laData.chatChannel);
-        }
-
-        if (confMan.params.laData.modChannel) {
-            confMan.verto.unsubscribe(confMan.params.laData.modChannel);
-        }
-
-        if (confMan.params.mainModID) {
-            $(confMan.params.mainModID).html("");
-        }
-    };
-
-    $.verto.dialog = function(direction, verto, params) {
-        var dialog = this;
-
-        dialog.params = $.extend({
-            useVideo: verto.options.useVideo,
-            useStereo: verto.options.useStereo,
-           screenShare: false,
-           useCamera: false,
-           useMic: verto.options.deviceParams.useMic,
-           useSpeak: verto.options.deviceParams.useSpeak,
-            tag: verto.options.tag,
-            localTag: verto.options.localTag,
-            login: verto.options.login,
-      videoParams: verto.options.videoParams,
-      useStream: verto.options.useStream,
-        }, params);
-       
-
-       if (!dialog.params.screenShare) {
-           dialog.params.useCamera = verto.options.deviceParams.useCamera;
-       }
-
-        dialog.verto = verto;
-        dialog.direction = direction;
-        dialog.lastState = null;
-        dialog.state = dialog.lastState = $.verto.enum.state.new;
-        dialog.callbacks = verto.callbacks;
-        dialog.answered = false;
-        dialog.attach = params.attach || false;
-       dialog.screenShare = params.screenShare || false;
-       dialog.useCamera = dialog.params.useCamera;
-       dialog.useMic = dialog.params.useMic;
-       dialog.useSpeak = dialog.params.useSpeak;
-       
-        if (dialog.params.callID) {
-            dialog.callID = dialog.params.callID;
-        } else {
-            dialog.callID = dialog.params.callID = generateGUID();
-        }
-
-       if (typeof(dialog.params.tag) === "function") {
-               dialog.params.tag = dialog.params.tag();
-       }
-       
-        if (dialog.params.tag) {
-            dialog.audioStream = document.getElementById(dialog.params.tag);
-
-            if (dialog.params.useVideo) {
-                dialog.videoStream = dialog.audioStream;
-            }
-        } //else conjure one TBD
-
-        if (dialog.params.localTag) {
-           dialog.localVideo = document.getElementById(dialog.params.localTag);
-       }
-
-        dialog.verto.dialogs[dialog.callID] = dialog;
-
-        var RTCcallbacks = {};
-
-        if (dialog.direction == $.verto.enum.direction.inbound) {
-            if (dialog.params.display_direction === "outbound") {
-                dialog.params.remote_caller_id_name = dialog.params.caller_id_name;
-                dialog.params.remote_caller_id_number = dialog.params.caller_id_number;
-            } else {
-                dialog.params.remote_caller_id_name = dialog.params.callee_id_name;
-                dialog.params.remote_caller_id_number = dialog.params.callee_id_number;
-            }
-
-            if (!dialog.params.remote_caller_id_name) {
-                dialog.params.remote_caller_id_name = "Nobody";
-            }
-
-            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 { //answer
-                dialog.setState($.verto.enum.state.answering);
-
-                dialog.sendMethod(dialog.attach ? "verto.attach" : "verto.answer", {
-                    sdp: dialog.rtc.mediaData.SDP
-                });
-            }
-        };
-
-        RTCcallbacks.onICE = function(rtc) {
-            //console.log("cand", rtc.mediaData.candidate);
-            if (rtc.type == "offer") {
-                console.log("offer", rtc.mediaData.candidate);
-                return;
-            }
-        };
-
-        RTCcallbacks.onStream = function(rtc, stream) {
-            if (dialog.callbacks.permissionCallback &&
-                typeof dialog.callbacks.permissionCallback.onGranted === 'function') {
-                dialog.callbacks.permissionCallback.onGranted(stream);
-            }
-            else if (dialog.verto.options.permissionCallback &&
-                typeof dialog.verto.options.permissionCallback.onGranted === 'function'){
-                dialog.verto.options.permissionCallback.onGranted(stream);
-            }
-            console.log("stream started");
-        };
-
-        RTCcallbacks.onRemoteStream = function(rtc, stream) {
-          if (typeof dialog.callbacks.onRemoteStream === 'function') {
-            dialog.callbacks.onRemoteStream(stream, dialog);
-          }
-          console.log("remote stream started");
-        };
-
-        RTCcallbacks.onError = function(e) {
-            if (dialog.callbacks.permissionCallback &&
-                typeof dialog.callbacks.permissionCallback.onDenied === 'function') {
-                dialog.callbacks.permissionCallback.onDenied();
-            }
-            else 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,
-      turnServer: verto.options.turnServer,
-      useStream: dialog.params.useStream
-        });
-
-        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 ((obj.noDialogParams && i != "callID")) {
-               continue;
-           }
-
-           obj.dialogParams[i] = dialog.params[i];
-        }
-       
-       delete obj.noDialogParams;
-
-        dialog.verto.rpcClient.call(method, obj,
-
-        function(e) {
-            /* Success */
-            dialog.processReply(method, true, e);
-        },
-
-        function(e) {
-            /* Error */
-            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;
-    }
-
-
-    // Attach audio output device to video element using device/sink ID.                                                                                           
-    function find_name(id) {
-       for (var i in $.verto.audioOutDevices) {
-           var source = $.verto.audioOutDevices[i];
-           if (source.id === id) {
-               return(source.label);
-           }
-       }
-
-       return id;
-    }
-
-    $.verto.dialog.prototype.setAudioPlaybackDevice = function(sinkId, callback, arg) {
-       var dialog = this;
-       var element = dialog.audioStream;
-
-       if (typeof element.sinkId !== 'undefined') {
-           var devname = find_name(sinkId);
-           console.info("Dialog: " + dialog.callID + " Setting speaker:", element, devname);
-
-           element.setSinkId(sinkId)
-               .then(function() {
-                   console.log("Dialog: " + dialog.callID + ' Success, audio output device attached: ' + sinkId);
-                   if (callback) {
-                       callback(true, devname, arg);
-                   }
-               })
-               .catch(function(error) {
-                   var errorMessage = error;
-                   if (error.name === 'SecurityError') {
-                       errorMessage = "Dialog: " + dialog.callID + ' You need to use HTTPS for selecting audio output ' +
-                           'device: ' + error;
-                   }
-                   if (callback) {
-                       callback(false, null, arg);
-                   }
-                   console.error(errorMessage);
-               });
-       } else {
-           console.warn("Dialog: " + dialog.callID + ' Browser does not support output device selection.');
-           if (callback) {
-               callback(false, null, arg);
-           }
-       }
-    }
-
-    $.verto.dialog.prototype.setState = function(state) {
-        var dialog = this;
-
-        if (dialog.state == $.verto.enum.state.ringing) {
-            dialog.stopRinging();
-        }
-
-        if (dialog.state == state || !checkStateChange(dialog.state, state)) {
-            console.error("Dialog " + dialog.callID + ": INVALID state change from " + dialog.state.name + " to " + state.name);
-            dialog.hangup();
-            return false;
-        }
-
-        console.log("Dialog " + dialog.callID + ": state change from " + dialog.state.name + " to " + state.name);
-
-        dialog.lastState = dialog.state;
-        dialog.state = state;
-
-        if (dialog.callbacks.onDialogState) {
-            dialog.callbacks.onDialogState(this);
-        }
-
-        switch (dialog.state) {
-
-        case $.verto.enum.state.early:
-        case $.verto.enum.state.active:
-
-           var speaker = dialog.useSpeak;
-           console.info("Using Speaker: ", speaker);
-
-           if (speaker && speaker !== "any" && speaker !== "none") {
-               setTimeout(function() {
-                   dialog.setAudioPlaybackDevice(speaker);
-               }, 500);
-           }
-
-           break;
-
-        case $.verto.enum.state.trying:
-            setTimeout(function() {
-                if (dialog.state == $.verto.enum.state.trying) {
-                    dialog.setState($.verto.enum.state.hangup);
-                }
-            }, 30000);
-            break;
-        case $.verto.enum.state.purge:
-            dialog.setState($.verto.enum.state.destroy);
-            break;
-        case $.verto.enum.state.hangup:
-
-            if (dialog.lastState.val > $.verto.enum.state.requesting.val && dialog.lastState.val < $.verto.enum.state.hangup.val) {
-                dialog.sendMethod("verto.bye", {});
-            }
-
-            dialog.setState($.verto.enum.state.destroy);
-            break;
-        case $.verto.enum.state.destroy:
-
-            if (typeof(dialog.verto.options.tag) === "function") {
-              $('#' + dialog.params.tag).remove();
-            }
-
-            delete dialog.verto.dialogs[dialog.callID];
-           if (dialog.params.screenShare) {
-               dialog.rtc.stopPeer();
-           } else {
-               dialog.rtc.stop();
-           }
-            break;
-        }
-
-        return true;
-    };
-
-    $.verto.dialog.prototype.processReply = function(method, success, e) {
-        var dialog = this;
-
-        //console.log("Response: " + method + " State:" + dialog.state.name, success, e);
-
-        switch (method) {
-
-        case "verto.answer":
-        case "verto.attach":
-            if (success) {
-                dialog.setState($.verto.enum.state.active);
-            } else {
-                dialog.hangup();
-            }
-            break;
-        case "verto.invite":
-            if (success) {
-                dialog.setState($.verto.enum.state.trying);
-            } else {
-                dialog.setState($.verto.enum.state.destroy);
-            }
-            break;
-
-        case "verto.bye":
-            dialog.hangup();
-            break;
-
-        case "verto.modify":
-            if (e.holdState) {
-                if (e.holdState == "held") {
-                    if (dialog.state != $.verto.enum.state.held) {
-                        dialog.setState($.verto.enum.state.held);
-                    }
-                } else if (e.holdState == "active") {
-                    if (dialog.state != $.verto.enum.state.active) {
-                        dialog.setState($.verto.enum.state.active);
-                    }
-                }
-            }
-
-            if (success) {}
-
-            break;
-
-        default:
-            break;
-        }
-
-    };
-
-    $.verto.dialog.prototype.hangup = function(params) {
-        var dialog = this;
-
-        if (params) {
-            if (params.causeCode) {
-               dialog.causeCode = params.causeCode;
-            }
-
-            if (params.cause) {
-               dialog.cause = params.cause;
-            }
-        }
-
-       if (!dialog.cause && !dialog.causeCode) {
-           dialog.cause = "NORMAL_CLEARING";
-       }
-
-        if (dialog.state.val >= $.verto.enum.state.new.val && dialog.state.val < $.verto.enum.state.hangup.val) {
-            dialog.setState($.verto.enum.state.hangup);
-        } else if (dialog.state.val < $.verto.enum.state.destroy) {
-            dialog.setState($.verto.enum.state.destroy);
-        }
-    };
-
-    $.verto.dialog.prototype.stopRinging = function() {
-        var dialog = this;
-        if (dialog.verto.ringer) {
-            dialog.verto.ringer.stop();
-        }
-    };
-
-    $.verto.dialog.prototype.indicateRing = function() {
-        var dialog = this;
-
-        if (dialog.verto.ringer) {
-            dialog.verto.ringer.attr("src", dialog.verto.options.ringFile)[0].play();
-
-            setTimeout(function() {
-                dialog.stopRinging();
-                if (dialog.state == $.verto.enum.state.ringing) {
-                    dialog.indicateRing();
-                }
-            },
-            dialog.verto.options.ringSleep);
-        }
-    };
-
-    $.verto.dialog.prototype.ring = function() {
-        var dialog = this;
-
-        dialog.setState($.verto.enum.state.ringing);
-        dialog.indicateRing();
-    };
-
-    $.verto.dialog.prototype.useVideo = function(on) {
-        var dialog = this;
-
-        dialog.params.useVideo = on;
-
-        if (on) {
-            dialog.videoStream = dialog.audioStream;
-        } else {
-            dialog.videoStream = null;
-        }
-
-        dialog.rtc.useVideo(dialog.videoStream, dialog.localVideo);
-
-    };
-
-    $.verto.dialog.prototype.setMute = function(what) {
-       var dialog = this;
-       return dialog.rtc.setMute(what);
-    };
-
-    $.verto.dialog.prototype.getMute = function() {
-       var dialog = this; 
-       return dialog.rtc.getMute();
-    };
-
-    $.verto.dialog.prototype.setVideoMute = function(what) {
-       var dialog = this;
-       return dialog.rtc.setVideoMute(what);
-    };
-
-    $.verto.dialog.prototype.getVideoMute = function() {
-       var dialog = this; 
-       return dialog.rtc.getVideoMute();
-    };
-
-    $.verto.dialog.prototype.useStereo = function(on) {
-        var dialog = this;
-
-        dialog.params.useStereo = on;
-        dialog.rtc.useStereo(on);
-    };
-
-    $.verto.dialog.prototype.dtmf = function(digits) {
-        var dialog = this;
-        if (digits) {
-            dialog.sendMethod("verto.info", {
-                dtmf: digits
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.rtt = function(obj) {
-        var dialog = this;
-       var pobj = {};
-
-       if (!obj) {
-           return false;
-       }
-
-       pobj.code = obj.code;
-       pobj.chars = obj.chars;
-
-
-        if (pobj.chars || pobj.code) {
-            dialog.sendMethod("verto.info", {
-                txt: obj,
-               noDialogParams: true
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.transfer = function(dest, params) {
-        var dialog = this;
-        if (dest) {
-            dialog.sendMethod("verto.modify", {
-                action: "transfer",
-                destination: dest,
-                params: params
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.replace = function(replaceCallID, params) {
-        var dialog = this;
-        if (replaceCallID) {
-            dialog.sendMethod("verto.modify", {
-                action: "replace",
-                replaceCallID: replaceCallID,
-                params: params
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.hold = function(params) {
-        var dialog = this;
-
-        dialog.sendMethod("verto.modify", {
-            action: "hold",
-            params: params
-        });
-    };
-
-    $.verto.dialog.prototype.unhold = function(params) {
-        var dialog = this;
-
-        dialog.sendMethod("verto.modify", {
-            action: "unhold",
-            params: params
-        });
-    };
-
-    $.verto.dialog.prototype.toggleHold = function(params) {
-        var dialog = this;
-
-        dialog.sendMethod("verto.modify", {
-            action: "toggleHold",
-            params: params
-        });
-    };
-
-    $.verto.dialog.prototype.message = function(msg) {
-        var dialog = this;
-        var err = 0;
-
-        msg.from = dialog.params.login;
-
-        if (!msg.to) {
-            console.error("Missing To");
-            err++;
-        }
-
-        if (!msg.body) {
-            console.error("Missing Body");
-            err++;
-        }
-
-        if (err) {
-            return false;
-        }
-
-        dialog.sendMethod("verto.info", {
-            msg: msg
-        });
-
-        return true;
-    };
-
-    $.verto.dialog.prototype.answer = function(params) {
-        var dialog = this;
-       
-        if (!dialog.answered) {
-           if (!params) {
-               params = {};
-           }
-
-           params.sdp = dialog.params.sdp;
-
-            if (params) {
-                if (params.useVideo) {
-                    dialog.useVideo(true);
-                }
-               dialog.params.callee_id_name = params.callee_id_name;
-               dialog.params.callee_id_number = params.callee_id_number;
-
-               if (params.useCamera) {
-                   dialog.useCamera = params.useCamera;
-               }
-
-               if (params.useMic) {
-                   dialog.useMic = params.useMic;
-               }
-
-               if (params.useSpeak) {
-                   dialog.useSpeak = params.useSpeak;
-               }
-            }
-           
-            dialog.rtc.createAnswer(params);
-            dialog.answered = true;
-        }
-    };
-
-    $.verto.dialog.prototype.handleAnswer = function(params) {
-        var dialog = this;
-
-        dialog.gotAnswer = true;
-
-        if (dialog.state.val >= $.verto.enum.state.active.val) {
-            return;
-        }
-
-        if (dialog.state.val >= $.verto.enum.state.early.val) {
-            dialog.setState($.verto.enum.state.active);
-        } else {
-            if (dialog.gotEarly) {
-                console.log("Dialog " + dialog.callID + " Got answer while still establishing early media, delaying...");
-            } else {
-                console.log("Dialog " + dialog.callID + " Answering Channel");
-                dialog.rtc.answer(params.sdp, function() {
-                    dialog.setState($.verto.enum.state.active);
-                }, function(e) {
-                    console.error(e);
-                    dialog.hangup();
-                });
-                console.log("Dialog " + dialog.callID + "ANSWER SDP", params.sdp);
-            }
-        }
-
-
-    };
-
-    $.verto.dialog.prototype.cidString = function(enc) {
-        var dialog = this;
-        var party = dialog.params.remote_caller_id_name + (enc ? " &lt;" : " <") + dialog.params.remote_caller_id_number + (enc ? "&gt;" : ">");
-        return party;
-    };
-
-    $.verto.dialog.prototype.sendMessage = function(msg, params) {
-        var dialog = this;
-
-        if (dialog.callbacks.onMessage) {
-            dialog.callbacks.onMessage(dialog.verto, dialog, msg, params);
-        }
-    };
-
-    $.verto.dialog.prototype.handleInfo = function(params) {
-        var dialog = this;
-
-        dialog.sendMessage($.verto.enum.message.info, params);
-
-    };
-
-    $.verto.dialog.prototype.handleDisplay = function(params) {
-        var dialog = this;
-
-        if (params.display_name) {
-            dialog.params.remote_caller_id_name = params.display_name;
-        }
-        if (params.display_number) {
-            dialog.params.remote_caller_id_number = params.display_number;
-        }
-
-        dialog.sendMessage($.verto.enum.message.display, {});
-    };
-
-    $.verto.dialog.prototype.handleMedia = function(params) {
-        var dialog = this;
-
-        if (dialog.state.val >= $.verto.enum.state.early.val) {
-            return;
-        }
-
-        dialog.gotEarly = true;
-
-        dialog.rtc.answer(params.sdp, function() {
-            console.log("Dialog " + dialog.callID + "Establishing early media");
-            dialog.setState($.verto.enum.state.early);
-
-            if (dialog.gotAnswer) {
-                console.log("Dialog " + dialog.callID + "Answering Channel");
-                dialog.setState($.verto.enum.state.active);
-            }
-        }, function(e) {
-            console.error(e);
-            dialog.hangup();
-        });
-        console.log("Dialog " + dialog.callID + "EARLY SDP", params.sdp);
-    };
-
-    $.verto.ENUM = function(s) {
-        var i = 0,
-        o = {};
-        s.split(" ").map(function(x) {
-            o[x] = {
-                name: x,
-                val: i++
-            };
-        });
-        return Object.freeze(o);
-    };
-
-    $.verto.enum = {};
-
-    $.verto.enum.states = Object.freeze({
-        new: {
-            requesting: 1,
-            recovering: 1,
-            ringing: 1,
-            destroy: 1,
-            answering: 1,
-           hangup: 1
-        },
-        requesting: {
-            trying: 1,
-            hangup: 1,
-           active: 1
-        },
-        recovering: {
-            answering: 1,
-            hangup: 1
-        },
-        trying: {
-            active: 1,
-            early: 1,
-            hangup: 1
-        },
-        ringing: {
-            answering: 1,
-            hangup: 1
-        },
-        answering: {
-            active: 1,
-            hangup: 1
-        },
-        active: {
-            answering: 1,
-            requesting: 1,
-            hangup: 1,
-            held: 1
-        },
-        held: {
-            hangup: 1,
-            active: 1
-        },
-        early: {
-            hangup: 1,
-            active: 1
-        },
-        hangup: {
-            destroy: 1
-        },
-        destroy: {},
-        purge: {
-            destroy: 1
-        }
-    });
-
-    $.verto.enum.state = $.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge");
-    $.verto.enum.direction = $.verto.ENUM("inbound outbound");
-    $.verto.enum.message = $.verto.ENUM("display info pvtEvent clientReady");
-
-    $.verto.enum = Object.freeze($.verto.enum);
-
-    $.verto.saved = [];
-    
-    $.verto.unloadJobs = [];
-
-    var unloadEventName = 'beforeunload';
-    // Hacks for Mobile Safari
-    var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
-    if (iOS) {
-      unloadEventName = 'pagehide';
-    }
-
-    $(window).bind(unloadEventName, function() {
-        for (var f in $.verto.unloadJobs) {
-          $.verto.unloadJobs[f]();
-        }
-
-        if ($.verto.haltClosure)
-          return $.verto.haltClosure();
-
-        for (var i in $.verto.saved) {
-            var verto = $.verto.saved[i];
-            if (verto) {
-                verto.purge();
-                verto.logout();
-            }
-        }
-
-        return $.verto.warnOnUnload;
-    });
-
-    $.verto.videoDevices = [];
-    $.verto.audioInDevices = [];
-    $.verto.audioOutDevices = [];
-
-    var checkDevices = function(runtime) {
-       console.info("enumerating devices");
-       var aud_in = [], aud_out = [], vid = [];        
-       var has_video = 0, has_audio = 0;
-       var Xstream;
-
-       function gotDevices(deviceInfos) {
-             // Handles being called several times to update labels. Preserve values.
-           for (var i = 0; i !== deviceInfos.length; ++i) {
-               var deviceInfo = deviceInfos[i];
-               var text = "";
-
-               console.log(deviceInfo);
-               console.log(deviceInfo.kind + ": " + deviceInfo.label + " id = " + deviceInfo.deviceId);
-
-               if (deviceInfo.kind === 'audioinput') {
-                   text = deviceInfo.label || 'microphone ' + (aud_in.length + 1);
-                   aud_in.push({id: deviceInfo.deviceId, kind: "audio_in", label: text});
-               } else if (deviceInfo.kind === 'audiooutput') {
-                   text = deviceInfo.label || 'speaker ' + (aud_out.length + 1);
-                   aud_out.push({id: deviceInfo.deviceId, kind: "audio_out", label: text});
-               } else if (deviceInfo.kind === 'videoinput') {
-                   text = deviceInfo.label || 'camera ' + (vid.length + 1);
-                   vid.push({id: deviceInfo.deviceId, kind: "video", label: text});
-               } else {
-                   console.log('Some other kind of source/device: ', deviceInfo);
-               }
-           }
-
-
-           $.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);
-
-           if (Xstream) {
-               Xstream.getTracks().forEach(function(track) {track.stop();});
-           }
-
-           if (runtime) {
-               runtime(true);
-           }
-       }
-
-
-       
-
-       function handleError(error) {
-           console.log('device enumeration error: ', error);
-           if (runtime) runtime(false);
-       }
-
-
-       function checkTypes(devs) {
-           for (var i = 0; i !== devs.length; ++i) {
-
-               if (devs[i].kind === 'audioinput') {
-                   has_audio++;
-               } else if (devs[i].kind === 'videoinput') {
-                   has_video++;
-               }
-           }
-
-           navigator.getUserMedia({ audio: (has_audio > 0 ? true : false), video: (has_video > 0 ? true : false)},
-                                  function(stream) {
-                                      Xstream = stream;
-                                      navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
-                                  },
-                                  function(err) {
-                                      console.log("The following error occurred: " + err.name);
-                                  }
-                                 );
-       }
-
-       navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);
-
-  };
-
-    $.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);
-(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){
-/*
- *  Copyright (c) 2017 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');
-
-function writeMediaSection(transceiver, caps, type, stream, dtlsRole) {
-  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' : dtlsRole || '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) {
-    // spec.
-    var msid = 'msid:' + stream.id + ' ' +
-        transceiver.rtpSender.track.id + '\r\n';
-    sdp += 'a=' + msid;
-
-    // for Chrome.
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-        ' ' + msid;
-    if (transceiver.sendEncodingParameters[0].rtx) {
-      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-          ' ' + msid;
-      sdp += 'a=ssrc-group:FID ' +
-          transceiver.sendEncodingParameters[0].ssrc + ' ' +
-          transceiver.sendEncodingParameters[0].rtx.ssrc +
-          '\r\n';
-    }
-  }
-  // FIXME: this should be written by writeRtpDescription.
-  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-      ' cname:' + SDPUtils.localCName + '\r\n';
-  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-        ' cname:' + SDPUtils.localCName + '\r\n';
-  }
-  return sdp;
-}
-
-// Edge does not like
-// 1) stun: filtered after 14393 unless ?transport=udp is present
-// 2) turn: that does not have all of turn:host:port?transport=udp
-// 3) turn: with ipv6 addresses
-// 4) turn: occurring muliple times
-function filterIceServers(iceServers, edgeVersion) {
-  var hasTurn = false;
-  iceServers = JSON.parse(JSON.stringify(iceServers));
-  return iceServers.filter(function(server) {
-    if (server && (server.urls || server.url)) {
-      var urls = server.urls || server.url;
-      if (server.url && !server.urls) {
-        console.warn('RTCIceServer.url is deprecated! Use urls instead.');
-      }
-      var isString = typeof urls === 'string';
-      if (isString) {
-        urls = [urls];
-      }
-      urls = urls.filter(function(url) {
-        var validTurn = url.indexOf('turn:') === 0 &&
-            url.indexOf('transport=udp') !== -1 &&
-            url.indexOf('turn:[') === -1 &&
-            !hasTurn;
-
-        if (validTurn) {
-          hasTurn = true;
-          return true;
-        }
-        return url.indexOf('stun:') === 0 && edgeVersion >= 14393 &&
-            url.indexOf('?transport=udp') === -1;
-      });
-
-      delete server.url;
-      server.urls = isString ? urls[0] : urls;
-      return !!urls.length;
-    }
-  });
-}
-
-// Determines the intersection of local and remote capabilities.
-function getCommonCapabilities(localCapabilities, remoteCapabilities) {
-  var commonCapabilities = {
-    codecs: [],
-    headerExtensions: [],
-    fecMechanisms: []
-  };
-
-  var findCodecByPayloadType = function(pt, codecs) {
-    pt = parseInt(pt, 10);
-    for (var i = 0; i < codecs.length; i++) {
-      if (codecs[i].payloadType === pt ||
-          codecs[i].preferredPayloadType === pt) {
-        return codecs[i];
-      }
-    }
-  };
-
-  var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
-    var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
-    var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
-    return lCodec && rCodec &&
-        lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
-  };
-
-  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) {
-        if (lCodec.name.toLowerCase() === 'rtx' &&
-            lCodec.parameters && rCodec.parameters.apt) {
-          // for RTX we need to find the local rtx that has a apt
-          // which points to the same local codec as the remote one.
-          if (!rtxCapabilityMatches(lCodec, rCodec,
-              localCapabilities.codecs, remoteCapabilities.codecs)) {
-            continue;
-          }
-        }
-        rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
-        // number of channels is the highest common number of channels
-        rCodec.numChannels = Math.min(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;
-}
-
-// is action=setLocalDescription with type allowed in signalingState
-function isActionAllowedInSignalingState(action, type, signalingState) {
-  return {
-    offer: {
-      setLocalDescription: ['stable', 'have-local-offer'],
-      setRemoteDescription: ['stable', 'have-remote-offer']
-    },
-    answer: {
-      setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
-      setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
-    }
-  }[type][action].indexOf(signalingState) !== -1;
-}
-
-function maybeAddCandidate(iceTransport, candidate) {
-  // Edge's internal representation adds some fields therefore
-  // not all fieldÑ• are taken into account.
-  var alreadyAdded = iceTransport.getRemoteCandidates()
-      .find(function(remoteCandidate) {
-        return candidate.foundation === remoteCandidate.foundation &&
-            candidate.ip === remoteCandidate.ip &&
-            candidate.port === remoteCandidate.port &&
-            candidate.priority === remoteCandidate.priority &&
-            candidate.protocol === remoteCandidate.protocol &&
-            candidate.type === remoteCandidate.type;
-      });
-  if (!alreadyAdded) {
-    iceTransport.addRemoteCandidate(candidate);
-  }
-  return !alreadyAdded;
-}
-
-
-// https://w3c.github.io/mediacapture-main/#mediastream
-// Helper function to add the track to the stream and
-// dispatch the event ourselves.
-function addTrackToStreamAndFireEvent(track, stream) {
-  stream.addTrack(track);
-  var e = new Event('addtrack'); // TODO: MediaStreamTrackEvent
-  e.track = track;
-  stream.dispatchEvent(e);
-}
-
-function removeTrackFromStreamAndFireEvent(track, stream) {
-  stream.removeTrack(track);
-  var e = new Event('removetrack'); // TODO: MediaStreamTrackEvent
-  e.track = track;
-  stream.dispatchEvent(e);
-}
-
-function fireAddTrack(pc, track, receiver, streams) {
-  var trackEvent = new Event('track');
-  trackEvent.track = track;
-  trackEvent.receiver = receiver;
-  trackEvent.transceiver = {receiver: receiver};
-  trackEvent.streams = streams;
-  window.setTimeout(function() {
-    pc._dispatchEvent('track', trackEvent);
-  });
-}
-
-function makeError(name, description) {
-  var e = new Error(description);
-  e.name = name;
-  return e;
-}
-
-module.exports = function(window, edgeVersion) {
-  var RTCPeerConnection = function(config) {
-    var pc = this;
-
-    var _eventTarget = document.createDocumentFragment();
-    ['addEventListener', 'removeEventListener', 'dispatchEvent']
-        .forEach(function(method) {
-          pc[method] = _eventTarget[method].bind(_eventTarget);
-        });
-
-    this.canTrickleIceCandidates = null;
-
-    this.needNegotiation = false;
-
-    this.localStreams = [];
-    this.remoteStreams = [];
-
-    this.localDescription = null;
-    this.remoteDescription = null;
-
-    this.signalingState = 'stable';
-    this.iceConnectionState = 'new';
-    this.iceGatheringState = 'new';
-
-    config = JSON.parse(JSON.stringify(config || {}));
-
-    this.usingBundle = config.bundlePolicy === 'max-bundle';
-    if (config.rtcpMuxPolicy === 'negotiate') {
-      throw(makeError('NotSupportedError',
-          'rtcpMuxPolicy \'negotiate\' is not supported'));
-    } else if (!config.rtcpMuxPolicy) {
-      config.rtcpMuxPolicy = 'require';
-    }
-
-    switch (config.iceTransportPolicy) {
-      case 'all':
-      case 'relay':
-        break;
-      default:
-        config.iceTransportPolicy = 'all';
-        break;
-    }
-
-    switch (config.bundlePolicy) {
-      case 'balanced':
-      case 'max-compat':
-      case 'max-bundle':
-        break;
-      default:
-        config.bundlePolicy = 'balanced';
-        break;
-    }
-
-    config.iceServers = filterIceServers(config.iceServers || [], edgeVersion);
-
-    this._iceGatherers = [];
-    if (config.iceCandidatePoolSize) {
-      for (var i = config.iceCandidatePoolSize; i > 0; i--) {
-        this._iceGatherers = new window.RTCIceGatherer({
-          iceServers: config.iceServers,
-          gatherPolicy: config.iceTransportPolicy
-        });
-      }
-    } else {
-      config.iceCandidatePoolSize = 0;
-    }
-
-    this._config = config;
-
-    // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
-    // everything that is needed to describe a SDP m-line.
-    this.transceivers = [];
-
-    this._sdpSessionId = SDPUtils.generateSessionId();
-    this._sdpSessionVersion = 0;
-
-    this._dtlsRole = undefined; // role for a=setup to use in answers.
-
-    this._isClosed = false;
-  };
-
-  // set up event handlers on prototype
-  RTCPeerConnection.prototype.onicecandidate = null;
-  RTCPeerConnection.prototype.onaddstream = null;
-  RTCPeerConnection.prototype.ontrack = null;
-  RTCPeerConnection.prototype.onremovestream = null;
-  RTCPeerConnection.prototype.onsignalingstatechange = null;
-  RTCPeerConnection.prototype.oniceconnectionstatechange = null;
-  RTCPeerConnection.prototype.onicegatheringstatechange = null;
-  RTCPeerConnection.prototype.onnegotiationneeded = null;
-  RTCPeerConnection.prototype.ondatachannel = null;
-
-  RTCPeerConnection.prototype._dispatchEvent = function(name, event) {
-    if (this._isClosed) {
-      return;
-    }
-    this.dispatchEvent(event);
-    if (typeof this['on' + name] === 'function') {
-      this['on' + name](event);
-    }
-  };
-
-  RTCPeerConnection.prototype._emitGatheringStateChange = function() {
-    var event = new Event('icegatheringstatechange');
-    this._dispatchEvent('icegatheringstatechange', event);
-  };
-
-  RTCPeerConnection.prototype.getConfiguration = function() {
-    return this._config;
-  };
-
-  RTCPeerConnection.prototype.getLocalStreams = function() {
-    return this.localStreams;
-  };
-
-  RTCPeerConnection.prototype.getRemoteStreams = function() {
-    return this.remoteStreams;
-  };
-
-  // internal helper to create a transceiver object.
-  // (whih is not yet the same as the WebRTC 1.0 transceiver)
-  RTCPeerConnection.prototype._createTransceiver = function(kind) {
-    var hasBundleTransport = this.transceivers.length > 0;
-    var transceiver = {
-      track: null,
-      iceGatherer: null,
-      iceTransport: null,
-      dtlsTransport: null,
-      localCapabilities: null,
-      remoteCapabilities: null,
-      rtpSender: null,
-      rtpReceiver: null,
-      kind: kind,
-      mid: null,
-      sendEncodingParameters: null,
-      recvEncodingParameters: null,
-      stream: null,
-      associatedRemoteMediaStreams: [],
-      wantReceive: true
-    };
-    if (this.usingBundle && hasBundleTransport) {
-      transceiver.iceTransport = this.transceivers[0].iceTransport;
-      transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
-    } else {
-      var transports = this._createIceAndDtlsTransports();
-      transceiver.iceTransport = transports.iceTransport;
-      transceiver.dtlsTransport = transports.dtlsTransport;
-    }
-    this.transceivers.push(transceiver);
-    return transceiver;
-  };
-
-  RTCPeerConnection.prototype.addTrack = function(track, stream) {
-    var alreadyExists = this.transceivers.find(function(s) {
-      return s.track === track;
-    });
-
-    if (alreadyExists) {
-      throw makeError('InvalidAccessError', 'Track already exists.');
-    }
-
-    if (this.signalingState === 'closed') {
-      throw makeError('InvalidStateError',
-          'Attempted to call addTrack on a closed peerconnection.');
-    }
-
-    var transceiver;
-    for (var i = 0; i < this.transceivers.length; i++) {
-      if (!this.transceivers[i].track &&
-          this.transceivers[i].kind === track.kind) {
-        transceiver = this.transceivers[i];
-      }
-    }
-    if (!transceiver) {
-      transceiver = this._createTransceiver(track.kind);
-    }
-
-    this._maybeFireNegotiationNeeded();
-
-    if (this.localStreams.indexOf(stream) === -1) {
-      this.localStreams.push(stream);
-    }
-
-    transceiver.track = track;
-    transceiver.stream = stream;
-    transceiver.rtpSender = new window.RTCRtpSender(track,
-        transceiver.dtlsTransport);
-    return transceiver.rtpSender;
-  };
-
-  RTCPeerConnection.prototype.addStream = function(stream) {
-    var pc = this;
-    if (edgeVersion >= 15025) {
-      stream.getTracks().forEach(function(track) {
-        pc.addTrack(track, stream);
-      });
-    } else {
-      // Clone is necessary for local demos mostly, attaching directly
-      // to two different senders does not work (build 10547).
-      // Fixed in 15025 (or earlier)
-      var clonedStream = stream.clone();
-      stream.getTracks().forEach(function(track, idx) {
-        var clonedTrack = clonedStream.getTracks()[idx];
-        track.addEventListener('enabled', function(event) {
-          clonedTrack.enabled = event.enabled;
-        });
-      });
-      clonedStream.getTracks().forEach(function(track) {
-        pc.addTrack(track, clonedStream);
-      });
-    }
-  };
-
-  RTCPeerConnection.prototype.removeTrack = function(sender) {
-    if (!(sender instanceof window.RTCRtpSender)) {
-      throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' +
-          'does not implement interface RTCRtpSender.');
-    }
-
-    var transceiver = this.transceivers.find(function(t) {
-      return t.rtpSender === sender;
-    });
-
-    if (!transceiver) {
-      throw makeError('InvalidAccessError',
-          'Sender was not created by this connection.');
-    }
-    var stream = transceiver.stream;
-
-    transceiver.rtpSender.stop();
-    transceiver.rtpSender = null;
-    transceiver.track = null;
-    transceiver.stream = null;
-
-    // remove the stream from the set of local streams
-    var localStreams = this.transceivers.map(function(t) {
-      return t.stream;
-    });
-    if (localStreams.indexOf(stream) === -1 &&
-        this.localStreams.indexOf(stream) > -1) {
-      this.localStreams.splice(this.localStreams.indexOf(stream), 1);
-    }
-
-    this._maybeFireNegotiationNeeded();
-  };
-
-  RTCPeerConnection.prototype.removeStream = function(stream) {
-    var pc = this;
-    stream.getTracks().forEach(function(track) {
-      var sender = pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-      if (sender) {
-        pc.removeTrack(sender);
-      }
-    });
-  };
-
-  RTCPeerConnection.prototype.getSenders = function() {
-    return this.transceivers.filter(function(transceiver) {
-      return !!transceiver.rtpSender;
-    })
-    .map(function(transceiver) {
-      return transceiver.rtpSender;
-    });
-  };
-
-  RTCPeerConnection.prototype.getReceivers = function() {
-    return this.transceivers.filter(function(transceiver) {
-      return !!transceiver.rtpReceiver;
-    })
-    .map(function(transceiver) {
-      return transceiver.rtpReceiver;
-    });
-  };
-
-
-  RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex,
-      usingBundle) {
-    var pc = this;
-    if (usingBundle && sdpMLineIndex > 0) {
-      return this.transceivers[0].iceGatherer;
-    } else if (this._iceGatherers.length) {
-      return this._iceGatherers.shift();
-    }
-    var iceGatherer = new window.RTCIceGatherer({
-      iceServers: this._config.iceServers,
-      gatherPolicy: this._config.iceTransportPolicy
-    });
-    Object.defineProperty(iceGatherer, 'state',
-        {value: 'new', writable: true}
-    );
-
-    this.transceivers[sdpMLineIndex].candidates = [];
-    this.transceivers[sdpMLineIndex].bufferCandidates = function(event) {
-      var end = !event.candidate || Object.keys(event.candidate).length === 0;
-      // polyfill since RTCIceGatherer.state is not implemented in
-      // Edge 10547 yet.
-      iceGatherer.state = end ? 'completed' : 'gathering';
-      if (pc.transceivers[sdpMLineIndex].candidates !== null) {
-        pc.transceivers[sdpMLineIndex].candidates.push(event.candidate);
-      }
-    };
-    iceGatherer.addEventListener('localcandidate',
-      this.transceivers[sdpMLineIndex].bufferCandidates);
-    return iceGatherer;
-  };
-
-  // start gathering from an RTCIceGatherer.
-  RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) {
-    var pc = this;
-    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
-    if (iceGatherer.onlocalcandidate) {
-      return;
-    }
-    var candidates = this.transceivers[sdpMLineIndex].candidates;
-    this.transceivers[sdpMLineIndex].candidates = null;
-    iceGatherer.removeEventListener('localcandidate',
-      this.transceivers[sdpMLineIndex].bufferCandidates);
-    iceGatherer.onlocalcandidate = function(evt) {
-      if (pc.usingBundle && sdpMLineIndex > 0) {
-        // if we know that we use bundle we can drop candidates with
-        // Ñ•dpMLineIndex > 0. If we don't do this then our state gets
-        // confused since we dispose the extra ice gatherer.
-        return;
-      }
-      var event = new Event('icecandidate');
-      event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
-
-      var cand = evt.candidate;
-      // Edge emits an empty object for RTCIceCandidateComplete‥
-      var end = !cand || Object.keys(cand).length === 0;
-      if (end) {
-        // polyfill since RTCIceGatherer.state is not implemented in
-        // Edge 10547 yet.
-        if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') {
-          iceGatherer.state = 'completed';
-        }
-      } else {
-        if (iceGatherer.state === 'new') {
-          iceGatherer.state = 'gathering';
-        }
-        // RTCIceCandidate doesn't have a component, needs to be added
-        cand.component = 1;
-        event.candidate.candidate = SDPUtils.writeCandidate(cand);
-      }
-
-      // update local description.
-      var sections = SDPUtils.splitSections(pc.localDescription.sdp);
-      if (!end) {
-        sections[event.candidate.sdpMLineIndex + 1] +=
-            'a=' + event.candidate.candidate + '\r\n';
-      } else {
-        sections[event.candidate.sdpMLineIndex + 1] +=
-            'a=end-of-candidates\r\n';
-      }
-      pc.localDescription.sdp = sections.join('');
-      var complete = pc.transceivers.every(function(transceiver) {
-        return transceiver.iceGatherer &&
-            transceiver.iceGatherer.state === 'completed';
-      });
-
-      if (pc.iceGatheringState !== 'gathering') {
-        pc.iceGatheringState = 'gathering';
-        pc._emitGatheringStateChange();
-      }
-
-      // Emit candidate. Also emit null candidate when all gatherers are
-      // complete.
-      if (!end) {
-        pc._dispatchEvent('icecandidate', event);
-      }
-      if (complete) {
-        pc._dispatchEvent('icecandidate', new Event('icecandidate'));
-        pc.iceGatheringState = 'complete';
-        pc._emitGatheringStateChange();
-      }
-    };
-
-    // emit already gathered candidates.
-    window.setTimeout(function() {
-      candidates.forEach(function(candidate) {
-        var e = new Event('RTCIceGatherEvent');
-        e.candidate = candidate;
-        iceGatherer.onlocalcandidate(e);
-      });
-    }, 0);
-  };
-
-  // Create ICE transport and DTLS transport.
-  RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
-    var pc = this;
-    var iceTransport = new window.RTCIceTransport(null);
-    iceTransport.onicestatechange = function() {
-      pc._updateConnectionState();
-    };
-
-    var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
-    dtlsTransport.ondtlsstatechange = function() {
-      pc._updateConnectionState();
-    };
-    dtlsTransport.onerror = function() {
-      // onerror does not set state to failed by itpc.
-      Object.defineProperty(dtlsTransport, 'state',
-          {value: 'failed', writable: true});
-      pc._updateConnectionState();
-    };
-
-    return {
-      iceTransport: iceTransport,
-      dtlsTransport: dtlsTransport
-    };
-  };
-
-  // Destroy ICE gatherer, ICE transport and DTLS transport.
-  // Without triggering the callbacks.
-  RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
-      sdpMLineIndex) {
-    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
-    if (iceGatherer) {
-      delete iceGatherer.onlocalcandidate;
-      delete this.transceivers[sdpMLineIndex].iceGatherer;
-    }
-    var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
-    if (iceTransport) {
-      delete iceTransport.onicestatechange;
-      delete this.transceivers[sdpMLineIndex].iceTransport;
-    }
-    var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
-    if (dtlsTransport) {
-      delete dtlsTransport.ondtlsstatechange;
-      delete dtlsTransport.onerror;
-      delete this.transceivers[sdpMLineIndex].dtlsTransport;
-    }
-  };
-
-  // Start the RTP Sender and Receiver for a transceiver.
-  RTCPeerConnection.prototype._transceive = function(transceiver,
-      send, recv) {
-    var params = getCommonCapabilities(transceiver.localCapabilities,
-        transceiver.remoteCapabilities);
-    if (send && transceiver.rtpSender) {
-      params.encodings = transceiver.sendEncodingParameters;
-      params.rtcp = {
-        cname: SDPUtils.localCName,
-        compound: transceiver.rtcpParameters.compound
-      };
-      if (transceiver.recvEncodingParameters.length) {
-        params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
-      }
-      transceiver.rtpSender.send(params);
-    }
-    if (recv && transceiver.rtpReceiver && params.codecs.length > 0) {
-      // remove RTX field in Edge 14942
-      if (transceiver.kind === 'video'
-          && transceiver.recvEncodingParameters
-          && edgeVersion < 15019) {
-        transceiver.recvEncodingParameters.forEach(function(p) {
-          delete p.rtx;
-        });
-      }
-      if (transceiver.recvEncodingParameters.length) {
-        params.encodings = transceiver.recvEncodingParameters;
-      }
-      params.rtcp = {
-        compound: transceiver.rtcpParameters.compound
-      };
-      if (transceiver.rtcpParameters.cname) {
-        params.rtcp.cname = transceiver.rtcpParameters.cname;
-      }
-      if (transceiver.sendEncodingParameters.length) {
-        params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
-      }
-      transceiver.rtpReceiver.receive(params);
-    }
-  };
-
-  RTCPeerConnection.prototype.setLocalDescription = function(description) {
-    var pc = this;
-
-    if (!isActionAllowedInSignalingState('setLocalDescription',
-        description.type, this.signalingState) || this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not set local ' + description.type +
-          ' in state ' + pc.signalingState));
-    }
-
-    var sections;
-    var sessionpart;
-    if (description.type === 'offer') {
-      // 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);
-        pc.transceivers[sdpMLineIndex].localCapabilities = caps;
-      });
-
-      this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-        pc._gather(transceiver.mid, sdpMLineIndex);
-      });
-    } else if (description.type === 'answer') {
-      sections = SDPUtils.splitSections(pc.remoteDescription.sdp);
-      sessionpart = sections.shift();
-      var isIceLite = SDPUtils.matchPrefix(sessionpart,
-          'a=ice-lite').length > 0;
-      sections.forEach(function(mediaSection, sdpMLineIndex) {
-        var transceiver = pc.transceivers[sdpMLineIndex];
-        var iceGatherer = transceiver.iceGatherer;
-        var iceTransport = transceiver.iceTransport;
-        var dtlsTransport = transceiver.dtlsTransport;
-        var localCapabilities = transceiver.localCapabilities;
-        var remoteCapabilities = transceiver.remoteCapabilities;
-
-        // treat bundle-only as not-rejected.
-        var rejected = SDPUtils.isRejected(mediaSection) &&
-            SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
-
-        if (!rejected && !transceiver.isDatachannel) {
-          var remoteIceParameters = SDPUtils.getIceParameters(
-              mediaSection, sessionpart);
-          var remoteDtlsParameters = SDPUtils.getDtlsParameters(
-              mediaSection, sessionpart);
-          if (isIceLite) {
-            remoteDtlsParameters.role = 'server';
-          }
-
-          if (!pc.usingBundle || sdpMLineIndex === 0) {
-            pc._gather(transceiver.mid, sdpMLineIndex);
-            if (iceTransport.state === 'new') {
-              iceTransport.start(iceGatherer, remoteIceParameters,
-                  isIceLite ? 'controlling' : 'controlled');
-            }
-            if (dtlsTransport.state === 'new') {
-              dtlsTransport.start(remoteDtlsParameters);
-            }
-          }
-
-          // Calculate intersection of capabilities.
-          var params = getCommonCapabilities(localCapabilities,
-              remoteCapabilities);
-
-          // Start the RTCRtpSender. The RTCRtpReceiver for this
-          // transceiver has already been started in setRemoteDescription.
-          pc._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 +
-            '"');
-    }
-
-    return Promise.resolve();
-  };
-
-  RTCPeerConnection.prototype.setRemoteDescription = function(description) {
-    var pc = this;
-
-    if (!isActionAllowedInSignalingState('setRemoteDescription',
-        description.type, this.signalingState) || this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not set remote ' + description.type +
-          ' in state ' + pc.signalingState));
-    }
-
-    var streams = {};
-    this.remoteStreams.forEach(function(stream) {
-      streams[stream.id] = stream;
-    });
-    var receiverList = [];
-    var sections = SDPUtils.splitSections(description.sdp);
-    var sessionpart = sections.shift();
-    var isIceLite = SDPUtils.matchPrefix(sessionpart,
-        'a=ice-lite').length > 0;
-    var usingBundle = SDPUtils.matchPrefix(sessionpart,
-        'a=group:BUNDLE ').length > 0;
-    this.usingBundle = usingBundle;
-    var iceOptions = SDPUtils.matchPrefix(sessionpart,
-        'a=ice-options:')[0];
-    if (iceOptions) {
-      this.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
-          .indexOf('trickle') >= 0;
-    } else {
-      this.canTrickleIceCandidates = false;
-    }
-
-    sections.forEach(function(mediaSection, sdpMLineIndex) {
-      var lines = SDPUtils.splitLines(mediaSection);
-      var kind = SDPUtils.getKind(mediaSection);
-      // treat bundle-only as not-rejected.
-      var rejected = SDPUtils.isRejected(mediaSection) &&
-          SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
-      var protocol = lines[0].substr(2).split(' ')[2];
-
-      var direction = SDPUtils.getDirection(mediaSection, sessionpart);
-      var remoteMsid = SDPUtils.parseMsid(mediaSection);
-
-      var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
-
-      // Reject datachannels which are not implemented yet.
-      if (kind === 'application' && protocol === 'DTLS/SCTP') {
-        pc.transceivers[sdpMLineIndex] = {
-          mid: mid,
-          isDatachannel: true
-        };
-        return;
-      }
-
-      var transceiver;
-      var iceGatherer;
-      var iceTransport;
-      var dtlsTransport;
-      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 rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
-
-      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;
-          });
-
-      // Check if we can use BUNDLE and dispose transports.
-      if ((description.type === 'offer' || description.type === 'answer') &&
-          !rejected && usingBundle && sdpMLineIndex > 0 &&
-          pc.transceivers[sdpMLineIndex]) {
-        pc._disposeIceAndDtlsTransports(sdpMLineIndex);
-        pc.transceivers[sdpMLineIndex].iceGatherer =
-            pc.transceivers[0].iceGatherer;
-        pc.transceivers[sdpMLineIndex].iceTransport =
-            pc.transceivers[0].iceTransport;
-        pc.transceivers[sdpMLineIndex].dtlsTransport =
-            pc.transceivers[0].dtlsTransport;
-        if (pc.transceivers[sdpMLineIndex].rtpSender) {
-          pc.transceivers[sdpMLineIndex].rtpSender.setTransport(
-              pc.transceivers[0].dtlsTransport);
-        }
-        if (pc.transceivers[sdpMLineIndex].rtpReceiver) {
-          pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
-              pc.transceivers[0].dtlsTransport);
-        }
-      }
-      if (description.type === 'offer' && !rejected) {
-        transceiver = pc.transceivers[sdpMLineIndex] ||
-            pc._createTransceiver(kind);
-        transceiver.mid = mid;
-
-        if (!transceiver.iceGatherer) {
-          transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
-              usingBundle);
-        }
-
-        if (cands.length && transceiver.iceTransport.state === 'new') {
-          if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {
-            transceiver.iceTransport.setRemoteCandidates(cands);
-          } else {
-            cands.forEach(function(candidate) {
-              maybeAddCandidate(transceiver.iceTransport, candidate);
-            });
-          }
-        }
-
-        localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
-
-        // filter RTX until additional stuff needed for RTX is implemented
-        // in adapter.js
-        if (edgeVersion < 15019) {
-          localCapabilities.codecs = localCapabilities.codecs.filter(
-              function(codec) {
-                return codec.name !== 'rtx';
-              });
-        }
-
-        sendEncodingParameters = transceiver.sendEncodingParameters || [{
-          ssrc: (2 * sdpMLineIndex + 2) * 1001
-        }];
-
-        // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
-        var isNewTrack = false;
-        if (direction === 'sendrecv' || direction === 'sendonly') {
-          isNewTrack = !transceiver.rtpReceiver;
-          rtpReceiver = transceiver.rtpReceiver ||
-              new window.RTCRtpReceiver(transceiver.dtlsTransport, kind);
-
-          if (isNewTrack) {
-            var stream;
-            track = rtpReceiver.track;
-            // FIXME: does not work with Plan B.
-            if (remoteMsid && remoteMsid.stream === '-') {
-              // no-op. a stream id of '-' means: no associated stream.
-            } else if (remoteMsid) {
-              if (!streams[remoteMsid.stream]) {
-                streams[remoteMsid.stream] = new window.MediaStream();
-                Object.defineProperty(streams[remoteMsid.stream], 'id', {
-                  get: function() {
-                    return remoteMsid.stream;
-                  }
-                });
-              }
-              Object.defineProperty(track, 'id', {
-                get: function() {
-                  return remoteMsid.track;
-                }
-              });
-              stream = streams[remoteMsid.stream];
-            } else {
-              if (!streams.default) {
-                streams.default = new window.MediaStream();
-              }
-              stream = streams.default;
-            }
-            if (stream) {
-              addTrackToStreamAndFireEvent(track, stream);
-              transceiver.associatedRemoteMediaStreams.push(stream);
-            }
-            receiverList.push([track, rtpReceiver, stream]);
-          }
-        } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) {
-          transceiver.associatedRemoteMediaStreams.forEach(function(s) {
-            var nativeTrack = s.getTracks().find(function(t) {
-              return t.id === transceiver.rtpReceiver.track.id;
-            });
-            if (nativeTrack) {
-              removeTrackFromStreamAndFireEvent(nativeTrack, s);
-            }
-          });
-          transceiver.associatedRemoteMediaStreams = [];
-        }
-
-        transceiver.localCapabilities = localCapabilities;
-        transceiver.remoteCapabilities = remoteCapabilities;
-        transceiver.rtpReceiver = rtpReceiver;
-        transceiver.rtcpParameters = rtcpParameters;
-        transceiver.sendEncodingParameters = sendEncodingParameters;
-        transceiver.recvEncodingParameters = recvEncodingParameters;
-
-        // Start the RTCRtpReceiver now. The RTPSender is started in
-        // setLocalDescription.
-        pc._transceive(pc.transceivers[sdpMLineIndex],
-            false,
-            isNewTrack);
-      } else if (description.type === 'answer' && !rejected) {
-        transceiver = pc.transceivers[sdpMLineIndex];
-        iceGatherer = transceiver.iceGatherer;
-        iceTransport = transceiver.iceTransport;
-        dtlsTransport = transceiver.dtlsTransport;
-        rtpReceiver = transceiver.rtpReceiver;
-        sendEncodingParameters = transceiver.sendEncodingParameters;
-        localCapabilities = transceiver.localCapabilities;
-
-        pc.transceivers[sdpMLineIndex].recvEncodingParameters =
-            recvEncodingParameters;
-        pc.transceivers[sdpMLineIndex].remoteCapabilities =
-            remoteCapabilities;
-        pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
-
-        if (cands.length && iceTransport.state === 'new') {
-          if ((isIceLite || isComplete) &&
-              (!usingBundle || sdpMLineIndex === 0)) {
-            iceTransport.setRemoteCandidates(cands);
-          } else {
-            cands.forEach(function(candidate) {
-              maybeAddCandidate(transceiver.iceTransport, candidate);
-            });
-          }
-        }
-
-        if (!usingBundle || sdpMLineIndex === 0) {
-          if (iceTransport.state === 'new') {
-            iceTransport.start(iceGatherer, remoteIceParameters,
-                'controlling');
-          }
-          if (dtlsTransport.state === 'new') {
-            dtlsTransport.start(remoteDtlsParameters);
-          }
-        }
-
-        pc._transceive(transceiver,
-            direction === 'sendrecv' || direction === 'recvonly',
-            direction === 'sendrecv' || direction === 'sendonly');
-
-        // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
-        if (rtpReceiver &&
-            (direction === 'sendrecv' || direction === 'sendonly')) {
-          track = rtpReceiver.track;
-          if (remoteMsid) {
-            if (!streams[remoteMsid.stream]) {
-              streams[remoteMsid.stream] = new window.MediaStream();
-            }
-            addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]);
-            receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
-          } else {
-            if (!streams.default) {
-              streams.default = new window.MediaStream();
-            }
-            addTrackToStreamAndFireEvent(track, streams.default);
-            receiverList.push([track, rtpReceiver, streams.default]);
-          }
-        } else {
-          // FIXME: actually the receiver should be created later.
-          delete transceiver.rtpReceiver;
-        }
-      }
-    });
-
-    if (this._dtlsRole === undefined) {
-      this._dtlsRole = description.type === 'offer' ? 'active' : 'passive';
-    }
-
-    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 +
-            '"');
-    }
-    Object.keys(streams).forEach(function(sid) {
-      var stream = streams[sid];
-      if (stream.getTracks().length) {
-        if (pc.remoteStreams.indexOf(stream) === -1) {
-          pc.remoteStreams.push(stream);
-          var event = new Event('addstream');
-          event.stream = stream;
-          window.setTimeout(function() {
-            pc._dispatchEvent('addstream', event);
-          });
-        }
-
-        receiverList.forEach(function(item) {
-          var track = item[0];
-          var receiver = item[1];
-          if (stream.id !== item[2].id) {
-            return;
-          }
-          fireAddTrack(pc, track, receiver, [stream]);
-        });
-      }
-    });
-    receiverList.forEach(function(item) {
-      if (item[2]) {
-        return;
-      }
-      fireAddTrack(pc, item[0], item[1], []);
-    });
-
-    // check whether addIceCandidate({}) was called within four seconds after
-    // setRemoteDescription.
-    window.setTimeout(function() {
-      if (!(pc && pc.transceivers)) {
-        return;
-      }
-      pc.transceivers.forEach(function(transceiver) {
-        if (transceiver.iceTransport &&
-            transceiver.iceTransport.state === 'new' &&
-            transceiver.iceTransport.getRemoteCandidates().length > 0) {
-          console.warn('Timeout for addRemoteCandidate. Consider sending ' +
-              'an end-of-candidates notification');
-          transceiver.iceTransport.addRemoteCandidate({});
-        }
-      });
-    }, 4000);
-
-    return Promise.resolve();
-  };
-
-  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._isClosed = true;
-    this._updateSignalingState('closed');
-  };
-
-  // Update the signaling state.
-  RTCPeerConnection.prototype._updateSignalingState = function(newState) {
-    this.signalingState = newState;
-    var event = new Event('signalingstatechange');
-    this._dispatchEvent('signalingstatechange', event);
-  };
-
-  // Determine whether to fire the negotiationneeded event.
-  RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
-    var pc = this;
-    if (this.signalingState !== 'stable' || this.needNegotiation === true) {
-      return;
-    }
-    this.needNegotiation = true;
-    window.setTimeout(function() {
-      if (pc.needNegotiation === false) {
-        return;
-      }
-      pc.needNegotiation = false;
-      var event = new Event('negotiationneeded');
-      pc._dispatchEvent('negotiationneeded', event);
-    }, 0);
-  };
-
-  // Update the connection state.
-  RTCPeerConnection.prototype._updateConnectionState = function() {
-    var newState;
-    var states = {
-      'new': 0,
-      closed: 0,
-      connecting: 0,
-      checking: 0,
-      connected: 0,
-      completed: 0,
-      disconnected: 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 !== this.iceConnectionState) {
-      this.iceConnectionState = newState;
-      var event = new Event('iceconnectionstatechange');
-      this._dispatchEvent('iceconnectionstatechange', event);
-    }
-  };
-
-  RTCPeerConnection.prototype.createOffer = function() {
-    var pc = this;
-
-    if (this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not call createOffer after close'));
-    }
-
-    var numAudioTracks = this.transceivers.filter(function(t) {
-      return t.kind === 'audio';
-    }).length;
-    var numVideoTracks = this.transceivers.filter(function(t) {
-      return t.kind === 'video';
-    }).length;
-
-    // Determine number of audio and video tracks we need to send/recv.
-    var offerOptions = arguments[0];
-    if (offerOptions) {
-      // Reject Chrome legacy constraints.
-      if (offerOptions.mandatory || offerOptions.optional) {
-        throw new TypeError(
-            'Legacy mandatory/optional constraints not supported.');
-      }
-      if (offerOptions.offerToReceiveAudio !== undefined) {
-        if (offerOptions.offerToReceiveAudio === true) {
-          numAudioTracks = 1;
-        } else if (offerOptions.offerToReceiveAudio === false) {
-          numAudioTracks = 0;
-        } else {
-          numAudioTracks = offerOptions.offerToReceiveAudio;
-        }
-      }
-      if (offerOptions.offerToReceiveVideo !== undefined) {
-        if (offerOptions.offerToReceiveVideo === true) {
-          numVideoTracks = 1;
-        } else if (offerOptions.offerToReceiveVideo === false) {
-          numVideoTracks = 0;
-        } else {
-          numVideoTracks = offerOptions.offerToReceiveVideo;
-        }
-      }
-    }
-
-    this.transceivers.forEach(function(transceiver) {
-      if (transceiver.kind === 'audio') {
-        numAudioTracks--;
-        if (numAudioTracks < 0) {
-          transceiver.wantReceive = false;
-        }
-      } else if (transceiver.kind === 'video') {
-        numVideoTracks--;
-        if (numVideoTracks < 0) {
-          transceiver.wantReceive = false;
-        }
-      }
-    });
-
-    // Create M-lines for recvonly streams.
-    while (numAudioTracks > 0 || numVideoTracks > 0) {
-      if (numAudioTracks > 0) {
-        this._createTransceiver('audio');
-        numAudioTracks--;
-      }
-      if (numVideoTracks > 0) {
-        this._createTransceiver('video');
-        numVideoTracks--;
-      }
-    }
-
-    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId,
-        this._sdpSessionVersion++);
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      // For each track, create an ice gatherer, ice transport,
-      // dtls transport, potentially rtpsender and rtpreceiver.
-      var track = transceiver.track;
-      var kind = transceiver.kind;
-      var mid = SDPUtils.generateIdentifier();
-      transceiver.mid = mid;
-
-      if (!transceiver.iceGatherer) {
-        transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
-            pc.usingBundle);
-      }
-
-      var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
-      // filter RTX until additional stuff needed for RTX is implemented
-      // in adapter.js
-      if (edgeVersion < 15019) {
-        localCapabilities.codecs = localCapabilities.codecs.filter(
-            function(codec) {
-              return codec.name !== 'rtx';
-            });
-      }
-      localCapabilities.codecs.forEach(function(codec) {
-        // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
-        // by adding level-asymmetry-allowed=1
-        if (codec.name === 'H264' &&
-            codec.parameters['level-asymmetry-allowed'] === undefined) {
-          codec.parameters['level-asymmetry-allowed'] = '1';
-        }
-      });
-
-      // generate an ssrc now, to be used later in rtpSender.send
-      var sendEncodingParameters = transceiver.sendEncodingParameters || [{
-        ssrc: (2 * sdpMLineIndex + 1) * 1001
-      }];
-      if (track) {
-        // add RTX
-        if (edgeVersion >= 15019 && kind === 'video' &&
-            !sendEncodingParameters[0].rtx) {
-          sendEncodingParameters[0].rtx = {
-            ssrc: sendEncodingParameters[0].ssrc + 1
-          };
-        }
-      }
-
-      if (transceiver.wantReceive) {
-        transceiver.rtpReceiver = new window.RTCRtpReceiver(
-            transceiver.dtlsTransport, kind);
-      }
-
-      transceiver.localCapabilities = localCapabilities;
-      transceiver.sendEncodingParameters = sendEncodingParameters;
-    });
-
-    // always offer BUNDLE and dispose on return if not supported.
-    if (this._config.bundlePolicy !== 'max-compat') {
-      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
-        return t.mid;
-      }).join(' ') + '\r\n';
-    }
-    sdp += 'a=ice-options:trickle\r\n';
-
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      sdp += writeMediaSection(transceiver, transceiver.localCapabilities,
-          'offer', transceiver.stream, pc._dtlsRole);
-      sdp += 'a=rtcp-rsize\r\n';
-
-      if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' &&
-          (sdpMLineIndex === 0 || !pc.usingBundle)) {
-        transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) {
-          cand.component = 1;
-          sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n';
-        });
-
-        if (transceiver.iceGatherer.state === 'completed') {
-          sdp += 'a=end-of-candidates\r\n';
-        }
-      }
-    });
-
-    var desc = new window.RTCSessionDescription({
-      type: 'offer',
-      sdp: sdp
-    });
-    return Promise.resolve(desc);
-  };
-
-  RTCPeerConnection.prototype.createAnswer = function() {
-    var pc = this;
-
-    if (this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not call createAnswer after close'));
-    }
-
-    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId,
-        this._sdpSessionVersion++);
-    if (this.usingBundle) {
-      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
-        return t.mid;
-      }).join(' ') + '\r\n';
-    }
-    var mediaSectionsInOffer = SDPUtils.splitSections(
-        this.remoteDescription.sdp).length - 1;
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      if (sdpMLineIndex + 1 > mediaSectionsInOffer) {
-        return;
-      }
-      if (transceiver.isDatachannel) {
-        sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
-            'c=IN IP4 0.0.0.0\r\n' +
-            'a=mid:' + transceiver.mid + '\r\n';
-        return;
-      }
-
-      // FIXME: look at direction.
-      if (transceiver.stream) {
-        var localTrack;
-        if (transceiver.kind === 'audio') {
-          localTrack = transceiver.stream.getAudioTracks()[0];
-        } else if (transceiver.kind === 'video') {
-          localTrack = transceiver.stream.getVideoTracks()[0];
-        }
-        if (localTrack) {
-          // add RTX
-          if (edgeVersion >= 15019 && transceiver.kind === 'video' &&
-              !transceiver.sendEncodingParameters[0].rtx) {
-            transceiver.sendEncodingParameters[0].rtx = {
-              ssrc: transceiver.sendEncodingParameters[0].ssrc + 1
-            };
-          }
-        }
-      }
-
-      // Calculate intersection of capabilities.
-      var commonCapabilities = getCommonCapabilities(
-          transceiver.localCapabilities,
-          transceiver.remoteCapabilities);
-
-      var hasRtx = commonCapabilities.codecs.filter(function(c) {
-        return c.name.toLowerCase() === 'rtx';
-      }).length;
-      if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
-        delete transceiver.sendEncodingParameters[0].rtx;
-      }
-
-      sdp += writeMediaSection(transceiver, commonCapabilities,
-          'answer', transceiver.stream, pc._dtlsRole);
-      if (transceiver.rtcpParameters &&
-          transceiver.rtcpParameters.reducedSize) {
-        sdp += 'a=rtcp-rsize\r\n';
-      }
-    });
-
-    var desc = new window.RTCSessionDescription({
-      type: 'answer',
-      sdp: sdp
-    });
-    return Promise.resolve(desc);
-  };
-
-  RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
-    var sections;
-    if (!candidate || candidate.candidate === '') {
-      for (var j = 0; j < this.transceivers.length; j++) {
-        if (this.transceivers[j].isDatachannel) {
-          continue;
-        }
-        this.transceivers[j].iceTransport.addRemoteCandidate({});
-        sections = SDPUtils.splitSections(this.remoteDescription.sdp);
-        sections[j + 1] += 'a=end-of-candidates\r\n';
-        this.remoteDescription.sdp = sections.join('');
-        if (this.usingBundle) {
-          break;
-        }
-      }
-    } else if (!(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) {
-      throw new TypeError('sdpMLineIndex or sdpMid required');
-    } else if (!this.remoteDescription) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not add ICE candidate without a remote description'));
-    } else {
-      var sdpMLineIndex = candidate.sdpMLineIndex;
-      if (candidate.sdpMid) {
-        for (var i = 0; i < this.transceivers.length; i++) {
-          if (this.transceivers[i].mid === candidate.sdpMid) {
-            sdpMLineIndex = i;
-            break;
-          }
-        }
-      }
-      var transceiver = this.transceivers[sdpMLineIndex];
-      if (transceiver) {
-        if (transceiver.isDatachannel) {
-          return Promise.resolve();
-        }
-        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 Promise.resolve();
-        }
-        // Ignore RTCP candidates, we assume RTCP-MUX.
-        if (cand.component && cand.component !== 1) {
-          return Promise.resolve();
-        }
-        // when using bundle, avoid adding candidates to the wrong
-        // ice transport. And avoid adding candidates added in the SDP.
-        if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 &&
-            transceiver.iceTransport !== this.transceivers[0].iceTransport)) {
-          if (!maybeAddCandidate(transceiver.iceTransport, cand)) {
-            return Promise.reject(makeError('OperationError',
-                'Can not add ICE candidate'));
-          }
-        }
-
-        // update the remoteDescription.
-        var candidateString = candidate.candidate.trim();
-        if (candidateString.indexOf('a=') === 0) {
-          candidateString = candidateString.substr(2);
-        }
-        sections = SDPUtils.splitSections(this.remoteDescription.sdp);
-        sections[sdpMLineIndex + 1] += 'a=' +
-            (cand.type ? candidateString : 'end-of-candidates')
-            + '\r\n';
-        this.remoteDescription.sdp = sections.join('');
-      } else {
-        return Promise.reject(makeError('OperationError',
-            'Can not add ICE candidate'));
-      }
-    }
-    return Promise.resolve();
-  };
-
-  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 fixStatsType = function(stat) {
-      return {
-        inboundrtp: 'inbound-rtp',
-        outboundrtp: 'outbound-rtp',
-        candidatepair: 'candidate-pair',
-        localcandidate: 'local-candidate',
-        remotecandidate: 'remote-candidate'
-      }[stat.type] || stat.type;
-    };
-    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) {
-            result[id].type = fixStatsType(result[id]);
-            results.set(id, result[id]);
-          });
-        });
-        resolve(results);
-      });
-    });
-  };
-
-  // legacy callback shims. Should be moved to adapter.js some days.
-  var methods = ['createOffer', 'createAnswer'];
-  methods.forEach(function(method) {
-    var nativeMethod = RTCPeerConnection.prototype[method];
-    RTCPeerConnection.prototype[method] = function() {
-      var args = arguments;
-      if (typeof args[0] === 'function' ||
-          typeof args[1] === 'function') { // legacy
-        return nativeMethod.apply(this, [arguments[2]])
-        .then(function(description) {
-          if (typeof args[0] === 'function') {
-            args[0].apply(null, [description]);
-          }
-        }, function(error) {
-          if (typeof args[1] === 'function') {
-            args[1].apply(null, [error]);
-          }
-        });
-      }
-      return nativeMethod.apply(this, arguments);
-    };
-  });
-
-  methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'];
-  methods.forEach(function(method) {
-    var nativeMethod = RTCPeerConnection.prototype[method];
-    RTCPeerConnection.prototype[method] = function() {
-      var args = arguments;
-      if (typeof args[1] === 'function' ||
-          typeof args[2] === 'function') { // legacy
-        return nativeMethod.apply(this, arguments)
-        .then(function() {
-          if (typeof args[1] === 'function') {
-            args[1].apply(null);
-          }
-        }, function(error) {
-          if (typeof args[2] === 'function') {
-            args[2].apply(null, [error]);
-          }
-        });
-      }
-      return nativeMethod.apply(this, arguments);
-    };
-  });
-
-  // getStats is special. It doesn't have a spec legacy method yet we support
-  // getStats(something, cb) without error callbacks.
-  ['getStats'].forEach(function(method) {
-    var nativeMethod = RTCPeerConnection.prototype[method];
-    RTCPeerConnection.prototype[method] = function() {
-      var args = arguments;
-      if (typeof args[1] === 'function') {
-        return nativeMethod.apply(this, arguments)
-        .then(function() {
-          if (typeof args[1] === 'function') {
-            args[1].apply(null);
-          }
-        });
-      }
-      return nativeMethod.apply(this, arguments);
-    };
-  });
-
-  return RTCPeerConnection;
-};
-
-},{"sdp":2}],2:[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: parseInt(parts[1], 10),
-    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;
-      case 'ufrag':
-        candidate.ufrag = parts[i + 1]; // for backward compability.
-        candidate.usernameFragment = parts[i + 1];
-        break;
-      default: // extension handling, in particular ufrag
-        candidate[parts[i]] = parts[i + 1];
-        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);
-  }
-  if (candidate.ufrag) {
-    sdp.push('ufrag');
-    sdp.push(candidate.ufrag);
-  }
-  return 'candidate:' + sdp.join(' ');
-};
-
-// Parses an ice-options line, returns an array of option tags.
-// a=ice-options:foo bar
-SDPUtils.parseIceOptions = function(line) {
-  return line.substr(14).split(' ');
-}
-
-// 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
-// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
-SDPUtils.parseExtmap = function(line) {
-  var parts = line.substr(9).split(' ');
-  return {
-    id: parseInt(parts[0], 10),
-    direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
-    uri: parts[1]
-  };
-};
-
-// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
-// RTCRtpHeaderExtension.
-SDPUtils.writeExtmap = function(headerExtension) {
-  return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
-      (headerExtension.direction && headerExtension.direction !== 'sendrecv'
-          ? '/' + headerExtension.direction
-          : '') +
-      ' ' + 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 the MID (RFC 5888) from a media section.
-// returns the MID or undefined if no mid line was found.
-SDPUtils.getMid = function(mediaSection) {
-  var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
-  if (mid) {
-    return mid.substr(6);
-  }
-}
-
-SDPUtils.parseFingerprint = function(line) {
-  var parts = line.substr(14).split(' ');
-  return {
-    algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
-    value: parts[1]
-  };
-};
-
-// 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.matchPrefix(mediaSection + sessionpart,
-      'a=fingerprint:');
-  // Note: a=setup line is ignored since we use the 'auto' role.
-  // Note2: 'algorithm' is not case sensitive except in Edge.
-  return {
-    role: 'auto',
-    fingerprints: lines.map(SDPUtils.parseFingerprint)
-  };
-};
-
-// 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);
-  });
-  var maxptime = 0;
-  caps.codecs.forEach(function(codec) {
-    if (codec.maxptime > maxptime) {
-      maxptime = codec.maxptime;
-    }
-  });
-  if (maxptime > 0) {
-    sdp += 'a=maxptime:' + maxptime + '\r\n';
-  }
-  sdp += 'a=rtcp-mux\r\n';
-
-  caps.headerExtensions.forEach(function(extension) {
-    sdp += SDPUtils.writeExtmap(extension);
-  });
-  // FIXME: write fecMechanisms.
-  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: {
-          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) {
-      // use formula from JSEP to convert b=AS to TIAS value.
-      bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95
-          - (50 * 40 * 8);
-    } else {
-      bandwidth = undefined;
-    }
-    encodingParameters.forEach(function(params) {
-      params.maxBitrate = bandwidth;
-    });
-  }
-  return encodingParameters;
-};
-
-// parses http://draft.ortc.org/#rtcrtcpparameters*
-SDPUtils.parseRtcpParameters = function(mediaSection) {
-  var rtcpParameters = {};
-
-  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) {
-    rtcpParameters.cname = remoteSsrc.value;
-    rtcpParameters.ssrc = remoteSsrc.ssrc;
-  }
-
-  // Edge uses the compound attribute instead of reducedSize
-  // compound is !reducedSize
-  var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
-  rtcpParameters.reducedSize = rsize.length > 0;
-  rtcpParameters.compound = rsize.length === 0;
-
-  // parses the rtcp-mux attrÑ–bute.
-  // Note that Edge does not support unmuxed RTCP.
-  var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
-  rtcpParameters.mux = mux.length > 0;
-
-  return rtcpParameters;
-};
-
-// parses either a=msid: or a=ssrc:... msid lines and returns
-// the id of the MediaStream and MediaStreamTrack.
-SDPUtils.parseMsid = function(mediaSection) {
-  var parts;
-  var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
-  if (spec.length === 1) {
-    parts = spec[0].substr(7).split(' ');
-    return {stream: parts[0], track: parts[1]};
-  }
-  var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
-  .map(function(line) {
-    return SDPUtils.parseSsrcMedia(line);
-  })
-  .filter(function(parts) {
-    return parts.attribute === 'msid';
-  });
-  if (planB.length > 0) {
-    parts = planB[0].value.split(' ');
-    return {stream: parts[0], track: parts[1]};
-  }
-};
-
-// Generate a session ID for SDP.
-// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
-// recommends using a cryptographically random +ve 64-bit value
-// but right now this should be acceptable and within the right range
-SDPUtils.generateSessionId = function() {
-  return Math.random().toString().substr(2, 21);
-};
-
-// Write boilder plate for start of SDP
-// sessId argument is optional - if not supplied it will
-// be generated randomly
-// sessVersion is optional and defaults to 2
-SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) {
-  var sessionId;
-  var version = sessVer !== undefined ? sessVer : 2;
-  if (sessId) {
-    sessionId = sessId;
-  } else {
-    sessionId = SDPUtils.generateSessionId();
-  }
-  // FIXME: sess-id should be an NTP timestamp.
-  return 'v=0\r\n' +
-      'o=thisisadapterortc ' + sessionId + ' ' + version + ' 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.direction) {
-    sdp += 'a=' + transceiver.direction + '\r\n';
-  } else 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) {
-    // spec.
-    var msid = 'msid:' + stream.id + ' ' +
-        transceiver.rtpSender.track.id + '\r\n';
-    sdp += 'a=' + msid;
-
-    // for Chrome.
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-        ' ' + msid;
-    if (transceiver.sendEncodingParameters[0].rtx) {
-      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-          ' ' + msid;
-      sdp += 'a=ssrc-group:FID ' +
-          transceiver.sendEncodingParameters[0].ssrc + ' ' +
-          transceiver.sendEncodingParameters[0].rtx.ssrc +
-          '\r\n';
-    }
-  }
-  // FIXME: this should be written by writeRtpDescription.
-  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-      ' cname:' + SDPUtils.localCName + '\r\n';
-  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.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';
-};
-
-SDPUtils.getKind = function(mediaSection) {
-  var lines = SDPUtils.splitLines(mediaSection);
-  var mline = lines[0].split(' ');
-  return mline[0].substr(2);
-};
-
-SDPUtils.isRejected = function(mediaSection) {
-  return mediaSection.split(' ', 2)[1] === '0';
-};
-
-SDPUtils.parseMLine = function(mediaSection) {
-  var lines = SDPUtils.splitLines(mediaSection);
-  var mline = lines[0].split(' ');
-  return {
-    kind: mline[0].substr(2),
-    port: parseInt(mline[1], 10),
-    protocol: mline[2],
-    fmt: mline.slice(3).join(' ')
-  };
-};
-
-// Expose public methods.
-if (typeof module === 'object') {
-  module.exports = SDPUtils;
-}
-
-},{}],3:[function(require,module,exports){
-(function (global){
-/*
- *  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 adapterFactory = require('./adapter_factory.js');
-module.exports = adapterFactory({window: global.window});
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"./adapter_factory.js":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 utils = require('./utils');
-// Shimming starts here.
-module.exports = function(dependencies, opts) {
-  var window = dependencies && dependencies.window;
-
-  var options = {
-    shimChrome: true,
-    shimFirefox: true,
-    shimEdge: true,
-    shimSafari: true,
-  };
-
-  for (var key in opts) {
-    if (hasOwnProperty.call(opts, key)) {
-      options[key] = opts[key];
-    }
-  }
-
-  // Utils.
-  var logging = utils.log;
-  var browserDetails = utils.detectBrowser(window);
-
-  // 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;
-  var commonShim = require('./common_shim') || null;
-
-  // Export to the adapter global object visible in the browser.
-  var adapter = {
-    browserDetails: browserDetails,
-    commonShim: commonShim,
-    extractVersion: utils.extractVersion,
-    disableLog: utils.disableLog,
-    disableWarnings: utils.disableWarnings
-  };
-
-  // Shim browser if found.
-  switch (browserDetails.browser) {
-    case 'chrome':
-      if (!chromeShim || !chromeShim.shimPeerConnection ||
-          !options.shimChrome) {
-        logging('Chrome shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming chrome.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = chromeShim;
-      commonShim.shimCreateObjectURL(window);
-
-      chromeShim.shimGetUserMedia(window);
-      chromeShim.shimMediaStream(window);
-      chromeShim.shimSourceObject(window);
-      chromeShim.shimPeerConnection(window);
-      chromeShim.shimOnTrack(window);
-      chromeShim.shimAddTrackRemoveTrack(window);
-      chromeShim.shimGetSendersWithDtmf(window);
-
-      commonShim.shimRTCIceCandidate(window);
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    case 'firefox':
-      if (!firefoxShim || !firefoxShim.shimPeerConnection ||
-          !options.shimFirefox) {
-        logging('Firefox shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming firefox.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = firefoxShim;
-      commonShim.shimCreateObjectURL(window);
-
-      firefoxShim.shimGetUserMedia(window);
-      firefoxShim.shimSourceObject(window);
-      firefoxShim.shimPeerConnection(window);
-      firefoxShim.shimOnTrack(window);
-      firefoxShim.shimRemoveStream(window);
-
-      commonShim.shimRTCIceCandidate(window);
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    case 'edge':
-      if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) {
-        logging('MS edge shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming edge.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = edgeShim;
-      commonShim.shimCreateObjectURL(window);
-
-      edgeShim.shimGetUserMedia(window);
-      edgeShim.shimPeerConnection(window);
-      edgeShim.shimReplaceTrack(window);
-
-      // the edge shim implements the full RTCIceCandidate object.
-
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    case 'safari':
-      if (!safariShim || !options.shimSafari) {
-        logging('Safari shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming safari.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = safariShim;
-      commonShim.shimCreateObjectURL(window);
-
-      safariShim.shimRTCIceServerUrls(window);
-      safariShim.shimCallbacksAPI(window);
-      safariShim.shimLocalStreamsAPI(window);
-      safariShim.shimRemoteStreamsAPI(window);
-      safariShim.shimTrackEventTransceiver(window);
-      safariShim.shimGetUserMedia(window);
-      safariShim.shimCreateOfferLegacy(window);
-
-      commonShim.shimRTCIceCandidate(window);
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    default:
-      logging('Unsupported browser!');
-      break;
-  }
-
-  return adapter;
-};
-
-},{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":10,"./safari/safari_shim":12,"./utils":13}],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 utils = require('../utils.js');
-var logging = utils.log;
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimMediaStream: function(window) {
-    window.MediaStream = window.MediaStream || window.webkitMediaStream;
-  },
-
-  shimOnTrack: function(window) {
-    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.addEventListener('track', this._ontrack = f);
-        }
-      });
-      var origSetRemoteDescription =
-          window.RTCPeerConnection.prototype.setRemoteDescription;
-      window.RTCPeerConnection.prototype.setRemoteDescription = function() {
-        var pc = this;
-        if (!pc._ontrackpoly) {
-          pc._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 receiver;
-              if (window.RTCPeerConnection.prototype.getReceivers) {
-                receiver = pc.getReceivers().find(function(r) {
-                  return r.track && r.track.id === te.track.id;
-                });
-              } else {
-                receiver = {track: te.track};
-              }
-
-              var event = new Event('track');
-              event.track = te.track;
-              event.receiver = receiver;
-              event.transceiver = {receiver: receiver};
-              event.streams = [e.stream];
-              pc.dispatchEvent(event);
-            });
-            e.stream.getTracks().forEach(function(track) {
-              var receiver;
-              if (window.RTCPeerConnection.prototype.getReceivers) {
-                receiver = pc.getReceivers().find(function(r) {
-                  return r.track && r.track.id === track.id;
-                });
-              } else {
-                receiver = {track: track};
-              }
-              var event = new Event('track');
-              event.track = track;
-              event.receiver = receiver;
-              event.transceiver = {receiver: receiver};
-              event.streams = [e.stream];
-              pc.dispatchEvent(event);
-            });
-          };
-          pc.addEventListener('addstream', pc._ontrackpoly);
-        }
-        return origSetRemoteDescription.apply(pc, arguments);
-      };
-    } else if (!('RTCRtpTransceiver' in window)) {
-      utils.wrapPeerConnectionEvent(window, 'track', function(e) {
-        if (!e.transceiver) {
-          e.transceiver = {receiver: e.receiver};
-        }
-        return e;
-      });
-    }
-  },
-
-  shimGetSendersWithDtmf: function(window) {
-    // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
-    if (typeof window === 'object' && window.RTCPeerConnection &&
-        !('getSenders' in window.RTCPeerConnection.prototype) &&
-        'createDTMFSender' in window.RTCPeerConnection.prototype) {
-      var shimSenderWithDtmf = function(pc, track) {
-        return {
-          track: track,
-          get dtmf() {
-            if (this._dtmf === undefined) {
-              if (track.kind === 'audio') {
-                this._dtmf = pc.createDTMFSender(track);
-              } else {
-                this._dtmf = null;
-              }
-            }
-            return this._dtmf;
-          },
-          _pc: pc
-        };
-      };
-
-      // augment addTrack when getSenders is not available.
-      if (!window.RTCPeerConnection.prototype.getSenders) {
-        window.RTCPeerConnection.prototype.getSenders = function() {
-          this._senders = this._senders || [];
-          return this._senders.slice(); // return a copy of the internal state.
-        };
-        var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
-        window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-          var pc = this;
-          var sender = origAddTrack.apply(pc, arguments);
-          if (!sender) {
-            sender = shimSenderWithDtmf(pc, track);
-            pc._senders.push(sender);
-          }
-          return sender;
-        };
-
-        var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
-        window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-          var pc = this;
-          origRemoveTrack.apply(pc, arguments);
-          var idx = pc._senders.indexOf(sender);
-          if (idx !== -1) {
-            pc._senders.splice(idx, 1);
-          }
-        };
-      }
-      var origAddStream = window.RTCPeerConnection.prototype.addStream;
-      window.RTCPeerConnection.prototype.addStream = function(stream) {
-        var pc = this;
-        pc._senders = pc._senders || [];
-        origAddStream.apply(pc, [stream]);
-        stream.getTracks().forEach(function(track) {
-          pc._senders.push(shimSenderWithDtmf(pc, track));
-        });
-      };
-
-      var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-      window.RTCPeerConnection.prototype.removeStream = function(stream) {
-        var pc = this;
-        pc._senders = pc._senders || [];
-        origRemoveStream.apply(pc, [stream]);
-
-        stream.getTracks().forEach(function(track) {
-          var sender = pc._senders.find(function(s) {
-            return s.track === track;
-          });
-          if (sender) {
-            pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender
-          }
-        });
-      };
-    } else if (typeof window === 'object' && window.RTCPeerConnection &&
-               'getSenders' in window.RTCPeerConnection.prototype &&
-               'createDTMFSender' in window.RTCPeerConnection.prototype &&
-               window.RTCRtpSender &&
-               !('dtmf' in window.RTCRtpSender.prototype)) {
-      var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
-      window.RTCPeerConnection.prototype.getSenders = function() {
-        var pc = this;
-        var senders = origGetSenders.apply(pc, []);
-        senders.forEach(function(sender) {
-          sender._pc = pc;
-        });
-        return senders;
-      };
-
-      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
-        get: function() {
-          if (this._dtmf === undefined) {
-            if (this.track.kind === 'audio') {
-              this._dtmf = this._pc.createDTMFSender(this.track);
-            } else {
-              this._dtmf = null;
-            }
-          }
-          return this._dtmf;
-        }
-      });
-    }
-  },
-
-  shimSourceObject: function(window) {
-    var URL = window && window.URL;
-
-    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 undefined;
-            }
-            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);
-            });
-          }
-        });
-      }
-    }
-  },
-
-  shimAddTrackRemoveTrackWithNative: function(window) {
-    // shim addTrack/removeTrack with native variants in order to make
-    // the interactions with legacy getLocalStreams behave as in other browsers.
-    // Keeps a mapping stream.id => [stream, rtpsenders...]
-    window.RTCPeerConnection.prototype.getLocalStreams = function() {
-      var pc = this;
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-      return Object.keys(this._shimmedLocalStreams).map(function(streamId) {
-        return pc._shimmedLocalStreams[streamId][0];
-      });
-    };
-
-    var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
-    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-      if (!stream) {
-        return origAddTrack.apply(this, arguments);
-      }
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-
-      var sender = origAddTrack.apply(this, arguments);
-      if (!this._shimmedLocalStreams[stream.id]) {
-        this._shimmedLocalStreams[stream.id] = [stream, sender];
-      } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) {
-        this._shimmedLocalStreams[stream.id].push(sender);
-      }
-      return sender;
-    };
-
-    var origAddStream = window.RTCPeerConnection.prototype.addStream;
-    window.RTCPeerConnection.prototype.addStream = function(stream) {
-      var pc = this;
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-
-      stream.getTracks().forEach(function(track) {
-        var alreadyExists = pc.getSenders().find(function(s) {
-          return s.track === track;
-        });
-        if (alreadyExists) {
-          throw new DOMException('Track already exists.',
-              'InvalidAccessError');
-        }
-      });
-      var existingSenders = pc.getSenders();
-      origAddStream.apply(this, arguments);
-      var newSenders = pc.getSenders().filter(function(newSender) {
-        return existingSenders.indexOf(newSender) === -1;
-      });
-      this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders);
-    };
-
-    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-      delete this._shimmedLocalStreams[stream.id];
-      return origRemoveStream.apply(this, arguments);
-    };
-
-    var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
-    window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-      var pc = this;
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-      if (sender) {
-        Object.keys(this._shimmedLocalStreams).forEach(function(streamId) {
-          var idx = pc._shimmedLocalStreams[streamId].indexOf(sender);
-          if (idx !== -1) {
-            pc._shimmedLocalStreams[streamId].splice(idx, 1);
-          }
-          if (pc._shimmedLocalStreams[streamId].length === 1) {
-            delete pc._shimmedLocalStreams[streamId];
-          }
-        });
-      }
-      return origRemoveTrack.apply(this, arguments);
-    };
-  },
-
-  shimAddTrackRemoveTrack: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-    // shim addTrack and removeTrack.
-    if (window.RTCPeerConnection.prototype.addTrack &&
-        browserDetails.version >= 65) {
-      return this.shimAddTrackRemoveTrackWithNative(window);
-    }
-
-    // also shim pc.getLocalStreams when addTrack is shimmed
-    // to return the original streams.
-    var origGetLocalStreams = window.RTCPeerConnection.prototype
-        .getLocalStreams;
-    window.RTCPeerConnection.prototype.getLocalStreams = function() {
-      var pc = this;
-      var nativeStreams = origGetLocalStreams.apply(this);
-      pc._reverseStreams = pc._reverseStreams || {};
-      return nativeStreams.map(function(stream) {
-        return pc._reverseStreams[stream.id];
-      });
-    };
-
-    var origAddStream = window.RTCPeerConnection.prototype.addStream;
-    window.RTCPeerConnection.prototype.addStream = function(stream) {
-      var pc = this;
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-
-      stream.getTracks().forEach(function(track) {
-        var alreadyExists = pc.getSenders().find(function(s) {
-          return s.track === track;
-        });
-        if (alreadyExists) {
-          throw new DOMException('Track already exists.',
-              'InvalidAccessError');
-        }
-      });
-      // Add identity mapping for consistency with addTrack.
-      // Unless this is being used with a stream from addTrack.
-      if (!pc._reverseStreams[stream.id]) {
-        var newStream = new window.MediaStream(stream.getTracks());
-        pc._streams[stream.id] = newStream;
-        pc._reverseStreams[newStream.id] = stream;
-        stream = newStream;
-      }
-      origAddStream.apply(pc, [stream]);
-    };
-
-    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      var pc = this;
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-
-      origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
-      delete pc._reverseStreams[(pc._streams[stream.id] ?
-          pc._streams[stream.id].id : stream.id)];
-      delete pc._streams[stream.id];
-    };
-
-    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-      var pc = this;
-      if (pc.signalingState === 'closed') {
-        throw new DOMException(
-          'The RTCPeerConnection\'s signalingState is \'closed\'.',
-          'InvalidStateError');
-      }
-      var streams = [].slice.call(arguments, 1);
-      if (streams.length !== 1 ||
-          !streams[0].getTracks().find(function(t) {
-            return t === track;
-          })) {
-        // this is not fully correct but all we can manage without
-        // [[associated MediaStreams]] internal slot.
-        throw new DOMException(
-          'The adapter.js addTrack polyfill only supports a single ' +
-          ' stream which is associated with the specified track.',
-          'NotSupportedError');
-      }
-
-      var alreadyExists = pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-      if (alreadyExists) {
-        throw new DOMException('Track already exists.',
-            'InvalidAccessError');
-      }
-
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-      var oldStream = pc._streams[stream.id];
-      if (oldStream) {
-        // this is using odd Chrome behaviour, use with caution:
-        // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
-        // Note: we rely on the high-level addTrack/dtmf shim to
-        // create the sender with a dtmf sender.
-        oldStream.addTrack(track);
-
-        // Trigger ONN async.
-        Promise.resolve().then(function() {
-          pc.dispatchEvent(new Event('negotiationneeded'));
-        });
-      } else {
-        var newStream = new window.MediaStream([track]);
-        pc._streams[stream.id] = newStream;
-        pc._reverseStreams[newStream.id] = stream;
-        pc.addStream(newStream);
-      }
-      return pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-    };
-
-    // replace the internal stream id with the external one and
-    // vice versa.
-    function replaceInternalStreamId(pc, description) {
-      var sdp = description.sdp;
-      Object.keys(pc._reverseStreams || []).forEach(function(internalId) {
-        var externalStream = pc._reverseStreams[internalId];
-        var internalStream = pc._streams[externalStream.id];
-        sdp = sdp.replace(new RegExp(internalStream.id, 'g'),
-            externalStream.id);
-      });
-      return new RTCSessionDescription({
-        type: description.type,
-        sdp: sdp
-      });
-    }
-    function replaceExternalStreamId(pc, description) {
-      var sdp = description.sdp;
-      Object.keys(pc._reverseStreams || []).forEach(function(internalId) {
-        var externalStream = pc._reverseStreams[internalId];
-        var internalStream = pc._streams[externalStream.id];
-        sdp = sdp.replace(new RegExp(externalStream.id, 'g'),
-            internalStream.id);
-      });
-      return new RTCSessionDescription({
-        type: description.type,
-        sdp: sdp
-      });
-    }
-    ['createOffer', 'createAnswer'].forEach(function(method) {
-      var nativeMethod = window.RTCPeerConnection.prototype[method];
-      window.RTCPeerConnection.prototype[method] = function() {
-        var pc = this;
-        var args = arguments;
-        var isLegacyCall = arguments.length &&
-            typeof arguments[0] === 'function';
-        if (isLegacyCall) {
-          return nativeMethod.apply(pc, [
-            function(description) {
-              var desc = replaceInternalStreamId(pc, description);
-              args[0].apply(null, [desc]);
-            },
-            function(err) {
-              if (args[1]) {
-                args[1].apply(null, err);
-              }
-            }, arguments[2]
-          ]);
-        }
-        return nativeMethod.apply(pc, arguments)
-        .then(function(description) {
-          return replaceInternalStreamId(pc, description);
-        });
-      };
-    });
-
-    var origSetLocalDescription =
-        window.RTCPeerConnection.prototype.setLocalDescription;
-    window.RTCPeerConnection.prototype.setLocalDescription = function() {
-      var pc = this;
-      if (!arguments.length || !arguments[0].type) {
-        return origSetLocalDescription.apply(pc, arguments);
-      }
-      arguments[0] = replaceExternalStreamId(pc, arguments[0]);
-      return origSetLocalDescription.apply(pc, arguments);
-    };
-
-    // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier
-
-    var origLocalDescription = Object.getOwnPropertyDescriptor(
-        window.RTCPeerConnection.prototype, 'localDescription');
-    Object.defineProperty(window.RTCPeerConnection.prototype,
-        'localDescription', {
-          get: function() {
-            var pc = this;
-            var description = origLocalDescription.get.apply(this);
-            if (description.type === '') {
-              return description;
-            }
-            return replaceInternalStreamId(pc, description);
-          }
-        });
-
-    window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-      var pc = this;
-      if (pc.signalingState === 'closed') {
-        throw new DOMException(
-          'The RTCPeerConnection\'s signalingState is \'closed\'.',
-          'InvalidStateError');
-      }
-      // We can not yet check for sender instanceof RTCRtpSender
-      // since we shim RTPSender. So we check if sender._pc is set.
-      if (!sender._pc) {
-        throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' +
-            'does not implement interface RTCRtpSender.', 'TypeError');
-      }
-      var isLocal = sender._pc === pc;
-      if (!isLocal) {
-        throw new DOMException('Sender was not created by this connection.',
-            'InvalidAccessError');
-      }
-
-      // Search for the native stream the senders track belongs to.
-      pc._streams = pc._streams || {};
-      var stream;
-      Object.keys(pc._streams).forEach(function(streamid) {
-        var hasTrack = pc._streams[streamid].getTracks().find(function(track) {
-          return sender.track === track;
-        });
-        if (hasTrack) {
-          stream = pc._streams[streamid];
-        }
-      });
-
-      if (stream) {
-        if (stream.getTracks().length === 1) {
-          // if this is the last track of the stream, remove the stream. This
-          // takes care of any shimmed _senders.
-          pc.removeStream(pc._reverseStreams[stream.id]);
-        } else {
-          // relying on the same odd chrome behaviour as above.
-          stream.removeTrack(sender.track);
-        }
-        pc.dispatchEvent(new Event('negotiationneeded'));
-      }
-    };
-  },
-
-  shimPeerConnection: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    // The RTCPeerConnection object.
-    if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) {
-      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-        // Translate iceTransportPolicy to iceTransports,
-        // see https://code.google.com/p/webrtc/issues/detail?id=4869
-        // this was fixed in M56 along with unprefixing RTCPeerConnection.
-        logging('PeerConnection');
-        if (pcConfig && pcConfig.iceTransportPolicy) {
-          pcConfig.iceTransports = pcConfig.iceTransportPolicy;
-        }
-
-        return new window.webkitRTCPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype =
-          window.webkitRTCPeerConnection.prototype;
-      // wrap static methods. Currently just generateCertificate.
-      if (window.webkitRTCPeerConnection.generateCertificate) {
-        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-          get: function() {
-            return window.webkitRTCPeerConnection.generateCertificate;
-          }
-        });
-      }
-    } else {
-      // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
-      var OrigPeerConnection = window.RTCPeerConnection;
-      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-        if (pcConfig && pcConfig.iceServers) {
-          var newIceServers = [];
-          for (var i = 0; i < pcConfig.iceServers.length; i++) {
-            var server = pcConfig.iceServers[i];
-            if (!server.hasOwnProperty('urls') &&
-                server.hasOwnProperty('url')) {
-              utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
-              server = JSON.parse(JSON.stringify(server));
-              server.urls = server.url;
-              newIceServers.push(server);
-            } else {
-              newIceServers.push(pcConfig.iceServers[i]);
-            }
-          }
-          pcConfig.iceServers = newIceServers;
-        }
-        return new OrigPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
-      // wrap static methods. Currently just generateCertificate.
-      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-        get: function() {
-          return OrigPeerConnection.generateCertificate;
-        }
-      });
-    }
-
-    var origGetStats = window.RTCPeerConnection.prototype.getStats;
-    window.RTCPeerConnection.prototype.getStats = function(selector,
-        successCallback, errorCallback) {
-      var pc = 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.apply(this, arguments);
-      }
-
-      // When spec-style getStats is supported, return those when called with
-      // either no arguments or the selector argument is null.
-      if (origGetStats.length === 0 && (arguments.length === 0 ||
-          typeof arguments[0] !== 'function')) {
-        return origGetStats.apply(this, []);
-      }
-
-      var fixChromeStats_ = function(response) {
-        var standardReport = {};
-        var reports = response.result();
-        reports.forEach(function(report) {
-          var standardStats = {
-            id: report.id,
-            timestamp: report.timestamp,
-            type: {
-              localcandidate: 'local-candidate',
-              remotecandidate: 'remote-candidate'
-            }[report.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) {
-        return new Map(Object.keys(stats).map(function(key) {
-          return [key, stats[key]];
-        }));
-      };
-
-      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) {
-        origGetStats.apply(pc, [
-          function(response) {
-            resolve(makeMapStats(fixChromeStats_(response)));
-          }, reject]);
-      }).then(successCallback, errorCallback);
-    };
-
-    // add promise support -- natively available in Chrome 51
-    if (browserDetails.version < 51) {
-      ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-          .forEach(function(method) {
-            var nativeMethod = window.RTCPeerConnection.prototype[method];
-            window.RTCPeerConnection.prototype[method] = function() {
-              var args = arguments;
-              var pc = this;
-              var promise = new Promise(function(resolve, reject) {
-                nativeMethod.apply(pc, [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]);
-                }
-              });
-            };
-          });
-    }
-
-    // promise support for createOffer and createAnswer. Available (without
-    // bugs) since M52: crbug/619289
-    if (browserDetails.version < 52) {
-      ['createOffer', 'createAnswer'].forEach(function(method) {
-        var nativeMethod = window.RTCPeerConnection.prototype[method];
-        window.RTCPeerConnection.prototype[method] = function() {
-          var pc = 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(pc, [resolve, reject, opts]);
-            });
-          }
-          return nativeMethod.apply(this, arguments);
-        };
-      });
-    }
-
-    // shim implicit creation of RTCSessionDescription/RTCIceCandidate
-    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-        .forEach(function(method) {
-          var nativeMethod = window.RTCPeerConnection.prototype[method];
-          window.RTCPeerConnection.prototype[method] = function() {
-            arguments[0] = new ((method === 'addIceCandidate') ?
-                window.RTCIceCandidate :
-                window.RTCSessionDescription)(arguments[0]);
-            return nativeMethod.apply(this, arguments);
-          };
-        });
-
-    // support for addIceCandidate(null or undefined)
-    var nativeAddIceCandidate =
-        window.RTCPeerConnection.prototype.addIceCandidate;
-    window.RTCPeerConnection.prototype.addIceCandidate = function() {
-      if (!arguments[0]) {
-        if (arguments[1]) {
-          arguments[1].apply(null);
-        }
-        return Promise.resolve();
-      }
-      return nativeAddIceCandidate.apply(this, arguments);
-    };
-  }
-};
-
-},{"../utils.js":13,"./getusermedia":6}],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';
-var utils = require('../utils.js');
-var logging = utils.log;
-
-// Expose public methods.
-module.exports = function(window) {
-  var browserDetails = utils.detectBrowser(window);
-  var navigator = window && window.navigator;
-
-  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) {
-    if (browserDetails.version >= 61) {
-      return func(constraints);
-    }
-    constraints = JSON.parse(JSON.stringify(constraints));
-    if (constraints && typeof constraints.audio === 'object') {
-      var remap = function(obj, a, b) {
-        if (a in obj && !(b in obj)) {
-          obj[b] = obj[a];
-          delete obj[a];
-        }
-      };
-      constraints = JSON.parse(JSON.stringify(constraints));
-      remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
-      remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
-      constraints.audio = constraintsToChrome_(constraints.audio);
-    }
-    if (constraints && typeof constraints.video === 'object') {
-      // Shim facingMode for mobile & surface pro.
-      var face = constraints.video.facingMode;
-      face = face && ((typeof face === 'object') ? face : {ideal: face});
-      var getSupportedFacingModeLies = browserDetails.version < 66;
-
-      if ((face && (face.exact === 'user' || face.exact === 'environment' ||
-                    face.ideal === 'user' || face.ideal === 'environment')) &&
-          !(navigator.mediaDevices.getSupportedConstraints &&
-            navigator.mediaDevices.getSupportedConstraints().facingMode &&
-            !getSupportedFacingModeLies)) {
-        delete constraints.video.facingMode;
-        var matches;
-        if (face.exact === 'environment' || face.ideal === 'environment') {
-          matches = ['back', 'rear'];
-        } else if (face.exact === 'user' || face.ideal === 'user') {
-          matches = ['front'];
-        }
-        if (matches) {
-          // Look for matches in label, or use last cam for back (typical).
-          return navigator.mediaDevices.enumerateDevices()
-          .then(function(devices) {
-            devices = devices.filter(function(d) {
-              return d.kind === 'videoinput';
-            });
-            var dev = devices.find(function(d) {
-              return matches.some(function(match) {
-                return d.label.toLowerCase().indexOf(match) !== -1;
-              });
-            });
-            if (!dev && devices.length && matches.indexOf('back') !== -1) {
-              dev = devices[devices.length - 1]; // more likely the back cam
-            }
-            if (dev) {
-              constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :
-                                                        {ideal: dev.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',
-        InvalidStateError: 'NotReadableError',
-        DevicesNotFoundError: 'NotFoundError',
-        ConstraintNotSatisfiedError: 'OverconstrainedError',
-        TrackStartError: 'NotReadableError',
-        MediaDeviceFailedDueToShutdown: 'NotReadableError',
-        MediaDeviceKillSwitchOn: 'NotReadableError'
-      }[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) {
-        if (onError) {
-          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 window.MediaStreamTrack.getSources(function(devices) {
-            resolve(devices.map(function(device) {
-              return {label: device.label,
-                kind: kinds[device.kind],
-                deviceId: device.id,
-                groupId: ''};
-            }));
-          });
-        });
-      },
-      getSupportedConstraints: function() {
-        return {
-          deviceId: true, echoCancellation: true, facingMode: true,
-          frameRate: true, height: true, width: true
-        };
-      }
-    };
-  }
-
-  // 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).then(function(stream) {
-          if (c.audio && !stream.getAudioTracks().length ||
-              c.video && !stream.getVideoTracks().length) {
-            stream.getTracks().forEach(function(track) {
-              track.stop();
-            });
-            throw new DOMException('', 'NotFoundError');
-          }
-          return stream;
-        }, 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":13}],7:[function(require,module,exports){
-/*
- *  Copyright (c) 2017 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 utils = require('./utils');
-
-module.exports = {
-  shimRTCIceCandidate: function(window) {
-    // foundation is arbitrarily chosen as an indicator for full support for
-    // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
-    if (window.RTCIceCandidate && 'foundation' in
-        window.RTCIceCandidate.prototype) {
-      return;
-    }
-
-    var NativeRTCIceCandidate = window.RTCIceCandidate;
-    window.RTCIceCandidate = function(args) {
-      // Remove the a= which shouldn't be part of the candidate string.
-      if (typeof args === 'object' && args.candidate &&
-          args.candidate.indexOf('a=') === 0) {
-        args = JSON.parse(JSON.stringify(args));
-        args.candidate = args.candidate.substr(2);
-      }
-
-      // Augment the native candidate with the parsed fields.
-      var nativeCandidate = new NativeRTCIceCandidate(args);
-      var parsedCandidate = SDPUtils.parseCandidate(args.candidate);
-      var augmentedCandidate = Object.assign(nativeCandidate,
-          parsedCandidate);
-
-      // Add a serializer that does not serialize the extra attributes.
-      augmentedCandidate.toJSON = function() {
-        return {
-          candidate: augmentedCandidate.candidate,
-          sdpMid: augmentedCandidate.sdpMid,
-          sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
-          usernameFragment: augmentedCandidate.usernameFragment,
-        };
-      };
-      return augmentedCandidate;
-    };
-
-    // Hook up the augmented candidate in onicecandidate and
-    // addEventListener('icecandidate', ...)
-    utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) {
-      if (e.candidate) {
-        Object.defineProperty(e, 'candidate', {
-          value: new window.RTCIceCandidate(e.candidate),
-          writable: 'false'
-        });
-      }
-      return e;
-    });
-  },
-
-  // shimCreateObjectURL must be called before shimSourceObject to avoid loop.
-
-  shimCreateObjectURL: function(window) {
-    var URL = window && window.URL;
-
-    if (!(typeof window === 'object' && window.HTMLMediaElement &&
-          'srcObject' in window.HTMLMediaElement.prototype &&
-        URL.createObjectURL && URL.revokeObjectURL)) {
-      // Only shim CreateObjectURL using srcObject if srcObject exists.
-      return undefined;
-    }
-
-    var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
-    var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
-    var streams = new Map(), newId = 0;
-
-    URL.createObjectURL = function(stream) {
-      if ('getTracks' in stream) {
-        var url = 'polyblob:' + (++newId);
-        streams.set(url, stream);
-        utils.deprecated('URL.createObjectURL(stream)',
-            'elem.srcObject = stream');
-        return url;
-      }
-      return nativeCreateObjectURL(stream);
-    };
-    URL.revokeObjectURL = function(url) {
-      nativeRevokeObjectURL(url);
-      streams.delete(url);
-    };
-
-    var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
-                                              'src');
-    Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
-      get: function() {
-        return dsc.get.apply(this);
-      },
-      set: function(url) {
-        this.srcObject = streams.get(url) || null;
-        return dsc.set.apply(this, [url]);
-      }
-    });
-
-    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
-    window.HTMLMediaElement.prototype.setAttribute = function() {
-      if (arguments.length === 2 &&
-          ('' + arguments[0]).toLowerCase() === 'src') {
-        this.srcObject = streams.get(arguments[1]) || null;
-      }
-      return nativeSetAttribute.apply(this, arguments);
-    };
-  },
-
-  shimMaxMessageSize: function(window) {
-    if (window.RTCSctpTransport || !window.RTCPeerConnection) {
-      return;
-    }
-    var browserDetails = utils.detectBrowser(window);
-
-    if (!('sctp' in window.RTCPeerConnection.prototype)) {
-      Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
-        get: function() {
-          return typeof this._sctp === 'undefined' ? null : this._sctp;
-        }
-      });
-    }
-
-    var sctpInDescription = function(description) {
-      var sections = SDPUtils.splitSections(description.sdp);
-      sections.shift();
-      return sections.some(function(mediaSection) {
-        var mLine = SDPUtils.parseMLine(mediaSection);
-        return mLine && mLine.kind === 'application'
-            && mLine.protocol.indexOf('SCTP') !== -1;
-      });
-    };
-
-    var getRemoteFirefoxVersion = function(description) {
-      // TODO: Is there a better solution for detecting Firefox?
-      var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);
-      if (match === null || match.length < 2) {
-        return -1;
-      }
-      var version = parseInt(match[1], 10);
-      // Test for NaN (yes, this is ugly)
-      return version !== version ? -1 : version;
-    };
-
-    var getCanSendMaxMessageSize = function(remoteIsFirefox) {
-      // Every implementation we know can send at least 64 KiB.
-      // Note: Although Chrome is technically able to send up to 256 KiB, the
-      //       data does not reach the other peer reliably.
-      //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
-      var canSendMaxMessageSize = 65536;
-      if (browserDetails.browser === 'firefox') {
-        if (browserDetails.version < 57) {
-          if (remoteIsFirefox === -1) {
-            // FF < 57 will send in 16 KiB chunks using the deprecated PPID
-            // fragmentation.
-            canSendMaxMessageSize = 16384;
-          } else {
-            // However, other FF (and RAWRTC) can reassemble PPID-fragmented
-            // messages. Thus, supporting ~2 GiB when sending.
-            canSendMaxMessageSize = 2147483637;
-          }
-        } else {
-          // Currently, all FF >= 57 will reset the remote maximum message size
-          // to the default value when a data channel is created at a later
-          // stage. :(
-          // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
-          canSendMaxMessageSize =
-            browserDetails.version === 57 ? 65535 : 65536;
-        }
-      }
-      return canSendMaxMessageSize;
-    };
-
-    var getMaxMessageSize = function(description, remoteIsFirefox) {
-      // Note: 65536 bytes is the default value from the SDP spec. Also,
-      //       every implementation we know supports receiving 65536 bytes.
-      var maxMessageSize = 65536;
-
-      // FF 57 has a slightly incorrect default remote max message size, so
-      // we need to adjust it here to avoid a failure when sending.
-      // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
-      if (browserDetails.browser === 'firefox'
-           && browserDetails.version === 57) {
-        maxMessageSize = 65535;
-      }
-
-      var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:');
-      if (match.length > 0) {
-        maxMessageSize = parseInt(match[0].substr(19), 10);
-      } else if (browserDetails.browser === 'firefox' &&
-                  remoteIsFirefox !== -1) {
-        // If the maximum message size is not present in the remote SDP and
-        // both local and remote are Firefox, the remote peer can receive
-        // ~2 GiB.
-        maxMessageSize = 2147483637;
-      }
-      return maxMessageSize;
-    };
-
-    var origSetRemoteDescription =
-        window.RTCPeerConnection.prototype.setRemoteDescription;
-    window.RTCPeerConnection.prototype.setRemoteDescription = function() {
-      var pc = this;
-      pc._sctp = null;
-
-      if (sctpInDescription(arguments[0])) {
-        // Check if the remote is FF.
-        var isFirefox = getRemoteFirefoxVersion(arguments[0]);
-
-        // Get the maximum message size the local peer is capable of sending
-        var canSendMMS = getCanSendMaxMessageSize(isFirefox);
-
-        // Get the maximum message size of the remote peer.
-        var remoteMMS = getMaxMessageSize(arguments[0], isFirefox);
-
-        // Determine final maximum message size
-        var maxMessageSize;
-        if (canSendMMS === 0 && remoteMMS === 0) {
-          maxMessageSize = Number.POSITIVE_INFINITY;
-        } else if (canSendMMS === 0 || remoteMMS === 0) {
-          maxMessageSize = Math.max(canSendMMS, remoteMMS);
-        } else {
-          maxMessageSize = Math.min(canSendMMS, remoteMMS);
-        }
-
-        // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
-        // attribute.
-        var sctp = {};
-        Object.defineProperty(sctp, 'maxMessageSize', {
-          get: function() {
-            return maxMessageSize;
-          }
-        });
-        pc._sctp = sctp;
-      }
-
-      return origSetRemoteDescription.apply(pc, arguments);
-    };
-  },
-
-  shimSendThrowTypeError: function(window) {
-    // Note: Although Firefox >= 57 has a native implementation, the maximum
-    //       message size can be reset for all data channels at a later stage.
-    //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
-
-    var origCreateDataChannel =
-      window.RTCPeerConnection.prototype.createDataChannel;
-    window.RTCPeerConnection.prototype.createDataChannel = function() {
-      var pc = this;
-      var dataChannel = origCreateDataChannel.apply(pc, arguments);
-      var origDataChannelSend = dataChannel.send;
-
-      // Patch 'send' method
-      dataChannel.send = function() {
-        var dc = this;
-        var data = arguments[0];
-        var length = data.length || data.size || data.byteLength;
-        if (length > pc.sctp.maxMessageSize) {
-          throw new DOMException('Message too large (can send a maximum of ' +
-            pc.sctp.maxMessageSize + ' bytes)', 'TypeError');
-        }
-        return origDataChannelSend.apply(dc, arguments);
-      };
-
-      return dataChannel;
-    };
-  }
-};
-
-},{"./utils":13,"sdp":2}],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 utils = require('../utils');
-var shimRTCPeerConnection = require('rtcpeerconnection-shim');
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimPeerConnection: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    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;
-        };
-      }
-      // this adds an additional event listener to MediaStrackTrack that signals
-      // when a tracks enabled property was changed. Workaround for a bug in
-      // addStream, see below. No longer required in 15025+
-      if (browserDetails.version < 15025) {
-        var origMSTEnabled = Object.getOwnPropertyDescriptor(
-            window.MediaStreamTrack.prototype, 'enabled');
-        Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {
-          set: function(value) {
-            origMSTEnabled.set.call(this, value);
-            var ev = new Event('enabled');
-            ev.enabled = value;
-            this.dispatchEvent(ev);
-          }
-        });
-      }
-    }
-
-    // ORTC defines the DTMF sender a bit different.
-    // https://github.com/w3c/ortc/issues/714
-    if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {
-      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
-        get: function() {
-          if (this._dtmf === undefined) {
-            if (this.track.kind === 'audio') {
-              this._dtmf = new window.RTCDtmfSender(this);
-            } else if (this.track.kind === 'video') {
-              this._dtmf = null;
-            }
-          }
-          return this._dtmf;
-        }
-      });
-    }
-
-    window.RTCPeerConnection =
-        shimRTCPeerConnection(window, browserDetails.version);
-  },
-  shimReplaceTrack: function(window) {
-    // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
-    if (window.RTCRtpSender &&
-        !('replaceTrack' in window.RTCRtpSender.prototype)) {
-      window.RTCRtpSender.prototype.replaceTrack =
-          window.RTCRtpSender.prototype.setTrack;
-    }
-  }
-};
-
-},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],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.
- */
- /* eslint-env node */
-'use strict';
-
-// Expose public methods.
-module.exports = function(window) {
-  var navigator = window && window.navigator;
-
-  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));
-    });
-  };
-};
-
-},{}],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 utils = require('../utils');
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimOnTrack: function(window) {
-    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.transceiver = {receiver: event.receiver};
-              event.streams = [e.stream];
-              this.dispatchEvent(event);
-            }.bind(this));
-          }.bind(this));
-        }
-      });
-    }
-    if (typeof window === 'object' && window.RTCTrackEvent &&
-        ('receiver' in window.RTCTrackEvent.prototype) &&
-        !('transceiver' in window.RTCTrackEvent.prototype)) {
-      Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
-        get: function() {
-          return {receiver: this.receiver};
-        }
-      });
-    }
-  },
-
-  shimSourceObject: function(window) {
-    // 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(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    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 window.mozRTCPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype =
-          window.mozRTCPeerConnection.prototype;
-
-      // wrap static methods. Currently just generateCertificate.
-      if (window.mozRTCPeerConnection.generateCertificate) {
-        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-          get: function() {
-            return window.mozRTCPeerConnection.generateCertificate;
-          }
-        });
-      }
-
-      window.RTCSessionDescription = window.mozRTCSessionDescription;
-      window.RTCIceCandidate = window.mozRTCIceCandidate;
-    }
-
-    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
-    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-        .forEach(function(method) {
-          var nativeMethod = window.RTCPeerConnection.prototype[method];
-          window.RTCPeerConnection.prototype[method] = function() {
-            arguments[0] = new ((method === 'addIceCandidate') ?
-                window.RTCIceCandidate :
-                window.RTCSessionDescription)(arguments[0]);
-            return nativeMethod.apply(this, arguments);
-          };
-        });
-
-    // support for addIceCandidate(null or undefined)
-    var nativeAddIceCandidate =
-        window.RTCPeerConnection.prototype.addIceCandidate;
-    window.RTCPeerConnection.prototype.addIceCandidate = function() {
-      if (!arguments[0]) {
-        if (arguments[1]) {
-          arguments[1].apply(null);
-        }
-        return Promise.resolve();
-      }
-      return 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 modernStatsTypes = {
-      inboundrtp: 'inbound-rtp',
-      outboundrtp: 'outbound-rtp',
-      candidatepair: 'candidate-pair',
-      localcandidate: 'local-candidate',
-      remotecandidate: 'remote-candidate'
-    };
-
-    var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
-    window.RTCPeerConnection.prototype.getStats = function(
-      selector,
-      onSucc,
-      onErr
-    ) {
-      return nativeGetStats.apply(this, [selector || null])
-        .then(function(stats) {
-          if (browserDetails.version < 48) {
-            stats = makeMapStats(stats);
-          }
-          if (browserDetails.version < 53 && !onSucc) {
-            // Shim only promise getStats with spec-hyphens in type names
-            // Leave callback version alone; misc old uses of forEach before Map
-            try {
-              stats.forEach(function(stat) {
-                stat.type = modernStatsTypes[stat.type] || stat.type;
-              });
-            } catch (e) {
-              if (e.name !== 'TypeError') {
-                throw e;
-              }
-              // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
-              stats.forEach(function(stat, i) {
-                stats.set(i, Object.assign({}, stat, {
-                  type: modernStatsTypes[stat.type] || stat.type
-                }));
-              });
-            }
-          }
-          return stats;
-        })
-        .then(onSucc, onErr);
-    };
-  },
-
-  shimRemoveStream: function(window) {
-    if (!window.RTCPeerConnection ||
-        'removeStream' in window.RTCPeerConnection.prototype) {
-      return;
-    }
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      var pc = this;
-      utils.deprecated('removeStream', 'removeTrack');
-      this.getSenders().forEach(function(sender) {
-        if (sender.track && stream.getTracks().indexOf(sender.track) !== -1) {
-          pc.removeTrack(sender);
-        }
-      });
-    };
-  }
-};
-
-},{"../utils":13,"./getusermedia":11}],11:[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 utils = require('../utils');
-var logging = utils.log;
-
-// Expose public methods.
-module.exports = function(window) {
-  var browserDetails = utils.detectBrowser(window);
-  var navigator = window && window.navigator;
-  var MediaStreamTrack = window && window.MediaStreamTrack;
-
-  var shimError_ = function(e) {
-    return {
-      name: {
-        InternalError: 'NotReadableError',
-        NotSupportedError: 'TypeError',
-        PermissionDeniedError: 'NotAllowedError',
-        SecurityError: '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).then(function(stream) {
-        // Work around https://bugzil.la/802326
-        if (c.audio && !stream.getAudioTracks().length ||
-            c.video && !stream.getVideoTracks().length) {
-          stream.getTracks().forEach(function(track) {
-            track.stop();
-          });
-          throw new DOMException('The object can not be found here.',
-                                 'NotFoundError');
-        }
-        return stream;
-      }, function(e) {
-        return Promise.reject(shimError_(e));
-      });
-    };
-  }
-  if (!(browserDetails.version > 55 &&
-      'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {
-    var remap = function(obj, a, b) {
-      if (a in obj && !(b in obj)) {
-        obj[b] = obj[a];
-        delete obj[a];
-      }
-    };
-
-    var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.
-        bind(navigator.mediaDevices);
-    navigator.mediaDevices.getUserMedia = function(c) {
-      if (typeof c === 'object' && typeof c.audio === 'object') {
-        c = JSON.parse(JSON.stringify(c));
-        remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
-        remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
-      }
-      return nativeGetUserMedia(c);
-    };
-
-    if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
-      var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
-      MediaStreamTrack.prototype.getSettings = function() {
-        var obj = nativeGetSettings.apply(this, arguments);
-        remap(obj, 'mozAutoGainControl', 'autoGainControl');
-        remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
-        return obj;
-      };
-    }
-
-    if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {
-      var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
-      MediaStreamTrack.prototype.applyConstraints = function(c) {
-        if (this.kind === 'audio' && typeof c === 'object') {
-          c = JSON.parse(JSON.stringify(c));
-          remap(c, 'autoGainControl', 'mozAutoGainControl');
-          remap(c, 'noiseSuppression', 'mozNoiseSuppression');
-        }
-        return nativeApplyConstraints.apply(this, [c]);
-      };
-    }
-  }
-  navigator.getUserMedia = function(constraints, onSuccess, onError) {
-    if (browserDetails.version < 44) {
-      return getUserMedia_(constraints, onSuccess, onError);
-    }
-    // Replace Firefox 44+'s deprecation warning with unprefixed version.
-    utils.deprecated('navigator.getUserMedia',
-        'navigator.mediaDevices.getUserMedia');
-    navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
-  };
-};
-
-},{"../utils":13}],12:[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 utils = require('../utils');
-
-module.exports = {
-  shimLocalStreamsAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getLocalStreams = function() {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        return this._localStreams;
-      };
-    }
-    if (!('getStreamById' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getStreamById = function(id) {
-        var result = null;
-        if (this._localStreams) {
-          this._localStreams.forEach(function(stream) {
-            if (stream.id === id) {
-              result = stream;
-            }
-          });
-        }
-        if (this._remoteStreams) {
-          this._remoteStreams.forEach(function(stream) {
-            if (stream.id === id) {
-              result = stream;
-            }
-          });
-        }
-        return result;
-      };
-    }
-    if (!('addStream' in window.RTCPeerConnection.prototype)) {
-      var _addTrack = window.RTCPeerConnection.prototype.addTrack;
-      window.RTCPeerConnection.prototype.addStream = function(stream) {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        if (this._localStreams.indexOf(stream) === -1) {
-          this._localStreams.push(stream);
-        }
-        var pc = this;
-        stream.getTracks().forEach(function(track) {
-          _addTrack.call(pc, track, stream);
-        });
-      };
-
-      window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-        if (stream) {
-          if (!this._localStreams) {
-            this._localStreams = [stream];
-          } else if (this._localStreams.indexOf(stream) === -1) {
-            this._localStreams.push(stream);
-          }
-        }
-        return _addTrack.call(this, track, stream);
-      };
-    }
-    if (!('removeStream' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.removeStream = function(stream) {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        var index = this._localStreams.indexOf(stream);
-        if (index === -1) {
-          return;
-        }
-        this._localStreams.splice(index, 1);
-        var pc = this;
-        var tracks = stream.getTracks();
-        this.getSenders().forEach(function(sender) {
-          if (tracks.indexOf(sender.track) !== -1) {
-            pc.removeTrack(sender);
-          }
-        });
-      };
-    }
-  },
-  shimRemoteStreamsAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getRemoteStreams = function() {
-        return this._remoteStreams ? this._remoteStreams : [];
-      };
-    }
-    if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
-      Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
-        get: function() {
-          return this._onaddstream;
-        },
-        set: function(f) {
-          var pc = this;
-          if (this._onaddstream) {
-            this.removeEventListener('addstream', this._onaddstream);
-            this.removeEventListener('track', this._onaddstreampoly);
-          }
-          this.addEventListener('addstream', this._onaddstream = f);
-          this.addEventListener('track', this._onaddstreampoly = function(e) {
-            e.streams.forEach(function(stream) {
-              if (!pc._remoteStreams) {
-                pc._remoteStreams = [];
-              }
-              if (pc._remoteStreams.indexOf(stream) >= 0) {
-                return;
-              }
-              pc._remoteStreams.push(stream);
-              var event = new Event('addstream');
-              event.stream = stream;
-              pc.dispatchEvent(event);
-            });
-          });
-        }
-      });
-    }
-  },
-  shimCallbacksAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    var prototype = window.RTCPeerConnection.prototype;
-    var createOffer = prototype.createOffer;
-    var createAnswer = prototype.createAnswer;
-    var setLocalDescription = prototype.setLocalDescription;
-    var setRemoteDescription = prototype.setRemoteDescription;
-    var addIceCandidate = prototype.addIceCandidate;
-
-    prototype.createOffer = function(successCallback, failureCallback) {
-      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
-      var promise = createOffer.apply(this, [options]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-
-    prototype.createAnswer = function(successCallback, failureCallback) {
-      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
-      var promise = createAnswer.apply(this, [options]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-
-    var withCallback = function(description, successCallback, failureCallback) {
-      var promise = setLocalDescription.apply(this, [description]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.setLocalDescription = withCallback;
-
-    withCallback = function(description, successCallback, failureCallback) {
-      var promise = setRemoteDescription.apply(this, [description]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.setRemoteDescription = withCallback;
-
-    withCallback = function(candidate, successCallback, failureCallback) {
-      var promise = addIceCandidate.apply(this, [candidate]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.addIceCandidate = withCallback;
-  },
-  shimGetUserMedia: function(window) {
-    var navigator = window && window.navigator;
-
-    if (!navigator.getUserMedia) {
-      if (navigator.webkitGetUserMedia) {
-        navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
-      } else if (navigator.mediaDevices &&
-          navigator.mediaDevices.getUserMedia) {
-        navigator.getUserMedia = function(constraints, cb, errcb) {
-          navigator.mediaDevices.getUserMedia(constraints)
-          .then(cb, errcb);
-        }.bind(navigator);
-      }
-    }
-  },
-  shimRTCIceServerUrls: function(window) {
-    // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
-    var OrigPeerConnection = window.RTCPeerConnection;
-    window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-      if (pcConfig && pcConfig.iceServers) {
-        var newIceServers = [];
-        for (var i = 0; i < pcConfig.iceServers.length; i++) {
-          var server = pcConfig.iceServers[i];
-          if (!server.hasOwnProperty('urls') &&
-              server.hasOwnProperty('url')) {
-            utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
-            server = JSON.parse(JSON.stringify(server));
-            server.urls = server.url;
-            delete server.url;
-            newIceServers.push(server);
-          } else {
-            newIceServers.push(pcConfig.iceServers[i]);
-          }
-        }
-        pcConfig.iceServers = newIceServers;
-      }
-      return new OrigPeerConnection(pcConfig, pcConstraints);
-    };
-    window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
-    // wrap static methods. Currently just generateCertificate.
-    if ('generateCertificate' in window.RTCPeerConnection) {
-      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-        get: function() {
-          return OrigPeerConnection.generateCertificate;
-        }
-      });
-    }
-  },
-  shimTrackEventTransceiver: function(window) {
-    // Add event.transceiver member over deprecated event.receiver
-    if (typeof window === 'object' && window.RTCPeerConnection &&
-        ('receiver' in window.RTCTrackEvent.prototype) &&
-        // can't check 'transceiver' in window.RTCTrackEvent.prototype, as it is
-        // defined for some reason even when window.RTCTransceiver is not.
-        !window.RTCTransceiver) {
-      Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
-        get: function() {
-          return {receiver: this.receiver};
-        }
-      });
-    }
-  },
-
-  shimCreateOfferLegacy: function(window) {
-    var origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
-    window.RTCPeerConnection.prototype.createOffer = function(offerOptions) {
-      var pc = this;
-      if (offerOptions) {
-        var audioTransceiver = pc.getTransceivers().find(function(transceiver) {
-          return transceiver.sender.track &&
-              transceiver.sender.track.kind === 'audio';
-        });
-        if (offerOptions.offerToReceiveAudio === false && audioTransceiver) {
-          if (audioTransceiver.direction === 'sendrecv') {
-            if (audioTransceiver.setDirection) {
-              audioTransceiver.setDirection('sendonly');
-            } else {
-              audioTransceiver.direction = 'sendonly';
-            }
-          } else if (audioTransceiver.direction === 'recvonly') {
-            if (audioTransceiver.setDirection) {
-              audioTransceiver.setDirection('inactive');
-            } else {
-              audioTransceiver.direction = 'inactive';
-            }
-          }
-        } else if (offerOptions.offerToReceiveAudio === true &&
-            !audioTransceiver) {
-          pc.addTransceiver('audio');
-        }
-
-        var videoTransceiver = pc.getTransceivers().find(function(transceiver) {
-          return transceiver.sender.track &&
-              transceiver.sender.track.kind === 'video';
-        });
-        if (offerOptions.offerToReceiveVideo === false && videoTransceiver) {
-          if (videoTransceiver.direction === 'sendrecv') {
-            videoTransceiver.setDirection('sendonly');
-          } else if (videoTransceiver.direction === 'recvonly') {
-            videoTransceiver.setDirection('inactive');
-          }
-        } else if (offerOptions.offerToReceiveVideo === true &&
-            !videoTransceiver) {
-          pc.addTransceiver('video');
-        }
-      }
-      return origCreateOffer.apply(pc, arguments);
-    };
-  }
-};
-
-},{"../utils":13}],13:[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;
-var deprecationWarnings_ = true;
-
-/**
- * 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.
- */
-function extractVersion(uastring, expr, pos) {
-  var match = uastring.match(expr);
-  return match && match.length >= pos && parseInt(match[pos], 10);
-}
-
-// Wraps the peerconnection event eventNameToWrap in a function
-// which returns the modified event object.
-function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
-  if (!window.RTCPeerConnection) {
-    return;
-  }
-  var proto = window.RTCPeerConnection.prototype;
-  var nativeAddEventListener = proto.addEventListener;
-  proto.addEventListener = function(nativeEventName, cb) {
-    if (nativeEventName !== eventNameToWrap) {
-      return nativeAddEventListener.apply(this, arguments);
-    }
-    var wrappedCallback = function(e) {
-      cb(wrapper(e));
-    };
-    this._eventMap = this._eventMap || {};
-    this._eventMap[cb] = wrappedCallback;
-    return nativeAddEventListener.apply(this, [nativeEventName,
-      wrappedCallback]);
-  };
-
-  var nativeRemoveEventListener = proto.removeEventListener;
-  proto.removeEventListener = function(nativeEventName, cb) {
-    if (nativeEventName !== eventNameToWrap || !this._eventMap
-        || !this._eventMap[cb]) {
-      return nativeRemoveEventListener.apply(this, arguments);
-    }
-    var unwrappedCb = this._eventMap[cb];
-    delete this._eventMap[cb];
-    return nativeRemoveEventListener.apply(this, [nativeEventName,
-      unwrappedCb]);
-  };
-
-  Object.defineProperty(proto, 'on' + eventNameToWrap, {
-    get: function() {
-      return this['_on' + eventNameToWrap];
-    },
-    set: function(cb) {
-      if (this['_on' + eventNameToWrap]) {
-        this.removeEventListener(eventNameToWrap,
-            this['_on' + eventNameToWrap]);
-        delete this['_on' + eventNameToWrap];
-      }
-      if (cb) {
-        this.addEventListener(eventNameToWrap,
-            this['_on' + eventNameToWrap] = cb);
-      }
-    }
-  });
-}
-
-// Utility methods.
-module.exports = {
-  extractVersion: extractVersion,
-  wrapPeerConnectionEvent: wrapPeerConnectionEvent,
-  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';
-  },
-
-  /**
-   * Disable or enable deprecation warnings
-   * @param {!boolean} bool set to true to disable warnings.
-   */
-  disableWarnings: function(bool) {
-    if (typeof bool !== 'boolean') {
-      return new Error('Argument type: ' + typeof bool +
-          '. Please use a boolean.');
-    }
-    deprecationWarnings_ = !bool;
-    return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
-  },
-
-  log: function() {
-    if (typeof window === 'object') {
-      if (logDisabled_) {
-        return;
-      }
-      if (typeof console !== 'undefined' && typeof console.log === 'function') {
-        console.log.apply(console, arguments);
-      }
-    }
-  },
-
-  /**
-   * Shows a deprecation warning suggesting the modern and spec-compatible API.
-   */
-  deprecated: function(oldMethod, newMethod) {
-    if (!deprecationWarnings_) {
-      return;
-    }
-    console.warn(oldMethod + ' is deprecated, please use ' + newMethod +
-        ' instead.');
-  },
-
-  /**
-   * Browser detector.
-   *
-   * @return {object} result containing browser and version
-   *     properties.
-   */
-  detectBrowser: function(window) {
-    var navigator = window && window.navigator;
-
-    // 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;
-    }
-
-    if (navigator.mozGetUserMedia) { // Firefox.
-      result.browser = 'firefox';
-      result.version = extractVersion(navigator.userAgent,
-          /Firefox\/(\d+)\./, 1);
-    } else if (navigator.webkitGetUserMedia) {
-      // Chrome, Chromium, Webview, Opera.
-      // Version matches Chrome/WebRTC version.
-      result.browser = 'chrome';
-      result.version = extractVersion(navigator.userAgent,
-          /Chrom(e|ium)\/(\d+)\./, 2);
-    } else if (navigator.mediaDevices &&
-        navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge.
-      result.browser = 'edge';
-      result.version = extractVersion(navigator.userAgent,
-          /Edge\/(\d+).(\d+)$/, 2);
-    } else if (navigator.mediaDevices &&
-        navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari.
-      result.browser = 'safari';
-      result.version = extractVersion(navigator.userAgent,
-          /AppleWebKit\/(\d+)\./, 1);
-    } else { // Default fallthrough: not supported.
-      result.browser = 'Not a supported browser.';
-      return result;
-    }
-
-    return result;
-  }
-};
-
-},{}]},{},[3])(3)
-});
\ No newline at end of file
+(function($){function findLine(sdpLines,prefix,substr){return findLineInRange(sdpLines,0,-1,prefix,substr);}
+function findLineInRange(sdpLines,startLine,endLine,prefix,substr){var realEndLine=(endLine!=-1)?endLine:sdpLines.length;for(var i=startLine;i<realEndLine;++i){if(sdpLines[i].indexOf(prefix)===0){if(!substr||sdpLines[i].toLowerCase().indexOf(substr.toLowerCase())!==-1){return i;}}}
+return null;}
+function getCodecPayloadType(sdpLine){var pattern=new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');var result=sdpLine.match(pattern);return(result&&result.length==2)?result[1]:null;}
+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(){}},useStream:null,},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(){}
+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 onRemoteStreamSuccess(self,stream){console.log("Remote Stream Success");doCallback(self,"onRemoteStream",stream);}
+function onICE(self,candidate){self.mediaData.candidate=candidate;self.mediaData.candidateList.push(self.mediaData.candidate);doCallback(self,"onICE");}
+function doCallback(self,func,arg){if(func in self.options.callbacks){self.options.callbacks[func](self,arg);}}
+function onICEComplete(self,candidate){console.log("ICE Complete");doCallback(self,"onICEComplete");}
+function onChannelError(self,e){console.error("Channel Error",e);doCallback(self,"onError",e);}
+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(typeof element.srcObject!=='undefined'){element.srcObject=stream;}else{console.error('Error attaching stream to element.');}}
+function onRemoteStream(self,stream){if(self.options.useVideo){self.options.useVideo.style.display='block';var iOS=['iPad','iPhone','iPod'].indexOf(navigator.platform)>=0;if(iOS){self.options.useVideo.setAttribute("playsinline",true);}}
+var element=self.options.useAudio;console.log("REMOTE STREAM",stream,element);FSRTCattachMediaStream(element,stream);self.remoteStream=stream;onRemoteStreamSuccess(self,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';self.options.useVideo['src']='';}
+if(self.localStream&&!self.options.useStream){if(typeof self.localStream.stop=='function'){self.localStream.stop();}else{if(self.localStream.active){var tracks=self.localStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}
+self.localStream=null;}
+if(self.options.localVideo){deactivateLocalVideo(self.options.localVideo);}
+if(self.options.localVideoStream&&!self.options.useStream){if(typeof self.options.localVideoStream.stop=='function'){self.options.localVideoStream.stop();}else{if(self.options.localVideoStream.active){var tracks=self.options.localVideoStream.getTracks();console.log(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;if(!self.localStream){return false;}
+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;}
+self.audioEnabled=audioTracks[i].enabled;}
+return!self.audioEnabled;}
+$.FSRTC.prototype.getVideoMute=function(){var self=this;return self.videoEnabled;}
+$.FSRTC.prototype.setVideoMute=function(what){var self=this;if(!self.localStream){return false;}
+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=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},turnServer:self.options.turnServer});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(self.options.useVideo&&self.options.localVideo&&!self.options.useStream){getUserMedia({constraints:{audio:false,video:{deviceId:params.useCamera},},localVideo:self.options.localVideo,useCameraLabel:self.options.useCameraLabel,useMicLabel:self.options.useMicLabel,onsuccess:function(e){self.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!",e);}});}
+if(self.options.useStream){if(self.options.useVideo){self.options.localVideoStream=self.options.useStream;if(self.options.localVideo){activateLocalVideo(self.options.localVideo,self.options.useStream);}}
+onSuccess(self.options.useStream);}
+else{getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,useCameraLabel:self.options.useCameraLabel,useMicLabel:self.options.useMicLabel,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={};if(obj.options.audioParams){audio=obj.options.audioParams;}
+if(obj.options.useMic!=="any"){audio.deviceId=assignMediaIdToConstraint(obj.options.useMic);}}
+if(obj.options.useVideo&&obj.options.localVideo&&!obj.options.useStream){getUserMedia({constraints:{audio:false,video:{deviceId:obj.options.useCamera},},localVideo:obj.options.localVideo,useCameraLabel:obj.options.useCameraLabel,useMicLabel:obj.options.useMicLabel,onsuccess:function(e){obj.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!",e);}});}
+var video={};var bestFrameRate=obj.options.videoParams.vertoBestFrameRate;var minFrameRate=obj.options.videoParams.minFrameRate||15;delete obj.options.videoParams.vertoBestFrameRate;if(obj.options.screenShare){if(!obj.options.useCamera&&!!navigator.mozGetUserMedia){var dowin=window.confirm("Do you want to share an application window?  If not you can share an entire screen.");video={width:{min:obj.options.videoParams.minWidth,max:obj.options.videoParams.maxWidth},height:{min:obj.options.videoParams.minHeight,max:obj.options.videoParams.maxHeight},mediaSource:dowin?"window":"screen"}}else{var opt=[];if(obj.options.useCamera){opt.push({sourceId:obj.options.useCamera});}
+if(bestFrameRate){opt.push({minFrameRate:bestFrameRate});opt.push({maxFrameRate:bestFrameRate});}
+video={mandatory:obj.options.videoParams,optional:opt};}}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=assignMediaIdToConstraint(obj.options.useCamera,video);}
+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){self.constraints.offerToReceiveVideo=false;self.constraints.offerToReceiveAudio=false;self.constraints.offerToSendAudio=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,turnServer:self.options.turnServer});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(self.options.useStream){if(self.options.useVideo){self.options.localVideoStream=self.options.useStream;if(self.options.localVideo){activateLocalVideo(self.options.localVideo,self.options.useStream);}}
+onSuccess(self.options.useStream);}
+else if(mediaParams.audio||mediaParams.video){getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,onsuccess:onSuccess,onerror:onError,useCameraLabel:self.options.useCameraLabel,useMicLabel:self.options.useMicLabel,});}else{onSuccess(null);}};function FSRTCPeerConnection(options){var gathering=false,done=false;var config={};var default_ice=[{urls:['stun:stun.l.google.com:19302']}];if(self.options.turnServer){default_ice.push(self.options.turnServer)}
+if(options.iceServers){if(typeof(options.iceServers)==="boolean"){config.iceServers=default_ice;}else{config.iceServers=options.iceServers;}}
+config.bundlePolicy="max-compat";config.sdpSemantics="plan-b";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){options.attachStream.getAudioTracks().forEach(function(track){peer.addTrack(track,options.attachStream)});options.attachStream.getVideoTracks().forEach(function(track){peer.addTrack(track,options.attachStream)});}
+if(options.attachStreams&&options.attachStreams.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.oninactive=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)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);};}
+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 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 activateLocalVideo(el,stream){el.srcObject=stream;el.style.display='block';}
+function deactivateLocalVideo(el){el.srcObject=null;el.style.display='none';}
+function assureConstraintByLabel(constraint,fallbackLabel){if(fallbackLabel===undefined&&constraint===undefined){return Promise.resolve(constraint);}
+if(typeof(assureMediaInputId)!=='function'){console.warn('Tried to use constraint fallbacks but did not found vendor function `assureMediaInputId` on window scope. Did you forget to import `vendor/media-device-id.js` before Verto?');return Promise.resolve(constraint);}
+if(typeof(constraint)==='object'&&!constraint.deviceId){return Promise.resolve(constraint);}
+if(constraint.deviceId){if(typeof(constraint.deviceId)==='string'){return new Promise(function(resolve){assureMediaInputId(fallbackLabel,constraint.deviceId).then(function(id){resolve(Object.assign({},constraint,{deviceId:id}));}).catch(function(){resolve(constraint);});});}
+if(typeof(constraint.deviceId)==='object'&&typeof(constraint.deviceId.exact)==='string'){return new Promise(function(resolve){assureMediaInputId(fallbackLabel,constraint.deviceId.exact).then(function(id){resolve(assignMediaIdToConstraint(id,constraint));}).catch(function(){resolve(constraint);});});}}
+return Promise.resolve(constraint);}
+function trustyGetUserMedia(options,constraints){navigator.mediaDevices.getUserMedia(constraints).then(function(stream){if(options.localVideo){activateLocalVideo(options.localVideo,stream);}
+if(options.onsuccess){options.onsuccess(stream);}}).catch(options.onerror||function(e){console.error(e);});}
+function assignMediaIdToConstraint(mediaId,rest){return Object.assign({},rest||{},{deviceId:{exact:mediaId}});}
+function getUserMedia(options){var constraints=options.constraints||{audio:true,video:video_constraints,};Promise.all([assureConstraintByLabel(constraints.audio,options.useMicLabel),assureConstraintByLabel(constraints.video,options.useCameraLabel),]).then(function(assurances){trustyGetUserMedia(options,{audio:assurances[0],video:assurances[1]});}).catch(function(error){console.error('Unexpected error on media id assurance attempts:',error,'Options:',options);});}
+$.FSRTC.resSupported=function(w,h){for(var i in $.FSRTC.validRes){if($.FSRTC.validRes[i][0]==w&&$.FSRTC.validRes[i][1]==h){return true;}}
+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=[[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;}
+w=resList[resI][0];h=resList[resI][1];resI++;var video={width:{exact:w},height:{exact:h}};if(cam!=="any"){video=assignMediaIdToConstraint(cam,video);}
+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.warn(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);}
+$.FSRTC.checkPerms=function(runtime,check_audio,check_video){getUserMedia({constraints:{audio:check_audio,video:check_video,},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info("media perm init complete");if(runtime){setTimeout(runtime,100,true);}},onerror:function(e){if(check_video&&check_audio){console.error("error, retesting with audio params only");return $.FSRTC.checkPerms(runtime,check_audio,false);}
+console.error("media perm init error");if(runtime){runtime(false)}}});}})(jQuery);(function($){$.JsonRpcClient=function(options){var self=this;this.options=$.extend({ajaxUrl:null,socketUrl:null,onmessage:null,login:null,passwd:null,sessid:null,loginParams:null,userVariables:null,getSocket:function(onmessage_cb){return self._getSocket(onmessage_cb);}},options);self.ws_cnt=0;this.wsOnMessage=function(event){self._wsOnMessage(event);};};$.JsonRpcClient.prototype._ws_socket=null;$.JsonRpcClient.prototype._ws_callbacks={};$.JsonRpcClient.prototype._current_id=1;$.JsonRpcClient.prototype.speedTest=function(bytes,cb){var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this.speedCB=cb;this.speedBytes=bytes;socket.send("#SPU "+bytes);var loops=bytes/1024;var rem=bytes%1024;var i;var data=new Array(1024).join(".");for(i=0;i<loops;i++){socket.send("#SPB "+data);}
+if(rem){socket.send("#SPB "+data);}
+socket.send("#SPE");}};$.JsonRpcClient.prototype.call=function(method,params,success_cb,error_cb){if(!params){params={};}
+if(this.options.sessid){params.sessid=this.options.sessid;}
+var request={jsonrpc:'2.0',method:method,params:params,id:this._current_id++};if(!success_cb){success_cb=function(e){console.log("Success: ",e);};}
+if(!error_cb){error_cb=function(e){console.log("Error: ",e);};}
+var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request,success_cb,error_cb);return;}
+if(this.options.ajaxUrl===null){throw"$.JsonRpcClient.call used with no websocket and no http endpoint.";}
+$.ajax({type:'POST',url:this.options.ajaxUrl,data:$.toJSON(request),dataType:'json',cache:false,success:function(data){if('error'in data)error_cb(data.error,this);success_cb(data.result,this);},error:function(jqXHR,textStatus,errorThrown){try{var response=$.parseJSON(jqXHR.responseText);if('console'in window)console.log(response);error_cb(response.error,this);}catch(err){error_cb({error:jqXHR.responseText},this);}}});};$.JsonRpcClient.prototype.notify=function(method,params){if(this.options.sessid){params.sessid=this.options.sessid;}
+var request={jsonrpc:'2.0',method:method,params:params};var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request);return;}
+if(this.options.ajaxUrl===null){throw"$.JsonRpcClient.notify used with no websocket and no http endpoint.";}
+$.ajax({type:'POST',url:this.options.ajaxUrl,data:$.toJSON(request),dataType:'json',cache:false});};$.JsonRpcClient.prototype.batch=function(callback,all_done_cb,error_cb){var batch=new $.JsonRpcClient._batchObject(this,all_done_cb,error_cb);callback(batch);batch._execute();};$.JsonRpcClient.prototype.socketReady=function(){if(this._ws_socket===null||this._ws_socket.readyState>1){return false;}
+return true;};$.JsonRpcClient.prototype.closeSocket=function(){var self=this;if(self.socketReady()){self._ws_socket.onclose=function(w){console.log("Closing Socket");};self._ws_socket.close();}};$.JsonRpcClient.prototype.loginData=function(params){var self=this;self.options.login=params.login;self.options.passwd=params.passwd;self.options.loginParams=params.loginParams;self.options.userVariables=params.userVariables;};$.JsonRpcClient.prototype.connectSocket=function(onmessage_cb){var self=this;if(self.to){clearTimeout(self.to);}
+if(!self.socketReady()){self.authing=false;if(self._ws_socket){delete self._ws_socket;}
+self._ws_socket=new WebSocket(self.options.socketUrl);if(self._ws_socket){self._ws_socket.onmessage=onmessage_cb;self._ws_socket.onclose=function(w){if(!self.ws_sleep){self.ws_sleep=1000;}
+if(self.options.onWSClose){self.options.onWSClose(self);}
+if(self.ws_cnt>10&&self.options.wsFallbackURL){self.options.socketUrl=self.options.wsFallbackURL;}
+console.error("Websocket Lost "+self.ws_cnt+" sleep: "+self.ws_sleep+"msec");self.to=setTimeout(function(){console.log("Attempting Reconnection....");self.connectSocket(onmessage_cb);},self.ws_sleep);self.ws_cnt++;if(self.ws_sleep<3000&&(self.ws_cnt%10)===0){self.ws_sleep+=1000;}};self._ws_socket.onopen=function(){if(self.to){clearTimeout(self.to);}
+self.ws_sleep=1000;self.ws_cnt=0;if(self.options.onWSConnect){self.options.onWSConnect(self);}
+var req;while((req=$.JsonRpcClient.q.pop())){self._ws_socket.send(req);}};}}
+return self._ws_socket?true:false;};$.JsonRpcClient.prototype.stopRetrying=function(){if(self.to)
+clearTimeout(self.to);}
+$.JsonRpcClient.prototype._getSocket=function(onmessage_cb){if(this.options.socketUrl===null||!("WebSocket"in window))return null;this.connectSocket(onmessage_cb);return this._ws_socket;};$.JsonRpcClient.q=[];$.JsonRpcClient.prototype._wsCall=function(socket,request,success_cb,error_cb){var request_json=$.toJSON(request);if(socket.readyState<1){self=this;$.JsonRpcClient.q.push(request_json);}else{socket.send(request_json);}
+if('id'in request&&typeof success_cb!=='undefined'){this._ws_callbacks[request.id]={request:request_json,request_obj:request,success_cb:success_cb,error_cb:error_cb};}};$.JsonRpcClient.prototype._wsOnMessage=function(event){var response;if(event.data[0]=="#"&&event.data[1]=="S"&&event.data[2]=="P"){if(event.data[3]=="U"){this.up_dur=parseInt(event.data.substring(4));}else if(this.speedCB&&event.data[3]=="D"){this.down_dur=parseInt(event.data.substring(4));var up_kps=(((this.speedBytes*8)/(this.up_dur/1000))/1024).toFixed(0);var down_kps=(((this.speedBytes*8)/(this.down_dur/1000))/1024).toFixed(0);console.info("Speed Test: Up: "+up_kps+" Down: "+down_kps);var cb=this.speedCB;this.speedCB=null;cb(event,{upDur:this.up_dur,downDur:this.down_dur,upKPS:up_kps,downKPS:down_kps});}
+return;}
+try{response=$.parseJSON(event.data);if(typeof response==='object'&&'jsonrpc'in response&&response.jsonrpc==='2.0'){if('result'in response&&this._ws_callbacks[response.id]){var success_cb=this._ws_callbacks[response.id].success_cb;delete this._ws_callbacks[response.id];success_cb(response.result,this);return;}else if('error'in response&&this._ws_callbacks[response.id]){var error_cb=this._ws_callbacks[response.id].error_cb;var orig_req=this._ws_callbacks[response.id].request;if(!self.authing&&response.error.code==-32000&&self.options.login&&self.options.passwd){self.authing=true;this.call("login",{login:self.options.login,passwd:self.options.passwd,loginParams:self.options.loginParams,userVariables:self.options.userVariables},this._ws_callbacks[response.id].request_obj.method=="login"?function(e){self.authing=false;console.log("logged in");delete self._ws_callbacks[response.id];if(self.options.onWSLogin){self.options.onWSLogin(true,self);}}:function(e){self.authing=false;console.log("logged in, resending request id: "+response.id);var socket=self.options.getSocket(self.wsOnMessage);if(socket!==null){socket.send(orig_req);}
+if(self.options.onWSLogin){self.options.onWSLogin(true,self);}},function(e){console.log("error logging in, request id:",response.id);delete self._ws_callbacks[response.id];error_cb(response.error,this);if(self.options.onWSLogin){self.options.onWSLogin(false,self);}});return;}
+delete this._ws_callbacks[response.id];error_cb(response.error,this);return;}}}catch(err){console.log("ERROR: "+err);return;}
+if(typeof this.options.onmessage==='function'){event.eventData=response;if(!event.eventData){event.eventData={};}
+var reply=this.options.onmessage(event);if(reply&&typeof reply==="object"&&event.eventData.id){var msg={jsonrpc:"2.0",id:event.eventData.id,result:reply};var socket=self.options.getSocket(self.wsOnMessage);if(socket!==null){socket.send($.toJSON(msg));}}}};$.JsonRpcClient._batchObject=function(jsonrpcclient,all_done_cb,error_cb){this._requests=[];this.jsonrpcclient=jsonrpcclient;this.all_done_cb=all_done_cb;this.error_cb=typeof error_cb==='function'?error_cb:function(){};};$.JsonRpcClient._batchObject.prototype.call=function(method,params,success_cb,error_cb){if(!params){params={};}
+if(this.options.sessid){params.sessid=this.options.sessid;}
+if(!success_cb){success_cb=function(e){console.log("Success: ",e);};}
+if(!error_cb){error_cb=function(e){console.log("Error: ",e);};}
+this._requests.push({request:{jsonrpc:'2.0',method:method,params:params,id:this.jsonrpcclient._current_id++},success_cb:success_cb,error_cb:error_cb});};$.JsonRpcClient._batchObject.prototype.notify=function(method,params){if(this.options.sessid){params.sessid=this.options.sessid;}
+this._requests.push({request:{jsonrpc:'2.0',method:method,params:params}});};$.JsonRpcClient._batchObject.prototype._execute=function(){var self=this;if(this._requests.length===0)return;var batch_request=[];var handlers={};var i=0;var call;var success_cb;var error_cb;var socket=self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);if(socket!==null){for(i=0;i<this._requests.length;i++){call=this._requests[i];success_cb=('success_cb'in call)?call.success_cb:undefined;error_cb=('error_cb'in call)?call.error_cb:undefined;self.jsonrpcclient._wsCall(socket,call.request,success_cb,error_cb);}
+if(typeof all_done_cb==='function')all_done_cb(result);return;}
+for(i=0;i<this._requests.length;i++){call=this._requests[i];batch_request.push(call.request);if('id'in call.request){handlers[call.request.id]={success_cb:call.success_cb,error_cb:call.error_cb};}}
+success_cb=function(data){self._batchCb(data,handlers,self.all_done_cb);};if(self.jsonrpcclient.options.ajaxUrl===null){throw"$.JsonRpcClient.batch used with no websocket and no http endpoint.";}
+$.ajax({url:self.jsonrpcclient.options.ajaxUrl,data:$.toJSON(batch_request),dataType:'json',cache:false,type:'POST',error:function(jqXHR,textStatus,errorThrown){self.error_cb(jqXHR,textStatus,errorThrown);},success:success_cb});};$.JsonRpcClient._batchObject.prototype._batchCb=function(result,handlers,all_done_cb){for(var i=0;i<result.length;i++){var response=result[i];if('error'in response){if(response.id===null||!(response.id in handlers)){if('console'in window)console.log(response);}else{handlers[response.id].error_cb(response.error,this);}}else{if(!(response.id in handlers)&&'console'in window){console.log(response);}else{handlers[response.id].success_cb(response.result,this);}}}
+if(typeof all_done_cb==='function')all_done_cb(result);};})(jQuery);(function($){var sources=[];var generateGUID=(typeof(window.crypto)!=='undefined'&&typeof(window.crypto.getRandomValues)!=='undefined')?function(){var buf=new Uint16Array(8);window.crypto.getRandomValues(buf);var S4=function(num){var ret=num.toString(16);while(ret.length<4){ret="0"+ret;}
+return ret;};return(S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-"+S4(buf[3])+"-"+S4(buf[4])+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7]));}:function(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){var r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);return v.toString(16);});};$.verto=function(options,callbacks){var verto=this;$.verto.saved.push(verto);verto.options=$.extend({login:null,passwd:null,socketUrl:null,tag:null,localTag:null,videoParams:{},audioParams:{},loginParams:{},deviceParams:{onResCheck:null},userVariables:{},iceServers:false,ringSleep:6000,sessid:null,useStream:null},options);if(verto.options.deviceParams.useCamera){$.FSRTC.getValidRes(verto.options.deviceParams.useCamera,verto.options.deviceParams.onResCheck);}
+if(!verto.options.deviceParams.useMic){verto.options.deviceParams.useMic="any";}
+if(!verto.options.deviceParams.useSpeak){verto.options.deviceParams.useSpeak="any";}
+if(verto.options.sessid){verto.sessid=verto.options.sessid;}else{verto.sessid=localStorage.getItem("verto_session_uuid")||generateGUID();localStorage.setItem("verto_session_uuid",verto.sessid);}
+verto.dialogs={};verto.callbacks=callbacks||{};verto.eventSUBS={};verto.rpcClient=new $.JsonRpcClient({login:verto.options.login,passwd:verto.options.passwd,socketUrl:verto.options.socketUrl,wsFallbackURL:verto.options.wsFallbackURL,turnServer:verto.options.turnServer,loginParams:verto.options.loginParams,userVariables:verto.options.userVariables,sessid:verto.sessid,onmessage:function(e){return verto.handleMessage(e.eventData);},onWSConnect:function(o){o.call('login',{});},onWSLogin:function(success){if(verto.callbacks.onWSLogin){verto.callbacks.onWSLogin(verto,success);}},onWSClose:function(success){if(verto.callbacks.onWSClose){verto.callbacks.onWSClose(verto,success);}
+verto.purge();}});var tag=verto.options.tag;if(typeof(tag)==="function"){tag=tag();}
+if(verto.options.ringFile&&verto.options.tag){verto.ringer=$("#"+tag);}
+verto.rpcClient.call('login',{});};$.verto.prototype.deviceParams=function(obj){var verto=this;for(var i in obj){verto.options.deviceParams[i]=obj[i];}
+if(obj.useCamera){$.FSRTC.getValidRes(verto.options.deviceParams.useCamera,obj?obj.onResCheck:undefined);}};$.verto.prototype.videoParams=function(obj){var verto=this;for(var i in obj){verto.options.videoParams[i]=obj[i];}};$.verto.prototype.iceServers=function(obj){var verto=this;verto.options.iceServers=obj;};$.verto.prototype.loginData=function(params){var verto=this;verto.options.login=params.login;verto.options.passwd=params.passwd;verto.rpcClient.loginData(params);};$.verto.prototype.logout=function(msg){var verto=this;verto.rpcClient.closeSocket();if(verto.callbacks.onWSClose){verto.callbacks.onWSClose(verto,false);}
+verto.purge();};$.verto.prototype.login=function(msg){var verto=this;verto.logout();verto.rpcClient.call('login',{});};$.verto.prototype.message=function(msg){var verto=this;var err=0;if(!msg.to){console.error("Missing To");err++;}
+if(!msg.body){console.error("Missing Body");err++;}
+if(err){return false;}
+verto.sendMethod("verto.info",{msg:msg});return true;};$.verto.prototype.processReply=function(method,success,e){var verto=this;var i;switch(method){case"verto.subscribe":for(i in e.unauthorizedChannels){drop_bad(verto,e.unauthorizedChannels[i]);}
+for(i in e.subscribedChannels){mark_ready(verto,e.subscribedChannels[i]);}
+break;case"verto.unsubscribe":break;}};$.verto.prototype.sendMethod=function(method,params){var verto=this;verto.rpcClient.call(method,params,function(e){verto.processReply(method,true,e);},function(e){verto.processReply(method,false,e);});};function do_sub(verto,channel,obj){}
+function drop_bad(verto,channel){console.error("drop unauthorized channel: "+channel);delete verto.eventSUBS[channel];}
+function mark_ready(verto,channel){for(var j in verto.eventSUBS[channel]){verto.eventSUBS[channel][j].ready=true;console.log("subscribed to channel: "+channel);if(verto.eventSUBS[channel][j].readyHandler){verto.eventSUBS[channel][j].readyHandler(verto,channel);}}}
+var SERNO=1;function do_subscribe(verto,channel,subChannels,sparams){var params=sparams||{};var local=params.local;var obj={eventChannel:channel,userData:params.userData,handler:params.handler,ready:false,readyHandler:params.readyHandler,serno:SERNO++};var isnew=false;if(!verto.eventSUBS[channel]){verto.eventSUBS[channel]=[];subChannels.push(channel);isnew=true;}
+verto.eventSUBS[channel].push(obj);if(local){obj.ready=true;obj.local=true;}
+if(!isnew&&verto.eventSUBS[channel][0].ready){obj.ready=true;if(obj.readyHandler){obj.readyHandler(verto,channel);}}
+return{serno:obj.serno,eventChannel:channel};}
+$.verto.prototype.subscribe=function(channel,sparams){var verto=this;var r=[];var subChannels=[];var params=sparams||{};if(typeof(channel)==="string"){r.push(do_subscribe(verto,channel,subChannels,params));}else{for(var i in channel){r.push(do_subscribe(verto,channel,subChannels,params));}}
+if(subChannels.length){verto.sendMethod("verto.subscribe",{eventChannel:subChannels.length==1?subChannels[0]:subChannels,subParams:params.subParams});}
+return r;};$.verto.prototype.unsubscribe=function(handle){var verto=this;var i;if(!handle){for(i in verto.eventSUBS){if(verto.eventSUBS[i]){verto.unsubscribe(verto.eventSUBS[i]);}}}else{var unsubChannels={};var sendChannels=[];var channel;if(typeof(handle)=="string"){delete verto.eventSUBS[handle];unsubChannels[handle]++;}else{for(i in handle){if(typeof(handle[i])=="string"){channel=handle[i];delete verto.eventSUBS[channel];unsubChannels[channel]++;}else{var repl=[];channel=handle[i].eventChannel;for(var j in verto.eventSUBS[channel]){if(verto.eventSUBS[channel][j].serno==handle[i].serno){}else{repl.push(verto.eventSUBS[channel][j]);}}
+verto.eventSUBS[channel]=repl;if(verto.eventSUBS[channel].length===0){delete verto.eventSUBS[channel];unsubChannels[channel]++;}}}}
+for(var u in unsubChannels){console.log("Sending Unsubscribe for: ",u);sendChannels.push(u);}
+if(sendChannels.length){verto.sendMethod("verto.unsubscribe",{eventChannel:sendChannels.length==1?sendChannels[0]:sendChannels});}}};$.verto.prototype.broadcast=function(channel,params){var verto=this;var msg={eventChannel:channel,data:{}};for(var i in params){msg.data[i]=params[i];}
+verto.sendMethod("verto.broadcast",msg);};$.verto.prototype.purge=function(callID){var verto=this;var x=0;var i;for(i in verto.dialogs){if(!x){console.log("purging dialogs");}
+x++;verto.dialogs[i].setState($.verto.enum.state.purge);}
+for(i in verto.eventSUBS){if(verto.eventSUBS[i]){console.log("purging subscription: "+i);delete verto.eventSUBS[i];}}};$.verto.prototype.hangup=function(callID){var verto=this;if(callID){var dialog=verto.dialogs[callID];if(dialog){dialog.hangup();}}else{for(var i in verto.dialogs){verto.dialogs[i].hangup();}}};$.verto.prototype.newCall=function(args,callbacks){var verto=this;if(!verto.rpcClient.socketReady()){console.error("Not Connected...");return;}
+if(args["useCamera"]){verto.options.deviceParams["useCamera"]=args["useCamera"];verto.options.deviceParams["useCameraLabel"]=args["useCameraLabel"];}
+var dialog=new $.verto.dialog($.verto.enum.direction.outbound,this,args);if(callbacks){dialog.callbacks=callbacks;}
+dialog.invite();return dialog;};$.verto.prototype.handleMessage=function(data){var verto=this;if(!(data&&data.method)){console.error("Invalid Data",data);return;}
+if(data.params.callID){var dialog=verto.dialogs[data.params.callID];if(data.method==="verto.attach"&&dialog){delete dialog.verto.dialogs[dialog.callID];dialog.rtc.stop();dialog=null;}
+if(dialog){switch(data.method){case'verto.bye':dialog.hangup(data.params);break;case'verto.answer':dialog.handleAnswer(data.params);break;case'verto.media':dialog.handleMedia(data.params);break;case'verto.display':dialog.handleDisplay(data.params);break;case'verto.info':dialog.handleInfo(data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",dialog,data.method);break;}}else{switch(data.method){case'verto.attach':data.params.attach=true;if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.useVideo=true;}
+if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
+dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);dialog.setState($.verto.enum.state.recovering);break;case'verto.invite':if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.wantVideo=true;}
+if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
+dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED");break;}}
+return{method:data.method};}else{switch(data.method){case'verto.punt':verto.purge();verto.logout();break;case'verto.event':var list=null;var key=null;if(data.params){key=data.params.eventChannel;}
+if(key){list=verto.eventSUBS[key];if(!list){list=verto.eventSUBS[key.split(".")[0]];}}
+if(!list&&key&&key===verto.sessid){if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.pvtEvent,data.params);}}else if(!list&&key&&verto.dialogs[key]){verto.dialogs[key].sendMessage($.verto.enum.message.pvtEvent,data.params);}else if(!list){if(!key){key="UNDEFINED";}
+console.error("UNSUBBED or invalid EVENT "+key+" IGNORED");}else{for(var i in list){var sub=list[i];if(!sub||!sub.ready){console.error("invalid EVENT for "+key+" IGNORED");}else if(sub.handler){sub.handler(verto,data.params,sub.userData);}else if(verto.callbacks.onEvent){verto.callbacks.onEvent(verto,data.params,sub.userData);}else{console.log("EVENT:",data.params);}}}
+break;case"verto.info":if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.info,data.params.msg);}
+console.debug("MESSAGE from: "+data.params.msg.from,data.params.msg.body);break;case'verto.clientReady':if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.clientReady,data.params);}
+console.debug("CLIENT READY",data.params);break;default:console.error("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",data.method);break;}}};var del_array=function(array,name){var r=[];var len=array.length;for(var i=0;i<len;i++){if(array[i]!=name){r.push(array[i]);}}
+return r;};var hashArray=function(){var vha=this;var hash={};var array=[];vha.reorder=function(a){array=a;var h=hash;hash={};var len=array.length;for(var i=0;i<len;i++){var key=array[i];if(h[key]){hash[key]=h[key];delete h[key];}}
+h=undefined;};vha.clear=function(){hash=undefined;array=undefined;hash={};array=[];};vha.add=function(name,val,insertAt){var redraw=false;if(!hash[name]){if(insertAt===undefined||insertAt<0||insertAt>=array.length){array.push(name);}else{var x=0;var n=[];var len=array.length;for(var i=0;i<len;i++){if(x++==insertAt){n.push(name);}
+n.push(array[i]);}
+array=undefined;array=n;n=undefined;redraw=true;}}
+hash[name]=val;return redraw;};vha.del=function(name){var r=false;if(hash[name]){array=del_array(array,name);delete hash[name];r=true;}else{console.error("can't del nonexistant key "+name);}
+return r;};vha.get=function(name){return hash[name];};vha.order=function(){return array;};vha.hash=function(){return hash;};vha.indexOf=function(name){var len=array.length;for(var i=0;i<len;i++){if(array[i]==name){return i;}}};vha.arrayLen=function(){return array.length;};vha.asArray=function(){var r=[];var len=array.length;for(var i=0;i<len;i++){var key=array[i];r.push(hash[key]);}
+return r;};vha.each=function(cb){var len=array.length;for(var i=0;i<len;i++){cb(array[i],hash[array[i]]);}};vha.dump=function(html){var str="";vha.each(function(name,val){str+="name: "+name+" val: "+JSON.stringify(val)+(html?"<br>":"\n");});return str;};};$.verto.liveArray=function(verto,context,name,config){var la=this;var lastSerno=0;var binding=null;var user_obj=config.userObj;var local=false;hashArray.call(la);la._add=la.add;la._del=la.del;la._reorder=la.reorder;la._clear=la.clear;la.context=context;la.name=name;la.user_obj=user_obj;la.verto=verto;la.broadcast=function(channel,obj){verto.broadcast(channel,obj);};la.errs=0;la.clear=function(){la._clear();lastSerno=0;if(la.onChange){la.onChange(la,{action:"clear"});}};la.checkSerno=function(serno){if(serno<0){return true;}
+if(lastSerno>0&&serno!=(lastSerno+1)){if(la.onErr){la.onErr(la,{lastSerno:lastSerno,serno:serno});}
+la.errs++;console.debug(la.errs);if(la.errs<3){la.bootstrap(la.user_obj);}
+return false;}else{lastSerno=serno;return true;}};la.reorder=function(serno,a){if(la.checkSerno(serno)){la._reorder(a);if(la.onChange){la.onChange(la,{serno:serno,action:"reorder"});}}};la.init=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){if(la.onChange){la.onChange(la,{serno:serno,action:"init",index:index,key:key,data:val});}}};la.bootObj=function(serno,val){if(la.checkSerno(serno)){for(var i in val){la._add(val[i][0],val[i][1]);}
+if(la.onChange){la.onChange(la,{serno:serno,action:"bootObj",data:val,redraw:true});}}};la.add=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){var redraw=la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"add",index:index,key:key,data:val,redraw:redraw});}}};la.modify=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"modify",key:key,data:val,index:index});}}};la.del=function(serno,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){if(index===null||index<0||index===undefined){index=la.indexOf(key);}
+var ok=la._del(key);if(ok&&la.onChange){la.onChange(la,{serno:serno,action:"del",key:key,index:index});}}};var eventHandler=function(v,e,la){var packet=e.data;if(packet.name!=la.name){return;}
+switch(packet.action){case"init":la.init(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"bootObj":la.bootObj(packet.wireSerno,packet.data);break;case"add":la.add(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"modify":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.modify(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);}
+break;case"del":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.del(packet.wireSerno,packet.hashKey,packet.arrIndex);}
+break;case"clear":la.clear();break;case"reorder":la.reorder(packet.wireSerno,packet.order);break;default:if(la.checkSerno(packet.wireSerno)){if(la.onChange){la.onChange(la,{serno:packet.wireSerno,action:packet.action,data:packet.data});}}
+break;}};if(la.context){binding=la.verto.subscribe(la.context,{handler:eventHandler,userData:la,subParams:config.subParams});}
+la.destroy=function(){la._clear();la.verto.unsubscribe(binding);};la.sendCommand=function(cmd,obj){var self=la;self.broadcast(self.context,{liveArray:{command:cmd,context:self.context,name:self.name,obj:obj}});};la.bootstrap=function(obj){var self=la;la.sendCommand("bootstrap",obj);};la.changepage=function(obj){var self=la;self.clear();self.broadcast(self.context,{liveArray:{command:"changepage",context:la.context,name:la.name,obj:obj}});};la.heartbeat=function(obj){var self=la;var callback=function(){self.heartbeat.call(self,obj);};self.broadcast(self.context,{liveArray:{command:"heartbeat",context:self.context,name:self.name,obj:obj}});self.hb_pid=setTimeout(callback,30000);};la.bootstrap(la.user_obj);};$.verto.liveTable=function(verto,context,name,jq,config){var dt;var la=new $.verto.liveArray(verto,context,name,{subParams:config.subParams});var lt=this;lt.liveArray=la;lt.dataTable=dt;lt.verto=verto;lt.destroy=function(){if(dt){dt.fnDestroy();}
+if(la){la.destroy();}
+dt=null;la=null;};la.onErr=function(obj,args){console.error("Error: ",obj,args);};function genRow(data){if(typeof(data[4])==="string"&&data[4].indexOf("{")>-1){var tmp=$.parseJSON(data[4]);data[4]=tmp.oldStatus;data[5]=null;}
+return data;}
+function genArray(obj){var data=obj.asArray();for(var i in data){data[i]=genRow(data[i]);}
+return data;}
+la.onChange=function(obj,args){var index=0;var iserr=0;if(!dt){if(!config.aoColumns){if(args.action!="init"){return;}
+config.aoColumns=[];for(var i in args.data){config.aoColumns.push({"sTitle":args.data[i]});}}
+dt=jq.dataTable(config);}
+if(dt&&(args.action=="del"||args.action=="modify")){index=args.index;if(index===undefined&&args.key){index=la.indexOf(args.key);}
+if(index===undefined){console.error("INVALID PACKET Missing INDEX\n",args);return;}}
+if(config.onChange){config.onChange(obj,args);}
+try{switch(args.action){case"bootObj":if(!args.data){console.error("missing data");return;}
+dt.fnClearTable();dt.fnAddData(genArray(obj));dt.fnAdjustColumnSizing();break;case"add":if(!args.data){console.error("missing data");return;}
+if(args.redraw>-1){dt.fnClearTable();dt.fnAddData(genArray(obj));}else{dt.fnAddData(genRow(args.data));}
+dt.fnAdjustColumnSizing();break;case"modify":if(!args.data){return;}
+dt.fnUpdate(genRow(args.data),index);dt.fnAdjustColumnSizing();break;case"del":dt.fnDeleteRow(index);dt.fnAdjustColumnSizing();break;case"clear":dt.fnClearTable();break;case"reorder":dt.fnClearTable();dt.fnAddData(genArray(obj));break;case"hide":jq.hide();break;case"show":jq.show();break;}}catch(err){console.error("ERROR: "+err);iserr++;}
+if(iserr){obj.errs++;if(obj.errs<3){obj.bootstrap(obj.user_obj);}}else{obj.errs=0;}};la.onChange(la,{action:"init"});};var CONFMAN_SERNO=1;$.verto.conf=function(verto,params){var conf=this;conf.params=$.extend({dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);conf.verto=verto;conf.serno=CONFMAN_SERNO++;createMainModeratorMethods();verto.subscribe(conf.params.laData.modChannel,{handler:function(v,e){if(conf.params.onBroadcast){conf.params.onBroadcast(verto,conf,e.data);}}});verto.subscribe(conf.params.laData.infoChannel,{handler:function(v,e){if(typeof(conf.params.infoCallback)==="function"){conf.params.infoCallback(v,e);}}});verto.subscribe(conf.params.laData.chatChannel,{handler:function(v,e){if(typeof(conf.params.chatCallback)==="function"){conf.params.chatCallback(v,e);}}});};$.verto.conf.prototype.modCommand=function(cmd,id,value){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.conf.prototype.destroy=function(){var conf=this;conf.destroyed=true;conf.params.onBroadcast(conf.verto,conf,'destroy');if(conf.params.laData.modChannel){conf.verto.unsubscribe(conf.params.laData.modChannel);}
+if(conf.params.laData.chatChannel){conf.verto.unsubscribe(conf.params.laData.chatChannel);}
+if(conf.params.laData.infoChannel){conf.verto.unsubscribe(conf.params.laData.infoChannel);}};function createMainModeratorMethods(){$.verto.conf.prototype.listVideoLayouts=function(){this.modCommand("list-videoLayouts",null,null);};$.verto.conf.prototype.play=function(file){this.modCommand("play",null,file);};$.verto.conf.prototype.stop=function(){this.modCommand("stop",null,"all");};$.verto.conf.prototype.deaf=function(memberID){this.modCommand("deaf",parseInt(memberID));};$.verto.conf.prototype.undeaf=function(memberID){this.modCommand("undeaf",parseInt(memberID));};$.verto.conf.prototype.record=function(file){this.modCommand("recording",null,["start",file]);};$.verto.conf.prototype.stopRecord=function(){this.modCommand("recording",null,["stop","all"]);};$.verto.conf.prototype.snapshot=function(file){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-write-png",null,file);};$.verto.conf.prototype.setVideoLayout=function(layout,canvasID){if(!this.params.hasVid){throw'Conference has no video';}
+if(canvasID){this.modCommand("vid-layout",null,[layout,canvasID]);}else{this.modCommand("vid-layout",null,layout);}};$.verto.conf.prototype.kick=function(memberID){this.modCommand("kick",parseInt(memberID));};$.verto.conf.prototype.muteMic=function(memberID){this.modCommand("tmute",parseInt(memberID));};$.verto.conf.prototype.muteVideo=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("tvmute",parseInt(memberID));};$.verto.conf.prototype.presenter=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-res-id",parseInt(memberID),"presenter");};$.verto.conf.prototype.videoFloor=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-floor",parseInt(memberID),"force");};$.verto.conf.prototype.banner=function(memberID,text){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-banner",parseInt(memberID),escape(text));};$.verto.conf.prototype.volumeDown=function(memberID){this.modCommand("volume_out",parseInt(memberID),"down");};$.verto.conf.prototype.volumeUp=function(memberID){this.modCommand("volume_out",parseInt(memberID),"up");};$.verto.conf.prototype.gainDown=function(memberID){this.modCommand("volume_in",parseInt(memberID),"down");};$.verto.conf.prototype.gainUp=function(memberID){this.modCommand("volume_in",parseInt(memberID),"up");};$.verto.conf.prototype.transfer=function(memberID,exten){this.modCommand("transfer",parseInt(memberID),exten);};$.verto.conf.prototype.sendChat=function(message,type){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};}
+$.verto.modfuncs={};$.verto.confMan=function(verto,params){var confMan=this;confMan.params=$.extend({tableID:null,statusID:null,mainModID:null,dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);confMan.verto=verto;confMan.serno=CONFMAN_SERNO++;confMan.canvasCount=confMan.params.laData.canvasCount;function genMainMod(jq){var play_id="play_"+confMan.serno;var stop_id="stop_"+confMan.serno;var recording_id="recording_"+confMan.serno;var snapshot_id="snapshot_"+confMan.serno;var rec_stop_id="recording_stop"+confMan.serno;var div_id="confman_"+confMan.serno;var html="<div id='"+div_id+"'><br>"+"<button class='ctlbtn' id='"+play_id+"'>Play</button>"+"<button class='ctlbtn' id='"+stop_id+"'>Stop</button>"+"<button class='ctlbtn' id='"+recording_id+"'>Record</button>"+"<button class='ctlbtn' id='"+rec_stop_id+"'>Record Stop</button>"+
+(confMan.params.hasVid?"<button class='ctlbtn' id='"+snapshot_id+"'>PNG Snapshot</button>":"")+"<br><br></div>";jq.html(html);$.verto.modfuncs.change_video_layout=function(id,canvas_id){var val=$("#"+id+" option:selected").text();if(val!=="none"){confMan.modCommand("vid-layout",null,[val,canvas_id]);}};if(confMan.params.hasVid){for(var j=0;j<confMan.canvasCount;j++){var vlayout_id="confman_vid_layout_"+j+"_"+confMan.serno;var vlselect_id="confman_vl_select_"+j+"_"+confMan.serno;var vlhtml="<div id='"+vlayout_id+"'><br>"+"<b>Video Layout Canvas "+(j+1)+"</b> <select onChange='$.verto.modfuncs.change_video_layout(\""+vlayout_id+"\", \""+(j+1)+"\")' id='"+vlselect_id+"'></select> "+"<br><br></div>";jq.append(vlhtml);}
+$("#"+snapshot_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("vid-write-png",null,file);}});}
+$("#"+play_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("play",null,file);}});$("#"+stop_id).click(function(){confMan.modCommand("stop",null,"all");});$("#"+recording_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("recording",null,["start",file]);}});$("#"+rec_stop_id).click(function(){confMan.modCommand("recording",null,["stop","all"]);});}
+function genControls(jq,rowid){var x=parseInt(rowid);var kick_id="kick_"+x;var canvas_in_next_id="canvas_in_next_"+x;var canvas_in_prev_id="canvas_in_prev_"+x;var canvas_out_next_id="canvas_out_next_"+x;var canvas_out_prev_id="canvas_out_prev_"+x;var canvas_in_set_id="canvas_in_set_"+x;var canvas_out_set_id="canvas_out_set_"+x;var layer_set_id="layer_set_"+x;var layer_next_id="layer_next_"+x;var layer_prev_id="layer_prev_"+x;var tmute_id="tmute_"+x;var tvmute_id="tvmute_"+x;var vbanner_id="vbanner_"+x;var tvpresenter_id="tvpresenter_"+x;var tvfloor_id="tvfloor_"+x;var box_id="box_"+x;var gainup_id="gain_in_up"+x;var gaindn_id="gain_in_dn"+x;var volup_id="vol_in_up"+x;var voldn_id="vol_in_dn"+x;var transfer_id="transfer"+x;var html="<div id='"+box_id+"'>";html+="<b>General Controls</b><hr noshade>";html+="<button class='ctlbtn' id='"+kick_id+"'>Kick</button>"+"<button class='ctlbtn' id='"+tmute_id+"'>Mute</button>"+"<button class='ctlbtn' id='"+gainup_id+"'>Gain -</button>"+"<button class='ctlbtn' id='"+gaindn_id+"'>Gain +</button>"+"<button class='ctlbtn' id='"+voldn_id+"'>Vol -</button>"+"<button class='ctlbtn' id='"+volup_id+"'>Vol +</button>"+"<button class='ctlbtn' id='"+transfer_id+"'>Transfer</button>";if(confMan.params.hasVid){html+="<br><br><b>Video Controls</b><hr noshade>";html+="<button class='ctlbtn' id='"+tvmute_id+"'>VMute</button>"+"<button class='ctlbtn' id='"+tvpresenter_id+"'>Presenter</button>"+"<button class='ctlbtn' id='"+tvfloor_id+"'>Vid Floor</button>"+"<button class='ctlbtn' id='"+vbanner_id+"'>Banner</button>";if(confMan.canvasCount>1){html+="<br><br><b>Canvas Controls</b><hr noshade>"+"<button class='ctlbtn' id='"+canvas_in_set_id+"'>Set Input Canvas</button>"+"<button class='ctlbtn' id='"+canvas_in_prev_id+"'>Prev Input Canvas</button>"+"<button class='ctlbtn' id='"+canvas_in_next_id+"'>Next Input Canvas</button>"+"<br>"+"<button class='ctlbtn' id='"+canvas_out_set_id+"'>Set Watching Canvas</button>"+"<button class='ctlbtn' id='"+canvas_out_prev_id+"'>Prev Watching Canvas</button>"+"<button class='ctlbtn' id='"+canvas_out_next_id+"'>Next Watching Canvas</button>";}
+html+="<br>"+"<button class='ctlbtn' id='"+layer_set_id+"'>Set Layer</button>"+"<button class='ctlbtn' id='"+layer_prev_id+"'>Prev Layer</button>"+"<button class='ctlbtn' id='"+layer_next_id+"'>Next Layer</button>"+"</div>";}
+jq.html(html);if(!jq.data("mouse")){$("#"+box_id).hide();}
+jq.mouseover(function(e){jq.data({"mouse":true});$("#"+box_id).show();});jq.mouseout(function(e){jq.data({"mouse":false});$("#"+box_id).hide();});$("#"+transfer_id).click(function(){var xten=prompt("Enter Extension");if(xten){confMan.modCommand("transfer",x,xten);}});$("#"+kick_id).click(function(){confMan.modCommand("kick",x);});$("#"+layer_set_id).click(function(){var cid=prompt("Please enter layer ID","");if(cid){confMan.modCommand("vid-layer",x,cid);}});$("#"+layer_next_id).click(function(){confMan.modCommand("vid-layer",x,"next");});$("#"+layer_prev_id).click(function(){confMan.modCommand("vid-layer",x,"prev");});$("#"+canvas_in_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-canvas",x,cid);}});$("#"+canvas_out_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-watching-canvas",x,cid);}});$("#"+canvas_in_next_id).click(function(){confMan.modCommand("vid-canvas",x,"next");});$("#"+canvas_in_prev_id).click(function(){confMan.modCommand("vid-canvas",x,"prev");});$("#"+canvas_out_next_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"next");});$("#"+canvas_out_prev_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"prev");});$("#"+tmute_id).click(function(){confMan.modCommand("tmute",x);});if(confMan.params.hasVid){$("#"+tvmute_id).click(function(){confMan.modCommand("tvmute",x);});$("#"+tvpresenter_id).click(function(){confMan.modCommand("vid-res-id",x,"presenter");});$("#"+tvfloor_id).click(function(){confMan.modCommand("vid-floor",x,"force");});$("#"+vbanner_id).click(function(){var text=prompt("Please enter text","");if(text){confMan.modCommand("vid-banner",x,escape(text));}});}
+$("#"+gainup_id).click(function(){confMan.modCommand("volume_in",x,"up");});$("#"+gaindn_id).click(function(){confMan.modCommand("volume_in",x,"down");});$("#"+volup_id).click(function(){confMan.modCommand("volume_out",x,"up");});$("#"+voldn_id).click(function(){confMan.modCommand("volume_out",x,"down");});return html;}
+var atitle="";var awidth=0;verto.subscribe(confMan.params.laData.infoChannel,{handler:function(v,e){if(typeof(confMan.params.infoCallback)==="function"){confMan.params.infoCallback(v,e);}}});verto.subscribe(confMan.params.laData.chatChannel,{handler:function(v,e){if(typeof(confMan.params.chatCallback)==="function"){confMan.params.chatCallback(v,e);}}});if(confMan.params.laData.role==="moderator"){atitle="Action";awidth=600;if(confMan.params.mainModID){genMainMod($(confMan.params.mainModID));$(confMan.params.displayID).html("Moderator Controls Ready<br><br>");}else{$(confMan.params.mainModID).html("");}
+verto.subscribe(confMan.params.laData.modChannel,{handler:function(v,e){if(confMan.params.onBroadcast){confMan.params.onBroadcast(verto,confMan,e.data);}
+if(e.data["conf-command"]==="list-videoLayouts"){for(var j=0;j<confMan.canvasCount;j++){var vlselect_id="#confman_vl_select_"+j+"_"+confMan.serno;var vlayout_id="#confman_vid_layout_"+j+"_"+confMan.serno;var x=0;var options;$(vlselect_id).selectmenu({});$(vlselect_id).selectmenu("enable");$(vlselect_id).empty();$(vlselect_id).append(new Option("Choose a Layout","none"));if(e.data.responseData){var rdata=[];for(var i in e.data.responseData){rdata.push(e.data.responseData[i].name);}
+options=rdata.sort(function(a,b){var ga=a.substring(0,6)=="group:"?true:false;var gb=b.substring(0,6)=="group:"?true:false;if((ga||gb)&&ga!=gb){return ga?-1:1;}
+return((a==b)?0:((a>b)?1:-1));});for(var i in options){$(vlselect_id).append(new Option(options[i],options[i]));x++;}}
+if(x){$(vlselect_id).selectmenu('refresh',true);}else{$(vlayout_id).hide();}}}else{if(!confMan.destroyed&&confMan.params.displayID){$(confMan.params.displayID).html(e.data.response+"<br><br>");if(confMan.lastTimeout){clearTimeout(confMan.lastTimeout);confMan.lastTimeout=0;}
+confMan.lastTimeout=setTimeout(function(){$(confMan.params.displayID).html(confMan.destroyed?"":"Moderator Controls Ready<br><br>");},4000);}}}});if(confMan.params.hasVid){confMan.modCommand("list-videoLayouts",null,null);}}
+var row_callback=null;if(confMan.params.laData.role==="moderator"){row_callback=function(nRow,aData,iDisplayIndex,iDisplayIndexFull){if(!aData[5]){var $row=$('td:eq(5)',nRow);genControls($row,aData);if(confMan.params.onLaRow){confMan.params.onLaRow(verto,confMan,$row,aData);}}};}
+confMan.lt=new $.verto.liveTable(verto,confMan.params.laData.laChannel,confMan.params.laData.laName,$(confMan.params.tableID),{subParams:{callID:confMan.params.dialog?confMan.params.dialog.callID:null},"onChange":function(obj,args){$(confMan.params.statusID).text("Conference Members: "+" ("+obj.arrayLen()+" Total)");if(confMan.params.onLaChange){confMan.params.onLaChange(verto,confMan,$.verto.enum.confEvent.laChange,obj,args);}},"aaData":[],"aoColumns":[{"sTitle":"ID","sWidth":"50"},{"sTitle":"Number","sWidth":"250"},{"sTitle":"Name","sWidth":"250"},{"sTitle":"Codec","sWidth":"100"},{"sTitle":"Status","sWidth":confMan.params.hasVid?"200px":"150px"},{"sTitle":atitle,"sWidth":awidth,}],"bAutoWidth":true,"bDestroy":true,"bSort":false,"bInfo":false,"bFilter":false,"bLengthChange":false,"bPaginate":false,"iDisplayLength":1400,"oLanguage":{"sEmptyTable":"The Conference is Empty....."},"fnRowCallback":row_callback});};$.verto.confMan.prototype.modCommand=function(cmd,id,value){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.confMan.prototype.sendChat=function(message,type){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};$.verto.confMan.prototype.destroy=function(){var confMan=this;confMan.destroyed=true;if(confMan.lt){confMan.lt.destroy();}
+if(confMan.params.laData.chatChannel){confMan.verto.unsubscribe(confMan.params.laData.chatChannel);}
+if(confMan.params.laData.modChannel){confMan.verto.unsubscribe(confMan.params.laData.modChannel);}
+if(confMan.params.mainModID){$(confMan.params.mainModID).html("");}};$.verto.dialog=function(direction,verto,params){var dialog=this;dialog.params=$.extend({useVideo:verto.options.useVideo,useStereo:verto.options.useStereo,screenShare:false,useCamera:false,useMic:verto.options.deviceParams.useMic,useMicLabel:verto.options.deviceParams.useMicLabel,useSpeak:verto.options.deviceParams.useSpeak,tag:verto.options.tag,localTag:verto.options.localTag,login:verto.options.login,videoParams:verto.options.videoParams,useStream:verto.options.useStream,},params);if(!dialog.params.screenShare){dialog.params.useCamera=verto.options.deviceParams.useCamera;dialog.params.useCameraLabel=verto.options.deviceParams.useCameraLabel;}
+dialog.verto=verto;dialog.direction=direction;dialog.lastState=null;dialog.state=dialog.lastState=$.verto.enum.state.new;dialog.callbacks=verto.callbacks;dialog.answered=false;dialog.attach=params.attach||false;dialog.screenShare=params.screenShare||false;dialog.useCamera=dialog.params.useCamera;dialog.useCameraLabel=dialog.params.useCameraLabel;dialog.useMic=dialog.params.useMic;dialog.useMicLabel=dialog.params.useMicLabel;dialog.useSpeak=dialog.params.useSpeak;if(dialog.params.callID){dialog.callID=dialog.params.callID;}else{dialog.callID=dialog.params.callID=generateGUID();}
+if(typeof(dialog.params.tag)==="function"){dialog.params.tag=dialog.params.tag();}
+if(dialog.params.tag){dialog.audioStream=document.getElementById(dialog.params.tag);if(dialog.params.useVideo){dialog.videoStream=dialog.audioStream;}}
+if(dialog.params.localTag){dialog.localVideo=document.getElementById(dialog.params.localTag);}
+dialog.verto.dialogs[dialog.callID]=dialog;var RTCcallbacks={};if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.params.display_direction==="outbound"){dialog.params.remote_caller_id_name=dialog.params.caller_id_name;dialog.params.remote_caller_id_number=dialog.params.caller_id_number;}else{dialog.params.remote_caller_id_name=dialog.params.callee_id_name;dialog.params.remote_caller_id_number=dialog.params.callee_id_number;}
+if(!dialog.params.remote_caller_id_name){dialog.params.remote_caller_id_name="Nobody";}
+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){if(dialog.callbacks.permissionCallback&&typeof dialog.callbacks.permissionCallback.onGranted==='function'){dialog.callbacks.permissionCallback.onGranted(stream);}
+else if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onGranted==='function'){dialog.verto.options.permissionCallback.onGranted(stream);}
+console.log("stream started");};RTCcallbacks.onRemoteStream=function(rtc,stream){if(typeof dialog.callbacks.onRemoteStream==='function'){dialog.callbacks.onRemoteStream(stream,dialog);}
+console.log("remote stream started");};RTCcallbacks.onError=function(e){if(dialog.callbacks.permissionCallback&&typeof dialog.callbacks.permissionCallback.onDenied==='function'){dialog.callbacks.permissionCallback.onDenied();}
+else 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,useCameraLabel:dialog.useCameraLabel,useMic:dialog.useMic,useMicLabel:dialog.useMicLabel,useSpeak:dialog.useSpeak,turnServer:verto.options.turnServer,useStream:dialog.params.useStream});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((obj.noDialogParams&&i!="callID")){continue;}
+obj.dialogParams[i]=dialog.params[i];}
+delete obj.noDialogParams;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;}
+function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}}
+return id;}
+$.verto.dialog.prototype.setAudioPlaybackDevice=function(sinkId,callback,arg){var dialog=this;var element=dialog.audioStream;if(typeof element.sinkId!=='undefined'){var devname=find_name(sinkId);console.info("Dialog: "+dialog.callID+" Setting speaker:",element,devname);element.setSinkId(sinkId).then(function(){console.log("Dialog: "+dialog.callID+' Success, audio output device attached: '+sinkId);if(callback){callback(true,devname,arg);}}).catch(function(error){var errorMessage=error;if(error.name==='SecurityError'){errorMessage="Dialog: "+dialog.callID+' You need to use HTTPS for selecting audio output '+'device: '+error;}
+if(callback){callback(false,null,arg);}
+console.error(errorMessage);});}else{console.warn("Dialog: "+dialog.callID+' Browser does not support output device selection.');if(callback){callback(false,null,arg);}}}
+$.verto.dialog.prototype.setState=function(state){var dialog=this;if(dialog.state==$.verto.enum.state.ringing){dialog.stopRinging();}
+if(dialog.state==state||!checkStateChange(dialog.state,state)){console.error("Dialog "+dialog.callID+": INVALID state change from "+dialog.state.name+" to "+state.name);dialog.hangup();return false;}
+console.log("Dialog "+dialog.callID+": state change from "+dialog.state.name+" to "+state.name);dialog.lastState=dialog.state;dialog.state=state;if(dialog.callbacks.onDialogState){dialog.callbacks.onDialogState(this);}
+switch(dialog.state){case $.verto.enum.state.early:case $.verto.enum.state.active:var speaker=dialog.useSpeak;console.info("Using Speaker: ",speaker);if(speaker&&speaker!=="any"&&speaker!=="none"){setTimeout(function(){dialog.setAudioPlaybackDevice(speaker);},500);}
+break;case $.verto.enum.state.trying:setTimeout(function(){if(dialog.state==$.verto.enum.state.trying){dialog.setState($.verto.enum.state.hangup);}},30000);break;case $.verto.enum.state.purge:dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.hangup:if(dialog.lastState.val>$.verto.enum.state.requesting.val&&dialog.lastState.val<$.verto.enum.state.hangup.val){dialog.sendMethod("verto.bye",{});}
+dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.destroy:if(typeof(dialog.verto.options.tag)==="function"){$('#'+dialog.params.tag).remove();}
+delete dialog.verto.dialogs[dialog.callID];if(dialog.params.screenShare){dialog.rtc.stopPeer();}else{dialog.rtc.stop();}
+break;}
+return true;};$.verto.dialog.prototype.processReply=function(method,success,e){var dialog=this;switch(method){case"verto.answer":case"verto.attach":if(success){dialog.setState($.verto.enum.state.active);}else{dialog.hangup();}
+break;case"verto.invite":if(success){dialog.setState($.verto.enum.state.trying);}else{dialog.setState($.verto.enum.state.destroy);}
+break;case"verto.bye":dialog.hangup();break;case"verto.modify":if(e.holdState){if(e.holdState=="held"){if(dialog.state!=$.verto.enum.state.held){dialog.setState($.verto.enum.state.held);}}else if(e.holdState=="active"){if(dialog.state!=$.verto.enum.state.active){dialog.setState($.verto.enum.state.active);}}}
+if(success){}
+break;default:break;}};$.verto.dialog.prototype.hangup=function(params){var dialog=this;if(params){if(params.causeCode){dialog.causeCode=params.causeCode;}
+if(params.cause){dialog.cause=params.cause;}}
+if(!dialog.cause&&!dialog.causeCode){dialog.cause="NORMAL_CLEARING";}
+if(dialog.state.val>=$.verto.enum.state.new.val&&dialog.state.val<$.verto.enum.state.hangup.val){dialog.setState($.verto.enum.state.hangup);}else if(dialog.state.val<$.verto.enum.state.destroy){dialog.setState($.verto.enum.state.destroy);}};$.verto.dialog.prototype.stopRinging=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.stop();}};$.verto.dialog.prototype.indicateRing=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.attr("src",dialog.verto.options.ringFile)[0].play();setTimeout(function(){dialog.stopRinging();if(dialog.state==$.verto.enum.state.ringing){dialog.indicateRing();}},dialog.verto.options.ringSleep);}};$.verto.dialog.prototype.ring=function(){var dialog=this;dialog.setState($.verto.enum.state.ringing);dialog.indicateRing();};$.verto.dialog.prototype.useVideo=function(on){var dialog=this;dialog.params.useVideo=on;if(on){dialog.videoStream=dialog.audioStream;}else{dialog.videoStream=null;}
+dialog.rtc.useVideo(dialog.videoStream,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.verto.dialog.prototype.useStereo=function(on){var dialog=this;dialog.params.useStereo=on;dialog.rtc.useStereo(on);};$.verto.dialog.prototype.dtmf=function(digits){var dialog=this;if(digits){dialog.sendMethod("verto.info",{dtmf:digits});}};$.verto.dialog.prototype.rtt=function(obj){var dialog=this;var pobj={};if(!obj){return false;}
+pobj.code=obj.code;pobj.chars=obj.chars;if(pobj.chars||pobj.code){dialog.sendMethod("verto.info",{txt:obj,noDialogParams:true});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.replace=function(replaceCallID,params){var dialog=this;if(replaceCallID){dialog.sendMethod("verto.modify",{action:"replace",replaceCallID:replaceCallID,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;if(!msg.to){console.error("Missing To");err++;}
+if(!msg.body){console.error("Missing Body");err++;}
+if(err){return false;}
+dialog.sendMethod("verto.info",{msg:msg});return true;};$.verto.dialog.prototype.answer=function(params){var dialog=this;if(!dialog.answered){if(!params){params={};}
+params.sdp=dialog.params.sdp;if(params){if(params.useVideo){dialog.useVideo(true);}
+dialog.params.callee_id_name=params.callee_id_name;dialog.params.callee_id_number=params.callee_id_number;if(params.useCamera){dialog.useCamera=params.useCamera;dialog.useCameraLabel=params.useCameraLabel;}
+if(params.useMic){dialog.useMic=params.useMic;dialog.useMic=params.useMicLabel;}
+if(params.useSpeak){dialog.useSpeak=params.useSpeak;}}
+dialog.rtc.createAnswer(params);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(params){var dialog=this;dialog.gotAnswer=true;if(dialog.state.val>=$.verto.enum.state.active.val){return;}
+if(dialog.state.val>=$.verto.enum.state.early.val){dialog.setState($.verto.enum.state.active);}else{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" &lt;":" <")+dialog.params.remote_caller_id_number+(enc?"&gt;":">");return party;};$.verto.dialog.prototype.sendMessage=function(msg,params){var dialog=this;if(dialog.callbacks.onMessage){dialog.callbacks.onMessage(dialog.verto,dialog,msg,params);}};$.verto.dialog.prototype.handleInfo=function(params){var dialog=this;dialog.sendMessage($.verto.enum.message.info,params);};$.verto.dialog.prototype.handleDisplay=function(params){var dialog=this;if(params.display_name){dialog.params.remote_caller_id_name=params.display_name;}
+if(params.display_number){dialog.params.remote_caller_id_number=params.display_number;}
+dialog.sendMessage($.verto.enum.message.display,{});};$.verto.dialog.prototype.handleMedia=function(params){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;}
+dialog.gotEarly=true;dialog.rtc.answer(params.sdp,function(){console.log("Dialog "+dialog.callID+"Establishing early media");dialog.setState($.verto.enum.state.early);if(dialog.gotAnswer){console.log("Dialog "+dialog.callID+"Answering Channel");dialog.setState($.verto.enum.state.active);}},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"EARLY SDP",params.sdp);};$.verto.ENUM=function(s){var i=0,o={};s.split(" ").map(function(x){o[x]={name:x,val:i++};});return Object.freeze(o);};$.verto.enum={};$.verto.enum.states=Object.freeze({new:{requesting:1,recovering:1,ringing:1,destroy:1,answering:1,hangup:1},requesting:{trying:1,hangup:1,active:1},recovering:{answering:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{answering:1,requesting:1,hangup:1,held:1},held:{hangup:1,active:1},early:{hangup:1,active:1},hangup:{destroy:1},destroy:{},purge:{destroy:1}});$.verto.enum.state=$.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent clientReady");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$.verto.unloadJobs=[];var unloadEventName='beforeunload';var iOS=['iPad','iPhone','iPod'].indexOf(navigator.platform)>=0;if(iOS){unloadEventName='pagehide';}
+$(window).bind(unloadEventName,function(){for(var f in $.verto.unloadJobs){$.verto.unloadJobs[f]();}
+if($.verto.haltClosure)
+return $.verto.haltClosure();for(var i in $.verto.saved){var verto=$.verto.saved[i];if(verto){verto.purge();verto.logout();}}
+return $.verto.warnOnUnload;});$.verto.videoDevices=[];$.verto.audioInDevices=[];$.verto.audioOutDevices=[];var checkDevices=function(runtime){console.info("enumerating devices");var aud_in=[],aud_out=[],vid=[];var has_video=0,has_audio=0;var Xstream;function gotDevices(deviceInfos){for(var i=0;i!==deviceInfos.length;++i){var deviceInfo=deviceInfos[i];var text="";console.log(deviceInfo);console.log(deviceInfo.kind+": "+deviceInfo.label+" id = "+deviceInfo.deviceId);if(deviceInfo.kind==='audioinput'){text=deviceInfo.label||'microphone '+(aud_in.length+1);aud_in.push({id:deviceInfo.deviceId,kind:"audio_in",label:text});}else if(deviceInfo.kind==='audiooutput'){text=deviceInfo.label||'speaker '+(aud_out.length+1);aud_out.push({id:deviceInfo.deviceId,kind:"audio_out",label:text});}else if(deviceInfo.kind==='videoinput'){text=deviceInfo.label||'camera '+(vid.length+1);vid.push({id:deviceInfo.deviceId,kind:"video",label:text});}else{console.log('Some other kind of source/device: ',deviceInfo);}}
+$.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);if(Xstream){Xstream.getTracks().forEach(function(track){track.stop();});}
+if(runtime){runtime(true);}}
+function handleError(error){console.log('device enumeration error: ',error);if(runtime)runtime(false);}
+function checkTypes(devs){for(var i=0;i!==devs.length;++i){if(devs[i].kind==='audioinput'){has_audio++;}else if(devs[i].kind==='videoinput'){has_video++;}}
+navigator.getUserMedia({audio:(has_audio>0?true:false),video:(has_video>0?true:false)},function(stream){Xstream=stream;navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);},function(err){console.log("The following error occurred: "+err.name);});}
+navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);};$.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);(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(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){'use strict';var _adapter_factory=require('./adapter_factory.js');var adapter=(0,_adapter_factory.adapterFactory)({window:window});module.exports=adapter;},{"./adapter_factory.js":2}],2:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.adapterFactory=adapterFactory;var _utils=require('./utils');var utils=_interopRequireWildcard(_utils);var _chrome_shim=require('./chrome/chrome_shim');var chromeShim=_interopRequireWildcard(_chrome_shim);var _edge_shim=require('./edge/edge_shim');var edgeShim=_interopRequireWildcard(_edge_shim);var _firefox_shim=require('./firefox/firefox_shim');var firefoxShim=_interopRequireWildcard(_firefox_shim);var _safari_shim=require('./safari/safari_shim');var safariShim=_interopRequireWildcard(_safari_shim);var _common_shim=require('./common_shim');var commonShim=_interopRequireWildcard(_common_shim);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function adapterFactory(){var _ref=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{},window=_ref.window;var options=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{shimChrome:true,shimFirefox:true,shimEdge:true,shimSafari:true};var logging=utils.log;var browserDetails=utils.detectBrowser(window);var adapter={browserDetails:browserDetails,commonShim:commonShim,extractVersion:utils.extractVersion,disableLog:utils.disableLog,disableWarnings:utils.disableWarnings};switch(browserDetails.browser){case'chrome':if(!chromeShim||!chromeShim.shimPeerConnection||!options.shimChrome){logging('Chrome shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming chrome.');adapter.browserShim=chromeShim;chromeShim.shimGetUserMedia(window);chromeShim.shimMediaStream(window);chromeShim.shimPeerConnection(window);chromeShim.shimOnTrack(window);chromeShim.shimAddTrackRemoveTrack(window);chromeShim.shimGetSendersWithDtmf(window);chromeShim.shimSenderReceiverGetStats(window);chromeShim.fixNegotiationNeeded(window);commonShim.shimRTCIceCandidate(window);commonShim.shimConnectionState(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);commonShim.removeAllowExtmapMixed(window);break;case'firefox':if(!firefoxShim||!firefoxShim.shimPeerConnection||!options.shimFirefox){logging('Firefox shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming firefox.');adapter.browserShim=firefoxShim;firefoxShim.shimGetUserMedia(window);firefoxShim.shimPeerConnection(window);firefoxShim.shimOnTrack(window);firefoxShim.shimRemoveStream(window);firefoxShim.shimSenderGetStats(window);firefoxShim.shimReceiverGetStats(window);firefoxShim.shimRTCDataChannel(window);commonShim.shimRTCIceCandidate(window);commonShim.shimConnectionState(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);break;case'edge':if(!edgeShim||!edgeShim.shimPeerConnection||!options.shimEdge){logging('MS edge shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming edge.');adapter.browserShim=edgeShim;edgeShim.shimGetUserMedia(window);edgeShim.shimGetDisplayMedia(window);edgeShim.shimPeerConnection(window);edgeShim.shimReplaceTrack(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);break;case'safari':if(!safariShim||!options.shimSafari){logging('Safari shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming safari.');adapter.browserShim=safariShim;safariShim.shimRTCIceServerUrls(window);safariShim.shimCreateOfferLegacy(window);safariShim.shimCallbacksAPI(window);safariShim.shimLocalStreamsAPI(window);safariShim.shimRemoteStreamsAPI(window);safariShim.shimTrackEventTransceiver(window);safariShim.shimGetUserMedia(window);commonShim.shimRTCIceCandidate(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);commonShim.removeAllowExtmapMixed(window);break;default:logging('Unsupported browser!');break;}
+return adapter;}},{"./chrome/chrome_shim":3,"./common_shim":6,"./edge/edge_shim":7,"./firefox/firefox_shim":11,"./safari/safari_shim":14,"./utils":15}],3:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=exports.shimGetUserMedia=undefined;var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};var _getusermedia=require('./getusermedia');Object.defineProperty(exports,'shimGetUserMedia',{enumerable:true,get:function get(){return _getusermedia.shimGetUserMedia;}});var _getdisplaymedia=require('./getdisplaymedia');Object.defineProperty(exports,'shimGetDisplayMedia',{enumerable:true,get:function get(){return _getdisplaymedia.shimGetDisplayMedia;}});exports.shimMediaStream=shimMediaStream;exports.shimOnTrack=shimOnTrack;exports.shimGetSendersWithDtmf=shimGetSendersWithDtmf;exports.shimSenderReceiverGetStats=shimSenderReceiverGetStats;exports.shimAddTrackRemoveTrackWithNative=shimAddTrackRemoveTrackWithNative;exports.shimAddTrackRemoveTrack=shimAddTrackRemoveTrack;exports.shimPeerConnection=shimPeerConnection;exports.fixNegotiationNeeded=fixNegotiationNeeded;var _utils=require('../utils.js');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function walkStats(stats,base,resultSet){if(!base||resultSet.has(base.id)){return;}
+resultSet.set(base.id,base);Object.keys(base).forEach(function(name){if(name.endsWith('Id')){walkStats(stats,stats.get(base[name]),resultSet);}else if(name.endsWith('Ids')){base[name].forEach(function(id){walkStats(stats,stats.get(id),resultSet);});}});}
+function filterStats(result,track,outbound){var streamStatsType=outbound?'outbound-rtp':'inbound-rtp';var filteredResult=new Map();if(track===null){return filteredResult;}
+var trackStats=[];result.forEach(function(value){if(value.type==='track'&&value.trackIdentifier===track.id){trackStats.push(value);}});trackStats.forEach(function(trackStat){result.forEach(function(stats){if(stats.type===streamStatsType&&stats.trackId===trackStat.id){walkStats(result,stats,filteredResult);}});});return filteredResult;}
+function shimMediaStream(window){window.MediaStream=window.MediaStream||window.webkitMediaStream;}
+function shimOnTrack(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&!('ontrack'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function get(){return this._ontrack;},set:function set(f){if(this._ontrack){this.removeEventListener('track',this._ontrack);}
+this.addEventListener('track',this._ontrack=f);},enumerable:true,configurable:true});var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){var _this=this;if(!this._ontrackpoly){this._ontrackpoly=function(e){e.stream.addEventListener('addtrack',function(te){var receiver=void 0;if(window.RTCPeerConnection.prototype.getReceivers){receiver=_this.getReceivers().find(function(r){return r.track&&r.track.id===te.track.id;});}else{receiver={track:te.track};}
+var event=new Event('track');event.track=te.track;event.receiver=receiver;event.transceiver={receiver:receiver};event.streams=[e.stream];_this.dispatchEvent(event);});e.stream.getTracks().forEach(function(track){var receiver=void 0;if(window.RTCPeerConnection.prototype.getReceivers){receiver=_this.getReceivers().find(function(r){return r.track&&r.track.id===track.id;});}else{receiver={track:track};}
+var event=new Event('track');event.track=track;event.receiver=receiver;event.transceiver={receiver:receiver};event.streams=[e.stream];_this.dispatchEvent(event);});};this.addEventListener('addstream',this._ontrackpoly);}
+return origSetRemoteDescription.apply(this,arguments);};}else{utils.wrapPeerConnectionEvent(window,'track',function(e){if(!e.transceiver){Object.defineProperty(e,'transceiver',{value:{receiver:e.receiver}});}
+return e;});}}
+function shimGetSendersWithDtmf(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&!('getSenders'in window.RTCPeerConnection.prototype)&&'createDTMFSender'in window.RTCPeerConnection.prototype){var shimSenderWithDtmf=function shimSenderWithDtmf(pc,track){return{track:track,get dtmf(){if(this._dtmf===undefined){if(track.kind==='audio'){this._dtmf=pc.createDTMFSender(track);}else{this._dtmf=null;}}
+return this._dtmf;},_pc:pc};};if(!window.RTCPeerConnection.prototype.getSenders){window.RTCPeerConnection.prototype.getSenders=function(){this._senders=this._senders||[];return this._senders.slice();};var origAddTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addTrack=function(track,stream){var sender=origAddTrack.apply(this,arguments);if(!sender){sender=shimSenderWithDtmf(this,track);this._senders.push(sender);}
+return sender;};var origRemoveTrack=window.RTCPeerConnection.prototype.removeTrack;window.RTCPeerConnection.prototype.removeTrack=function(sender){origRemoveTrack.apply(this,arguments);var idx=this._senders.indexOf(sender);if(idx!==-1){this._senders.splice(idx,1);}};}
+var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var _this2=this;this._senders=this._senders||[];origAddStream.apply(this,[stream]);stream.getTracks().forEach(function(track){_this2._senders.push(shimSenderWithDtmf(_this2,track));});};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){var _this3=this;this._senders=this._senders||[];origRemoveStream.apply(this,[stream]);stream.getTracks().forEach(function(track){var sender=_this3._senders.find(function(s){return s.track===track;});if(sender){_this3._senders.splice(_this3._senders.indexOf(sender),1);}});};}else if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&'getSenders'in window.RTCPeerConnection.prototype&&'createDTMFSender'in window.RTCPeerConnection.prototype&&window.RTCRtpSender&&!('dtmf'in window.RTCRtpSender.prototype)){var origGetSenders=window.RTCPeerConnection.prototype.getSenders;window.RTCPeerConnection.prototype.getSenders=function(){var _this4=this;var senders=origGetSenders.apply(this,[]);senders.forEach(function(sender){return sender._pc=_this4;});return senders;};Object.defineProperty(window.RTCRtpSender.prototype,'dtmf',{get:function get(){if(this._dtmf===undefined){if(this.track.kind==='audio'){this._dtmf=this._pc.createDTMFSender(this.track);}else{this._dtmf=null;}}
+return this._dtmf;}});}}
+function shimSenderReceiverGetStats(window){if(!((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&window.RTCRtpSender&&window.RTCRtpReceiver)){return;}
+if(!('getStats'in window.RTCRtpSender.prototype)){var origGetSenders=window.RTCPeerConnection.prototype.getSenders;if(origGetSenders){window.RTCPeerConnection.prototype.getSenders=function(){var _this5=this;var senders=origGetSenders.apply(this,[]);senders.forEach(function(sender){return sender._pc=_this5;});return senders;};}
+var origAddTrack=window.RTCPeerConnection.prototype.addTrack;if(origAddTrack){window.RTCPeerConnection.prototype.addTrack=function(){var sender=origAddTrack.apply(this,arguments);sender._pc=this;return sender;};}
+window.RTCRtpSender.prototype.getStats=function(){var sender=this;return this._pc.getStats().then(function(result){return(filterStats(result,sender.track,true));});};}
+if(!('getStats'in window.RTCRtpReceiver.prototype)){var origGetReceivers=window.RTCPeerConnection.prototype.getReceivers;if(origGetReceivers){window.RTCPeerConnection.prototype.getReceivers=function(){var _this6=this;var receivers=origGetReceivers.apply(this,[]);receivers.forEach(function(receiver){return receiver._pc=_this6;});return receivers;};}
+utils.wrapPeerConnectionEvent(window,'track',function(e){e.receiver._pc=e.srcElement;return e;});window.RTCRtpReceiver.prototype.getStats=function(){var receiver=this;return this._pc.getStats().then(function(result){return filterStats(result,receiver.track,false);});};}
+if(!('getStats'in window.RTCRtpSender.prototype&&'getStats'in window.RTCRtpReceiver.prototype)){return;}
+var origGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(){if(arguments.length>0&&arguments[0]instanceof window.MediaStreamTrack){var track=arguments[0];var sender=void 0;var receiver=void 0;var err=void 0;this.getSenders().forEach(function(s){if(s.track===track){if(sender){err=true;}else{sender=s;}}});this.getReceivers().forEach(function(r){if(r.track===track){if(receiver){err=true;}else{receiver=r;}}
+return r.track===track;});if(err||sender&&receiver){return Promise.reject(new DOMException('There are more than one sender or receiver for the track.','InvalidAccessError'));}else if(sender){return sender.getStats();}else if(receiver){return receiver.getStats();}
+return Promise.reject(new DOMException('There is no sender or receiver for the track.','InvalidAccessError'));}
+return origGetStats.apply(this,arguments);};}
+function shimAddTrackRemoveTrackWithNative(window){window.RTCPeerConnection.prototype.getLocalStreams=function(){var _this7=this;this._shimmedLocalStreams=this._shimmedLocalStreams||{};return Object.keys(this._shimmedLocalStreams).map(function(streamId){return _this7._shimmedLocalStreams[streamId][0];});};var origAddTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(!stream){return origAddTrack.apply(this,arguments);}
+this._shimmedLocalStreams=this._shimmedLocalStreams||{};var sender=origAddTrack.apply(this,arguments);if(!this._shimmedLocalStreams[stream.id]){this._shimmedLocalStreams[stream.id]=[stream,sender];}else if(this._shimmedLocalStreams[stream.id].indexOf(sender)===-1){this._shimmedLocalStreams[stream.id].push(sender);}
+return sender;};var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var _this8=this;this._shimmedLocalStreams=this._shimmedLocalStreams||{};stream.getTracks().forEach(function(track){var alreadyExists=_this8.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}});var existingSenders=this.getSenders();origAddStream.apply(this,arguments);var newSenders=this.getSenders().filter(function(newSender){return existingSenders.indexOf(newSender)===-1;});this._shimmedLocalStreams[stream.id]=[stream].concat(newSenders);};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){this._shimmedLocalStreams=this._shimmedLocalStreams||{};delete this._shimmedLocalStreams[stream.id];return origRemoveStream.apply(this,arguments);};var origRemoveTrack=window.RTCPeerConnection.prototype.removeTrack;window.RTCPeerConnection.prototype.removeTrack=function(sender){var _this9=this;this._shimmedLocalStreams=this._shimmedLocalStreams||{};if(sender){Object.keys(this._shimmedLocalStreams).forEach(function(streamId){var idx=_this9._shimmedLocalStreams[streamId].indexOf(sender);if(idx!==-1){_this9._shimmedLocalStreams[streamId].splice(idx,1);}
+if(_this9._shimmedLocalStreams[streamId].length===1){delete _this9._shimmedLocalStreams[streamId];}});}
+return origRemoveTrack.apply(this,arguments);};}
+function shimAddTrackRemoveTrack(window){if(!window.RTCPeerConnection){return;}
+var browserDetails=utils.detectBrowser(window);if(window.RTCPeerConnection.prototype.addTrack&&browserDetails.version>=65){return shimAddTrackRemoveTrackWithNative(window);}
+var origGetLocalStreams=window.RTCPeerConnection.prototype.getLocalStreams;window.RTCPeerConnection.prototype.getLocalStreams=function(){var _this10=this;var nativeStreams=origGetLocalStreams.apply(this);this._reverseStreams=this._reverseStreams||{};return nativeStreams.map(function(stream){return _this10._reverseStreams[stream.id];});};var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var _this11=this;this._streams=this._streams||{};this._reverseStreams=this._reverseStreams||{};stream.getTracks().forEach(function(track){var alreadyExists=_this11.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}});if(!this._reverseStreams[stream.id]){var newStream=new window.MediaStream(stream.getTracks());this._streams[stream.id]=newStream;this._reverseStreams[newStream.id]=stream;stream=newStream;}
+origAddStream.apply(this,[stream]);};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){this._streams=this._streams||{};this._reverseStreams=this._reverseStreams||{};origRemoveStream.apply(this,[this._streams[stream.id]||stream]);delete this._reverseStreams[this._streams[stream.id]?this._streams[stream.id].id:stream.id];delete this._streams[stream.id];};window.RTCPeerConnection.prototype.addTrack=function(track,stream){var _this12=this;if(this.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');}
+var streams=[].slice.call(arguments,1);if(streams.length!==1||!streams[0].getTracks().find(function(t){return t===track;})){throw new DOMException('The adapter.js addTrack polyfill only supports a single '+' stream which is associated with the specified track.','NotSupportedError');}
+var alreadyExists=this.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}
+this._streams=this._streams||{};this._reverseStreams=this._reverseStreams||{};var oldStream=this._streams[stream.id];if(oldStream){oldStream.addTrack(track);Promise.resolve().then(function(){_this12.dispatchEvent(new Event('negotiationneeded'));});}else{var newStream=new window.MediaStream([track]);this._streams[stream.id]=newStream;this._reverseStreams[newStream.id]=stream;this.addStream(newStream);}
+return this.getSenders().find(function(s){return s.track===track;});};function replaceInternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(internalStream.id,'g'),externalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});}
+function replaceExternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(externalStream.id,'g'),internalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});}
+['createOffer','createAnswer'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var _this13=this;var args=arguments;var isLegacyCall=arguments.length&&typeof arguments[0]==='function';if(isLegacyCall){return nativeMethod.apply(this,[function(description){var desc=replaceInternalStreamId(_this13,description);args[0].apply(null,[desc]);},function(err){if(args[1]){args[1].apply(null,err);}},arguments[2]]);}
+return nativeMethod.apply(this,arguments).then(function(description){return replaceInternalStreamId(_this13,description);});};});var origSetLocalDescription=window.RTCPeerConnection.prototype.setLocalDescription;window.RTCPeerConnection.prototype.setLocalDescription=function(){if(!arguments.length||!arguments[0].type){return origSetLocalDescription.apply(this,arguments);}
+arguments[0]=replaceExternalStreamId(this,arguments[0]);return origSetLocalDescription.apply(this,arguments);};var origLocalDescription=Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype,'localDescription');Object.defineProperty(window.RTCPeerConnection.prototype,'localDescription',{get:function get(){var description=origLocalDescription.get.apply(this);if(description.type===''){return description;}
+return replaceInternalStreamId(this,description);}});window.RTCPeerConnection.prototype.removeTrack=function(sender){var _this14=this;if(this.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');}
+if(!sender._pc){throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.','TypeError');}
+var isLocal=sender._pc===this;if(!isLocal){throw new DOMException('Sender was not created by this connection.','InvalidAccessError');}
+this._streams=this._streams||{};var stream=void 0;Object.keys(this._streams).forEach(function(streamid){var hasTrack=_this14._streams[streamid].getTracks().find(function(track){return sender.track===track;});if(hasTrack){stream=_this14._streams[streamid];}});if(stream){if(stream.getTracks().length===1){this.removeStream(this._reverseStreams[stream.id]);}else{stream.removeTrack(sender.track);}
+this.dispatchEvent(new Event('negotiationneeded'));}};}
+function shimPeerConnection(window){if(!window.RTCPeerConnection&&window.webkitRTCPeerConnection){window.RTCPeerConnection=window.webkitRTCPeerConnection;}
+if(!window.RTCPeerConnection){return;}
+var origGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(selector,successCallback,errorCallback){var _this15=this;var args=arguments;if(arguments.length>0&&typeof selector==='function'){return origGetStats.apply(this,arguments);}
+if(origGetStats.length===0&&(arguments.length===0||typeof arguments[0]!=='function')){return origGetStats.apply(this,[]);}
+var fixChromeStats_=function fixChromeStats_(response){var standardReport={};var reports=response.result();reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:{localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[report.type]||report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name);});standardReport[standardStats.id]=standardStats;});return standardReport;};var makeMapStats=function makeMapStats(stats){return new Map(Object.keys(stats).map(function(key){return[key,stats[key]];}));};if(arguments.length>=2){var successCallbackWrapper_=function successCallbackWrapper_(response){args[1](makeMapStats(fixChromeStats_(response)));};return origGetStats.apply(this,[successCallbackWrapper_,arguments[0]]);}
+return new Promise(function(resolve,reject){origGetStats.apply(_this15,[function(response){resolve(makeMapStats(fixChromeStats_(response)));},reject]);}).then(successCallback,errorCallback);};['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new(method==='addIceCandidate'?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);}
+return Promise.resolve();}
+return nativeAddIceCandidate.apply(this,arguments);};}
+function fixNegotiationNeeded(window){utils.wrapPeerConnectionEvent(window,'negotiationneeded',function(e){var pc=e.target;if(pc.signalingState!=='stable'){return;}
+return e;});}},{"../utils.js":15,"./getdisplaymedia":4,"./getusermedia":5}],4:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=shimGetDisplayMedia;function shimGetDisplayMedia(window,getSourceId){if(window.navigator.mediaDevices&&'getDisplayMedia'in window.navigator.mediaDevices){return;}
+if(!window.navigator.mediaDevices){return;}
+if(typeof getSourceId!=='function'){console.error('shimGetDisplayMedia: getSourceId argument is not '+'a function');return;}
+window.navigator.mediaDevices.getDisplayMedia=function(constraints){return getSourceId(constraints).then(function(sourceId){var widthSpecified=constraints.video&&constraints.video.width;var heightSpecified=constraints.video&&constraints.video.height;var frameRateSpecified=constraints.video&&constraints.video.frameRate;constraints.video={mandatory:{chromeMediaSource:'desktop',chromeMediaSourceId:sourceId,maxFrameRate:frameRateSpecified||3}};if(widthSpecified){constraints.video.mandatory.maxWidth=widthSpecified;}
+if(heightSpecified){constraints.video.mandatory.maxHeight=heightSpecified;}
+return window.navigator.mediaDevices.getUserMedia(constraints);});};}},{}],5:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimGetUserMedia=shimGetUserMedia;var _utils=require('../utils.js');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+var logging=utils.log;function shimGetUserMedia(window){var navigator=window&&window.navigator;if(!navigator.mediaDevices){return;}
+var browserDetails=utils.detectBrowser(window);var constraintsToChrome_=function constraintsToChrome_(c){if((typeof c==='undefined'?'undefined':_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 oldname_(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 shimConstraints_(constraints,func){if(browserDetails.version>=61){return func(constraints);}
+constraints=JSON.parse(JSON.stringify(constraints));if(constraints&&_typeof(constraints.audio)==='object'){var remap=function remap(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};constraints=JSON.parse(JSON.stringify(constraints));remap(constraints.audio,'autoGainControl','googAutoGainControl');remap(constraints.audio,'noiseSuppression','googNoiseSuppression');constraints.audio=constraintsToChrome_(constraints.audio);}
+if(constraints&&_typeof(constraints.video)==='object'){var face=constraints.video.facingMode;face=face&&((typeof face==='undefined'?'undefined':_typeof(face))==='object'?face:{ideal:face});var getSupportedFacingModeLies=browserDetails.version<66;if(face&&(face.exact==='user'||face.exact==='environment'||face.ideal==='user'||face.ideal==='environment')&&!(navigator.mediaDevices.getSupportedConstraints&&navigator.mediaDevices.getSupportedConstraints().facingMode&&!getSupportedFacingModeLies)){delete constraints.video.facingMode;var matches=void 0;if(face.exact==='environment'||face.ideal==='environment'){matches=['back','rear'];}else if(face.exact==='user'||face.ideal==='user'){matches=['front'];}
+if(matches){return navigator.mediaDevices.enumerateDevices().then(function(devices){devices=devices.filter(function(d){return d.kind==='videoinput';});var dev=devices.find(function(d){return matches.some(function(match){return d.label.toLowerCase().includes(match);});});if(!dev&&devices.length&&matches.includes('back')){dev=devices[devices.length-1];}
+if(dev){constraints.video.deviceId=face.exact?{exact:dev.deviceId}:{ideal:dev.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 shimError_(e){if(browserDetails.version>=64){return e;}
+return{name:{PermissionDeniedError:'NotAllowedError',PermissionDismissedError:'NotAllowedError',InvalidStateError:'NotAllowedError',DevicesNotFoundError:'NotFoundError',ConstraintNotSatisfiedError:'OverconstrainedError',TrackStartError:'NotReadableError',MediaDeviceFailedDueToShutdown:'NotAllowedError',MediaDeviceKillSwitchOn:'NotAllowedError',TabCaptureError:'AbortError',ScreenCaptureError:'AbortError',DeviceCaptureError:'AbortError'}[e.name]||e.name,message:e.message,constraint:e.constraint||e.constraintName,toString:function toString(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function getUserMedia_(constraints,onSuccess,onError){shimConstraints_(constraints,function(c){navigator.webkitGetUserMedia(c,onSuccess,function(e){if(onError){onError(shimError_(e));}});});};navigator.getUserMedia=getUserMedia_.bind(navigator);var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(cs){return shimConstraints_(cs,function(c){return origGetUserMedia(c).then(function(stream){if(c.audio&&!stream.getAudioTracks().length||c.video&&!stream.getVideoTracks().length){stream.getTracks().forEach(function(track){track.stop();});throw new DOMException('','NotFoundError');}
+return stream;},function(e){return Promise.reject(shimError_(e));});});};}},{"../utils.js":15}],6:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimRTCIceCandidate=shimRTCIceCandidate;exports.shimMaxMessageSize=shimMaxMessageSize;exports.shimSendThrowTypeError=shimSendThrowTypeError;exports.shimConnectionState=shimConnectionState;exports.removeAllowExtmapMixed=removeAllowExtmapMixed;var _sdp=require('sdp');var _sdp2=_interopRequireDefault(_sdp);var _utils=require('./utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}
+function shimRTCIceCandidate(window){if(!window.RTCIceCandidate||window.RTCIceCandidate&&'foundation'in window.RTCIceCandidate.prototype){return;}
+var NativeRTCIceCandidate=window.RTCIceCandidate;window.RTCIceCandidate=function(args){if((typeof args==='undefined'?'undefined':_typeof(args))==='object'&&args.candidate&&args.candidate.indexOf('a=')===0){args=JSON.parse(JSON.stringify(args));args.candidate=args.candidate.substr(2);}
+if(args.candidate&&args.candidate.length){var nativeCandidate=new NativeRTCIceCandidate(args);var parsedCandidate=_sdp2.default.parseCandidate(args.candidate);var augmentedCandidate=Object.assign(nativeCandidate,parsedCandidate);augmentedCandidate.toJSON=function(){return{candidate:augmentedCandidate.candidate,sdpMid:augmentedCandidate.sdpMid,sdpMLineIndex:augmentedCandidate.sdpMLineIndex,usernameFragment:augmentedCandidate.usernameFragment};};return augmentedCandidate;}
+return new NativeRTCIceCandidate(args);};window.RTCIceCandidate.prototype=NativeRTCIceCandidate.prototype;utils.wrapPeerConnectionEvent(window,'icecandidate',function(e){if(e.candidate){Object.defineProperty(e,'candidate',{value:new window.RTCIceCandidate(e.candidate),writable:'false'});}
+return e;});}
+function shimMaxMessageSize(window){if(window.RTCSctpTransport||!window.RTCPeerConnection){return;}
+var browserDetails=utils.detectBrowser(window);if(!('sctp'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'sctp',{get:function get(){return typeof this._sctp==='undefined'?null:this._sctp;}});}
+var sctpInDescription=function sctpInDescription(description){var sections=_sdp2.default.splitSections(description.sdp);sections.shift();return sections.some(function(mediaSection){var mLine=_sdp2.default.parseMLine(mediaSection);return mLine&&mLine.kind==='application'&&mLine.protocol.indexOf('SCTP')!==-1;});};var getRemoteFirefoxVersion=function getRemoteFirefoxVersion(description){var match=description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);if(match===null||match.length<2){return-1;}
+var version=parseInt(match[1],10);return version!==version?-1:version;};var getCanSendMaxMessageSize=function getCanSendMaxMessageSize(remoteIsFirefox){var canSendMaxMessageSize=65536;if(browserDetails.browser==='firefox'){if(browserDetails.version<57){if(remoteIsFirefox===-1){canSendMaxMessageSize=16384;}else{canSendMaxMessageSize=2147483637;}}else if(browserDetails.version<60){canSendMaxMessageSize=browserDetails.version===57?65535:65536;}else{canSendMaxMessageSize=2147483637;}}
+return canSendMaxMessageSize;};var getMaxMessageSize=function getMaxMessageSize(description,remoteIsFirefox){var maxMessageSize=65536;if(browserDetails.browser==='firefox'&&browserDetails.version===57){maxMessageSize=65535;}
+var match=_sdp2.default.matchPrefix(description.sdp,'a=max-message-size:');if(match.length>0){maxMessageSize=parseInt(match[0].substr(19),10);}else if(browserDetails.browser==='firefox'&&remoteIsFirefox!==-1){maxMessageSize=2147483637;}
+return maxMessageSize;};var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){this._sctp=null;if(sctpInDescription(arguments[0])){var isFirefox=getRemoteFirefoxVersion(arguments[0]);var canSendMMS=getCanSendMaxMessageSize(isFirefox);var remoteMMS=getMaxMessageSize(arguments[0],isFirefox);var maxMessageSize=void 0;if(canSendMMS===0&&remoteMMS===0){maxMessageSize=Number.POSITIVE_INFINITY;}else if(canSendMMS===0||remoteMMS===0){maxMessageSize=Math.max(canSendMMS,remoteMMS);}else{maxMessageSize=Math.min(canSendMMS,remoteMMS);}
+var sctp={};Object.defineProperty(sctp,'maxMessageSize',{get:function get(){return maxMessageSize;}});this._sctp=sctp;}
+return origSetRemoteDescription.apply(this,arguments);};}
+function shimSendThrowTypeError(window){if(!(window.RTCPeerConnection&&'createDataChannel'in window.RTCPeerConnection.prototype)){return;}
+function wrapDcSend(dc,pc){var origDataChannelSend=dc.send;dc.send=function(){var data=arguments[0];var length=data.length||data.size||data.byteLength;if(dc.readyState==='open'&&pc.sctp&&length>pc.sctp.maxMessageSize){throw new TypeError('Message too large (can send a maximum of '+pc.sctp.maxMessageSize+' bytes)');}
+return origDataChannelSend.apply(dc,arguments);};}
+var origCreateDataChannel=window.RTCPeerConnection.prototype.createDataChannel;window.RTCPeerConnection.prototype.createDataChannel=function(){var dataChannel=origCreateDataChannel.apply(this,arguments);wrapDcSend(dataChannel,this);return dataChannel;};utils.wrapPeerConnectionEvent(window,'datachannel',function(e){wrapDcSend(e.channel,e.target);return e;});}
+function shimConnectionState(window){if(!window.RTCPeerConnection||'connectionState'in window.RTCPeerConnection.prototype){return;}
+var proto=window.RTCPeerConnection.prototype;Object.defineProperty(proto,'connectionState',{get:function get(){return{completed:'connected',checking:'connecting'}[this.iceConnectionState]||this.iceConnectionState;},enumerable:true,configurable:true});Object.defineProperty(proto,'onconnectionstatechange',{get:function get(){return this._onconnectionstatechange||null;},set:function set(cb){if(this._onconnectionstatechange){this.removeEventListener('connectionstatechange',this._onconnectionstatechange);delete this._onconnectionstatechange;}
+if(cb){this.addEventListener('connectionstatechange',this._onconnectionstatechange=cb);}},enumerable:true,configurable:true});['setLocalDescription','setRemoteDescription'].forEach(function(method){var origMethod=proto[method];proto[method]=function(){if(!this._connectionstatechangepoly){this._connectionstatechangepoly=function(e){var pc=e.target;if(pc._lastConnectionState!==pc.connectionState){pc._lastConnectionState=pc.connectionState;var newEvent=new Event('connectionstatechange',e);pc.dispatchEvent(newEvent);}
+return e;};this.addEventListener('iceconnectionstatechange',this._connectionstatechangepoly);}
+return origMethod.apply(this,arguments);};});}
+function removeAllowExtmapMixed(window){if(!window.RTCPeerConnection){return;}
+var browserDetails=utils.detectBrowser(window);if(browserDetails.browser==='chrome'&&browserDetails.version>=71){return;}
+var nativeSRD=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(desc){if(desc&&desc.sdp&&desc.sdp.indexOf('\na=extmap-allow-mixed')!==-1){desc.sdp=desc.sdp.split('\n').filter(function(line){return line.trim()!=='a=extmap-allow-mixed';}).join('\n');}
+return nativeSRD.apply(this,arguments);};}},{"./utils":15,"sdp":17}],7:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=exports.shimGetUserMedia=undefined;var _getusermedia=require('./getusermedia');Object.defineProperty(exports,'shimGetUserMedia',{enumerable:true,get:function get(){return _getusermedia.shimGetUserMedia;}});var _getdisplaymedia=require('./getdisplaymedia');Object.defineProperty(exports,'shimGetDisplayMedia',{enumerable:true,get:function get(){return _getdisplaymedia.shimGetDisplayMedia;}});exports.shimPeerConnection=shimPeerConnection;exports.shimReplaceTrack=shimReplaceTrack;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);var _filtericeservers=require('./filtericeservers');var _rtcpeerconnectionShim=require('rtcpeerconnection-shim');var _rtcpeerconnectionShim2=_interopRequireDefault(_rtcpeerconnectionShim);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}
+function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimPeerConnection(window){var browserDetails=utils.detectBrowser(window);if(window.RTCIceGatherer){if(!window.RTCIceCandidate){window.RTCIceCandidate=function(args){return args;};}
+if(!window.RTCSessionDescription){window.RTCSessionDescription=function(args){return args;};}
+if(browserDetails.version<15025){var origMSTEnabled=Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype,'enabled');Object.defineProperty(window.MediaStreamTrack.prototype,'enabled',{set:function set(value){origMSTEnabled.set.call(this,value);var ev=new Event('enabled');ev.enabled=value;this.dispatchEvent(ev);}});}}
+if(window.RTCRtpSender&&!('dtmf'in window.RTCRtpSender.prototype)){Object.defineProperty(window.RTCRtpSender.prototype,'dtmf',{get:function get(){if(this._dtmf===undefined){if(this.track.kind==='audio'){this._dtmf=new window.RTCDtmfSender(this);}else if(this.track.kind==='video'){this._dtmf=null;}}
+return this._dtmf;}});}
+if(window.RTCDtmfSender&&!window.RTCDTMFSender){window.RTCDTMFSender=window.RTCDtmfSender;}
+var RTCPeerConnectionShim=(0,_rtcpeerconnectionShim2.default)(window,browserDetails.version);window.RTCPeerConnection=function(config){if(config&&config.iceServers){config.iceServers=(0,_filtericeservers.filterIceServers)(config.iceServers,browserDetails.version);utils.log('ICE servers after filtering:',config.iceServers);}
+return new RTCPeerConnectionShim(config);};window.RTCPeerConnection.prototype=RTCPeerConnectionShim.prototype;}
+function shimReplaceTrack(window){if(window.RTCRtpSender&&!('replaceTrack'in window.RTCRtpSender.prototype)){window.RTCRtpSender.prototype.replaceTrack=window.RTCRtpSender.prototype.setTrack;}}},{"../utils":15,"./filtericeservers":8,"./getdisplaymedia":9,"./getusermedia":10,"rtcpeerconnection-shim":16}],8:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.filterIceServers=filterIceServers;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function filterIceServers(iceServers,edgeVersion){var hasTurn=false;iceServers=JSON.parse(JSON.stringify(iceServers));return iceServers.filter(function(server){if(server&&(server.urls||server.url)){var urls=server.urls||server.url;if(server.url&&!server.urls){utils.deprecated('RTCIceServer.url','RTCIceServer.urls');}
+var isString=typeof urls==='string';if(isString){urls=[urls];}
+urls=urls.filter(function(url){if(url.indexOf('stun:')===0){return false;}
+var validTurn=url.startsWith('turn')&&!url.startsWith('turn:[')&&url.includes('transport=udp');if(validTurn&&!hasTurn){hasTurn=true;return true;}
+return validTurn&&!hasTurn;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;}});}},{"../utils":15}],9:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=shimGetDisplayMedia;function shimGetDisplayMedia(window){if(!('getDisplayMedia'in window.navigator)){return;}
+if(!window.navigator.mediaDevices){return;}
+if(window.navigator.mediaDevices&&'getDisplayMedia'in window.navigator.mediaDevices){return;}
+window.navigator.mediaDevices.getDisplayMedia=window.navigator.getDisplayMedia.bind(window.navigator.mediaDevices);}},{}],10:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetUserMedia=shimGetUserMedia;function shimGetUserMedia(window){var navigator=window&&window.navigator;var shimError_=function shimError_(e){return{name:{PermissionDeniedError:'NotAllowedError'}[e.name]||e.name,message:e.message,constraint:e.constraint,toString:function toString(){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));});};}},{}],11:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=exports.shimGetUserMedia=undefined;var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};var _getusermedia=require('./getusermedia');Object.defineProperty(exports,'shimGetUserMedia',{enumerable:true,get:function get(){return _getusermedia.shimGetUserMedia;}});var _getdisplaymedia=require('./getdisplaymedia');Object.defineProperty(exports,'shimGetDisplayMedia',{enumerable:true,get:function get(){return _getdisplaymedia.shimGetDisplayMedia;}});exports.shimOnTrack=shimOnTrack;exports.shimPeerConnection=shimPeerConnection;exports.shimSenderGetStats=shimSenderGetStats;exports.shimReceiverGetStats=shimReceiverGetStats;exports.shimRemoveStream=shimRemoveStream;exports.shimRTCDataChannel=shimRTCDataChannel;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimOnTrack(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCTrackEvent&&'receiver'in window.RTCTrackEvent.prototype&&!('transceiver'in window.RTCTrackEvent.prototype)){Object.defineProperty(window.RTCTrackEvent.prototype,'transceiver',{get:function get(){return{receiver:this.receiver};}});}}
+function shimPeerConnection(window){var browserDetails=utils.detectBrowser(window);if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!(window.RTCPeerConnection||window.mozRTCPeerConnection)){return;}
+if(!window.RTCPeerConnection&&window.mozRTCPeerConnection){window.RTCPeerConnection=window.mozRTCPeerConnection;}
+['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new(method==='addIceCandidate'?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);}
+return Promise.resolve();}
+return nativeAddIceCandidate.apply(this,arguments);};var modernStatsTypes={inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'};var nativeGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(selector,onSucc,onErr){return nativeGetStats.apply(this,[selector||null]).then(function(stats){if(browserDetails.version<53&&!onSucc){try{stats.forEach(function(stat){stat.type=modernStatsTypes[stat.type]||stat.type;});}catch(e){if(e.name!=='TypeError'){throw e;}
+stats.forEach(function(stat,i){stats.set(i,Object.assign({},stat,{type:modernStatsTypes[stat.type]||stat.type}));});}}
+return stats;}).then(onSucc,onErr);};}
+function shimSenderGetStats(window){if(!((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&window.RTCRtpSender)){return;}
+if(window.RTCRtpSender&&'getStats'in window.RTCRtpSender.prototype){return;}
+var origGetSenders=window.RTCPeerConnection.prototype.getSenders;if(origGetSenders){window.RTCPeerConnection.prototype.getSenders=function(){var _this=this;var senders=origGetSenders.apply(this,[]);senders.forEach(function(sender){return sender._pc=_this;});return senders;};}
+var origAddTrack=window.RTCPeerConnection.prototype.addTrack;if(origAddTrack){window.RTCPeerConnection.prototype.addTrack=function(){var sender=origAddTrack.apply(this,arguments);sender._pc=this;return sender;};}
+window.RTCRtpSender.prototype.getStats=function(){return this.track?this._pc.getStats(this.track):Promise.resolve(new Map());};}
+function shimReceiverGetStats(window){if(!((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&window.RTCRtpSender)){return;}
+if(window.RTCRtpSender&&'getStats'in window.RTCRtpReceiver.prototype){return;}
+var origGetReceivers=window.RTCPeerConnection.prototype.getReceivers;if(origGetReceivers){window.RTCPeerConnection.prototype.getReceivers=function(){var _this2=this;var receivers=origGetReceivers.apply(this,[]);receivers.forEach(function(receiver){return receiver._pc=_this2;});return receivers;};}
+utils.wrapPeerConnectionEvent(window,'track',function(e){e.receiver._pc=e.srcElement;return e;});window.RTCRtpReceiver.prototype.getStats=function(){return this._pc.getStats(this.track);};}
+function shimRemoveStream(window){if(!window.RTCPeerConnection||'removeStream'in window.RTCPeerConnection.prototype){return;}
+window.RTCPeerConnection.prototype.removeStream=function(stream){var _this3=this;utils.deprecated('removeStream','removeTrack');this.getSenders().forEach(function(sender){if(sender.track&&stream.getTracks().includes(sender.track)){_this3.removeTrack(sender);}});};}
+function shimRTCDataChannel(window){if(window.DataChannel&&!window.RTCDataChannel){window.RTCDataChannel=window.DataChannel;}}},{"../utils":15,"./getdisplaymedia":12,"./getusermedia":13}],12:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=shimGetDisplayMedia;function shimGetDisplayMedia(window,preferredMediaSource){if(window.navigator.mediaDevices&&'getDisplayMedia'in window.navigator.mediaDevices){return;}
+if(!window.navigator.mediaDevices){return;}
+window.navigator.mediaDevices.getDisplayMedia=function(constraints){if(!(constraints&&constraints.video)){var err=new DOMException('getDisplayMedia without video '+'constraints is undefined');err.name='NotFoundError';err.code=8;return Promise.reject(err);}
+if(constraints.video===true){constraints.video={mediaSource:preferredMediaSource};}else{constraints.video.mediaSource=preferredMediaSource;}
+return window.navigator.mediaDevices.getUserMedia(constraints);};}},{}],13:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimGetUserMedia=shimGetUserMedia;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimGetUserMedia(window){var browserDetails=utils.detectBrowser(window);var navigator=window&&window.navigator;var MediaStreamTrack=window&&window.MediaStreamTrack;navigator.getUserMedia=function(constraints,onSuccess,onError){utils.deprecated('navigator.getUserMedia','navigator.mediaDevices.getUserMedia');navigator.mediaDevices.getUserMedia(constraints).then(onSuccess,onError);};if(!(browserDetails.version>55&&'autoGainControl'in navigator.mediaDevices.getSupportedConstraints())){var remap=function remap(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};var nativeGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){if((typeof c==='undefined'?'undefined':_typeof(c))==='object'&&_typeof(c.audio)==='object'){c=JSON.parse(JSON.stringify(c));remap(c.audio,'autoGainControl','mozAutoGainControl');remap(c.audio,'noiseSuppression','mozNoiseSuppression');}
+return nativeGetUserMedia(c);};if(MediaStreamTrack&&MediaStreamTrack.prototype.getSettings){var nativeGetSettings=MediaStreamTrack.prototype.getSettings;MediaStreamTrack.prototype.getSettings=function(){var obj=nativeGetSettings.apply(this,arguments);remap(obj,'mozAutoGainControl','autoGainControl');remap(obj,'mozNoiseSuppression','noiseSuppression');return obj;};}
+if(MediaStreamTrack&&MediaStreamTrack.prototype.applyConstraints){var nativeApplyConstraints=MediaStreamTrack.prototype.applyConstraints;MediaStreamTrack.prototype.applyConstraints=function(c){if(this.kind==='audio'&&(typeof c==='undefined'?'undefined':_typeof(c))==='object'){c=JSON.parse(JSON.stringify(c));remap(c,'autoGainControl','mozAutoGainControl');remap(c,'noiseSuppression','mozNoiseSuppression');}
+return nativeApplyConstraints.apply(this,[c]);};}}}},{"../utils":15}],14:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimLocalStreamsAPI=shimLocalStreamsAPI;exports.shimRemoteStreamsAPI=shimRemoteStreamsAPI;exports.shimCallbacksAPI=shimCallbacksAPI;exports.shimGetUserMedia=shimGetUserMedia;exports.shimConstraints=shimConstraints;exports.shimRTCIceServerUrls=shimRTCIceServerUrls;exports.shimTrackEventTransceiver=shimTrackEventTransceiver;exports.shimCreateOfferLegacy=shimCreateOfferLegacy;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimLocalStreamsAPI(window){if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!window.RTCPeerConnection){return;}
+if(!('getLocalStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getLocalStreams=function(){if(!this._localStreams){this._localStreams=[];}
+return this._localStreams;};}
+if(!('addStream'in window.RTCPeerConnection.prototype)){var _addTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addStream=function(stream){var _this=this;if(!this._localStreams){this._localStreams=[];}
+if(!this._localStreams.includes(stream)){this._localStreams.push(stream);}
+stream.getTracks().forEach(function(track){return _addTrack.call(_this,track,stream);});};window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(stream){if(!this._localStreams){this._localStreams=[stream];}else if(!this._localStreams.includes(stream)){this._localStreams.push(stream);}}
+return _addTrack.call(this,track,stream);};}
+if(!('removeStream'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.removeStream=function(stream){var _this2=this;if(!this._localStreams){this._localStreams=[];}
+var index=this._localStreams.indexOf(stream);if(index===-1){return;}
+this._localStreams.splice(index,1);var tracks=stream.getTracks();this.getSenders().forEach(function(sender){if(tracks.includes(sender.track)){_this2.removeTrack(sender);}});};}}
+function shimRemoteStreamsAPI(window){if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!window.RTCPeerConnection){return;}
+if(!('getRemoteStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[];};}
+if(!('onaddstream'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'onaddstream',{get:function get(){return this._onaddstream;},set:function set(f){var _this3=this;if(this._onaddstream){this.removeEventListener('addstream',this._onaddstream);this.removeEventListener('track',this._onaddstreampoly);}
+this.addEventListener('addstream',this._onaddstream=f);this.addEventListener('track',this._onaddstreampoly=function(e){e.streams.forEach(function(stream){if(!_this3._remoteStreams){_this3._remoteStreams=[];}
+if(_this3._remoteStreams.includes(stream)){return;}
+_this3._remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;_this3.dispatchEvent(event);});});}});var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){var pc=this;if(!this._onaddstreampoly){this.addEventListener('track',this._onaddstreampoly=function(e){e.streams.forEach(function(stream){if(!pc._remoteStreams){pc._remoteStreams=[];}
+if(pc._remoteStreams.indexOf(stream)>=0){return;}
+pc._remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;pc.dispatchEvent(event);});});}
+return origSetRemoteDescription.apply(pc,arguments);};}}
+function shimCallbacksAPI(window){if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!window.RTCPeerConnection){return;}
+var prototype=window.RTCPeerConnection.prototype;var createOffer=prototype.createOffer;var createAnswer=prototype.createAnswer;var setLocalDescription=prototype.setLocalDescription;var setRemoteDescription=prototype.setRemoteDescription;var addIceCandidate=prototype.addIceCandidate;prototype.createOffer=function(successCallback,failureCallback){var options=arguments.length>=2?arguments[2]:arguments[0];var promise=createOffer.apply(this,[options]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.createAnswer=function(successCallback,failureCallback){var options=arguments.length>=2?arguments[2]:arguments[0];var promise=createAnswer.apply(this,[options]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};var withCallback=function withCallback(description,successCallback,failureCallback){var promise=setLocalDescription.apply(this,[description]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setLocalDescription=withCallback;withCallback=function withCallback(description,successCallback,failureCallback){var promise=setRemoteDescription.apply(this,[description]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setRemoteDescription=withCallback;withCallback=function withCallback(candidate,successCallback,failureCallback){var promise=addIceCandidate.apply(this,[candidate]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.addIceCandidate=withCallback;}
+function shimGetUserMedia(window){var navigator=window&&window.navigator;if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){var mediaDevices=navigator.mediaDevices;var _getUserMedia=mediaDevices.getUserMedia.bind(mediaDevices);navigator.mediaDevices.getUserMedia=function(constraints){return _getUserMedia(shimConstraints(constraints));};}
+if(!navigator.getUserMedia&&navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){navigator.getUserMedia=function(constraints,cb,errcb){navigator.mediaDevices.getUserMedia(constraints).then(cb,errcb);}.bind(navigator);}}
+function shimConstraints(constraints){if(constraints&&constraints.video!==undefined){return Object.assign({},constraints,{video:utils.compactObject(constraints.video)});}
+return constraints;}
+function shimRTCIceServerUrls(window){var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i<pcConfig.iceServers.length;i++){var server=pcConfig.iceServers[i];if(!server.hasOwnProperty('urls')&&server.hasOwnProperty('url')){utils.deprecated('RTCIceServer.url','RTCIceServer.urls');server=JSON.parse(JSON.stringify(server));server.urls=server.url;delete server.url;newIceServers.push(server);}else{newIceServers.push(pcConfig.iceServers[i]);}}
+pcConfig.iceServers=newIceServers;}
+return new OrigPeerConnection(pcConfig,pcConstraints);};window.RTCPeerConnection.prototype=OrigPeerConnection.prototype;if('generateCertificate'in window.RTCPeerConnection){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function get(){return OrigPeerConnection.generateCertificate;}});}}
+function shimTrackEventTransceiver(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&'receiver'in window.RTCTrackEvent.prototype&&!window.RTCTransceiver){Object.defineProperty(window.RTCTrackEvent.prototype,'transceiver',{get:function get(){return{receiver:this.receiver};}});}}
+function shimCreateOfferLegacy(window){var origCreateOffer=window.RTCPeerConnection.prototype.createOffer;window.RTCPeerConnection.prototype.createOffer=function(offerOptions){if(offerOptions){if(typeof offerOptions.offerToReceiveAudio!=='undefined'){offerOptions.offerToReceiveAudio=!!offerOptions.offerToReceiveAudio;}
+var audioTransceiver=this.getTransceivers().find(function(transceiver){return transceiver.sender.track&&transceiver.sender.track.kind==='audio';});if(offerOptions.offerToReceiveAudio===false&&audioTransceiver){if(audioTransceiver.direction==='sendrecv'){if(audioTransceiver.setDirection){audioTransceiver.setDirection('sendonly');}else{audioTransceiver.direction='sendonly';}}else if(audioTransceiver.direction==='recvonly'){if(audioTransceiver.setDirection){audioTransceiver.setDirection('inactive');}else{audioTransceiver.direction='inactive';}}}else if(offerOptions.offerToReceiveAudio===true&&!audioTransceiver){this.addTransceiver('audio');}
+if(typeof offerOptions.offerToReceiveVideo!=='undefined'){offerOptions.offerToReceiveVideo=!!offerOptions.offerToReceiveVideo;}
+var videoTransceiver=this.getTransceivers().find(function(transceiver){return transceiver.sender.track&&transceiver.sender.track.kind==='video';});if(offerOptions.offerToReceiveVideo===false&&videoTransceiver){if(videoTransceiver.direction==='sendrecv'){if(videoTransceiver.setDirection){videoTransceiver.setDirection('sendonly');}else{videoTransceiver.direction='sendonly';}}else if(videoTransceiver.direction==='recvonly'){if(videoTransceiver.setDirection){videoTransceiver.setDirection('inactive');}else{videoTransceiver.direction='inactive';}}}else if(offerOptions.offerToReceiveVideo===true&&!videoTransceiver){this.addTransceiver('video');}}
+return origCreateOffer.apply(this,arguments);};}},{"../utils":15}],15:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.extractVersion=extractVersion;exports.wrapPeerConnectionEvent=wrapPeerConnectionEvent;exports.disableLog=disableLog;exports.disableWarnings=disableWarnings;exports.log=log;exports.deprecated=deprecated;exports.detectBrowser=detectBrowser;exports.compactObject=compactObject;function _defineProperty(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true});}else{obj[key]=value;}return obj;}
+var logDisabled_=true;var deprecationWarnings_=true;function extractVersion(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos],10);}
+function wrapPeerConnectionEvent(window,eventNameToWrap,wrapper){if(!window.RTCPeerConnection){return;}
+var proto=window.RTCPeerConnection.prototype;var nativeAddEventListener=proto.addEventListener;proto.addEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap){return nativeAddEventListener.apply(this,arguments);}
+var wrappedCallback=function wrappedCallback(e){var modifiedEvent=wrapper(e);if(modifiedEvent){cb(modifiedEvent);}};this._eventMap=this._eventMap||{};this._eventMap[cb]=wrappedCallback;return nativeAddEventListener.apply(this,[nativeEventName,wrappedCallback]);};var nativeRemoveEventListener=proto.removeEventListener;proto.removeEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap||!this._eventMap||!this._eventMap[cb]){return nativeRemoveEventListener.apply(this,arguments);}
+var unwrappedCb=this._eventMap[cb];delete this._eventMap[cb];return nativeRemoveEventListener.apply(this,[nativeEventName,unwrappedCb]);};Object.defineProperty(proto,'on'+eventNameToWrap,{get:function get(){return this['_on'+eventNameToWrap];},set:function set(cb){if(this['_on'+eventNameToWrap]){this.removeEventListener(eventNameToWrap,this['_on'+eventNameToWrap]);delete this['_on'+eventNameToWrap];}
+if(cb){this.addEventListener(eventNameToWrap,this['_on'+eventNameToWrap]=cb);}},enumerable:true,configurable:true});}
+function disableLog(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+(typeof bool==='undefined'?'undefined':_typeof(bool))+'. Please use a boolean.');}
+logDisabled_=bool;return bool?'adapter.js logging disabled':'adapter.js logging enabled';}
+function disableWarnings(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+(typeof bool==='undefined'?'undefined':_typeof(bool))+'. Please use a boolean.');}
+deprecationWarnings_=!bool;return'adapter.js deprecation warnings '+(bool?'disabled':'enabled');}
+function log(){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'){if(logDisabled_){return;}
+if(typeof console!=='undefined'&&typeof console.log==='function'){console.log.apply(console,arguments);}}}
+function deprecated(oldMethod,newMethod){if(!deprecationWarnings_){return;}
+console.warn(oldMethod+' is deprecated, please use '+newMethod+' instead.');}
+function detectBrowser(window){var navigator=window.navigator;var result={browser:null,version:null};if(typeof window==='undefined'||!window.navigator){result.browser='Not a browser.';return result;}
+if(navigator.mozGetUserMedia){result.browser='firefox';result.version=extractVersion(navigator.userAgent,/Firefox\/(\d+)\./,1);}else if(navigator.webkitGetUserMedia){result.browser='chrome';result.version=extractVersion(navigator.userAgent,/Chrom(e|ium)\/(\d+)\./,2);}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){result.browser='edge';result.version=extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2);}else if(window.RTCPeerConnection&&navigator.userAgent.match(/AppleWebKit\/(\d+)\./)){result.browser='safari';result.version=extractVersion(navigator.userAgent,/AppleWebKit\/(\d+)\./,1);}else{result.browser='Not a supported browser.';return result;}
+return result;}
+function compactObject(data){if((typeof data==='undefined'?'undefined':_typeof(data))!=='object'){return data;}
+return Object.keys(data).reduce(function(accumulator,key){var isObject=_typeof(data[key])==='object';var value=isObject?compactObject(data[key]):data[key];var isEmptyObject=isObject&&!Object.keys(value).length;if(value===undefined||isEmptyObject){return accumulator;}
+return Object.assign(accumulator,_defineProperty({},key,value));},{});}},{}],16:[function(require,module,exports){'use strict';var SDPUtils=require('sdp');function fixStatsType(stat){return{inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[stat.type]||stat.type;}
+function writeMediaSection(transceiver,caps,type,stream,dtlsRole){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),type==='offer'?'actpass':dtlsRole||'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 trackId=transceiver.rtpSender._initialTrackId||transceiver.rtpSender.track.id;transceiver.rtpSender._initialTrackId=trackId;var msid='msid:'+(stream?stream.id:'-')+' '+
+trackId+'\r\n';sdp+='a='+msid;sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' '+msid;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+
+transceiver.sendEncodingParameters[0].ssrc+' '+
+transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}}
+sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' cname:'+SDPUtils.localCName+'\r\n';}
+return sdp;}
+function filterIceServers(iceServers,edgeVersion){var hasTurn=false;iceServers=JSON.parse(JSON.stringify(iceServers));return iceServers.filter(function(server){if(server&&(server.urls||server.url)){var urls=server.urls||server.url;if(server.url&&!server.urls){console.warn('RTCIceServer.url is deprecated! Use urls instead.');}
+var isString=typeof urls==='string';if(isString){urls=[urls];}
+urls=urls.filter(function(url){var validTurn=url.indexOf('turn:')===0&&url.indexOf('transport=udp')!==-1&&url.indexOf('turn:[')===-1&&!hasTurn;if(validTurn){hasTurn=true;return true;}
+return url.indexOf('stun:')===0&&edgeVersion>=14393&&url.indexOf('?transport=udp')===-1;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;}});}
+function getCommonCapabilities(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};var findCodecByPayloadType=function(pt,codecs){pt=parseInt(pt,10);for(var i=0;i<codecs.length;i++){if(codecs[i].payloadType===pt||codecs[i].preferredPayloadType===pt){return codecs[i];}}};var rtxCapabilityMatches=function(lRtx,rRtx,lCodecs,rCodecs){var lCodec=findCodecByPayloadType(lRtx.parameters.apt,lCodecs);var rCodec=findCodecByPayloadType(rRtx.parameters.apt,rCodecs);return lCodec&&rCodec&&lCodec.name.toLowerCase()===rCodec.name.toLowerCase();};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){if(lCodec.name.toLowerCase()==='rtx'&&lCodec.parameters&&rCodec.parameters.apt){if(!rtxCapabilityMatches(lCodec,rCodec,localCapabilities.codecs,remoteCapabilities.codecs)){continue;}}
+rCodec=JSON.parse(JSON.stringify(rCodec));rCodec.numChannels=Math.min(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;}
+function isActionAllowedInSignalingState(action,type,signalingState){return{offer:{setLocalDescription:['stable','have-local-offer'],setRemoteDescription:['stable','have-remote-offer']},answer:{setLocalDescription:['have-remote-offer','have-local-pranswer'],setRemoteDescription:['have-local-offer','have-remote-pranswer']}}[type][action].indexOf(signalingState)!==-1;}
+function maybeAddCandidate(iceTransport,candidate){var alreadyAdded=iceTransport.getRemoteCandidates().find(function(remoteCandidate){return candidate.foundation===remoteCandidate.foundation&&candidate.ip===remoteCandidate.ip&&candidate.port===remoteCandidate.port&&candidate.priority===remoteCandidate.priority&&candidate.protocol===remoteCandidate.protocol&&candidate.type===remoteCandidate.type;});if(!alreadyAdded){iceTransport.addRemoteCandidate(candidate);}
+return!alreadyAdded;}
+function makeError(name,description){var e=new Error(description);e.name=name;e.code={NotSupportedError:9,InvalidStateError:11,InvalidAccessError:15,TypeError:undefined,OperationError:undefined}[name];return e;}
+module.exports=function(window,edgeVersion){function addTrackToStreamAndFireEvent(track,stream){stream.addTrack(track);stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack',{track:track}));}
+function removeTrackFromStreamAndFireEvent(track,stream){stream.removeTrack(track);stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack',{track:track}));}
+function fireAddTrack(pc,track,receiver,streams){var trackEvent=new Event('track');trackEvent.track=track;trackEvent.receiver=receiver;trackEvent.transceiver={receiver:receiver};trackEvent.streams=streams;window.setTimeout(function(){pc._dispatchEvent('track',trackEvent);});}
+var RTCPeerConnection=function(config){var pc=this;var _eventTarget=document.createDocumentFragment();['addEventListener','removeEventListener','dispatchEvent'].forEach(function(method){pc[method]=_eventTarget[method].bind(_eventTarget);});this.canTrickleIceCandidates=null;this.needNegotiation=false;this.localStreams=[];this.remoteStreams=[];this._localDescription=null;this._remoteDescription=null;this.signalingState='stable';this.iceConnectionState='new';this.connectionState='new';this.iceGatheringState='new';config=JSON.parse(JSON.stringify(config||{}));this.usingBundle=config.bundlePolicy==='max-bundle';if(config.rtcpMuxPolicy==='negotiate'){throw(makeError('NotSupportedError','rtcpMuxPolicy \'negotiate\' is not supported'));}else if(!config.rtcpMuxPolicy){config.rtcpMuxPolicy='require';}
+switch(config.iceTransportPolicy){case'all':case'relay':break;default:config.iceTransportPolicy='all';break;}
+switch(config.bundlePolicy){case'balanced':case'max-compat':case'max-bundle':break;default:config.bundlePolicy='balanced';break;}
+config.iceServers=filterIceServers(config.iceServers||[],edgeVersion);this._iceGatherers=[];if(config.iceCandidatePoolSize){for(var i=config.iceCandidatePoolSize;i>0;i--){this._iceGatherers.push(new window.RTCIceGatherer({iceServers:config.iceServers,gatherPolicy:config.iceTransportPolicy}));}}else{config.iceCandidatePoolSize=0;}
+this._config=config;this.transceivers=[];this._sdpSessionId=SDPUtils.generateSessionId();this._sdpSessionVersion=0;this._dtlsRole=undefined;this._isClosed=false;};Object.defineProperty(RTCPeerConnection.prototype,'localDescription',{configurable:true,get:function(){return this._localDescription;}});Object.defineProperty(RTCPeerConnection.prototype,'remoteDescription',{configurable:true,get:function(){return this._remoteDescription;}});RTCPeerConnection.prototype.onicecandidate=null;RTCPeerConnection.prototype.onaddstream=null;RTCPeerConnection.prototype.ontrack=null;RTCPeerConnection.prototype.onremovestream=null;RTCPeerConnection.prototype.onsignalingstatechange=null;RTCPeerConnection.prototype.oniceconnectionstatechange=null;RTCPeerConnection.prototype.onconnectionstatechange=null;RTCPeerConnection.prototype.onicegatheringstatechange=null;RTCPeerConnection.prototype.onnegotiationneeded=null;RTCPeerConnection.prototype.ondatachannel=null;RTCPeerConnection.prototype._dispatchEvent=function(name,event){if(this._isClosed){return;}
+this.dispatchEvent(event);if(typeof this['on'+name]==='function'){this['on'+name](event);}};RTCPeerConnection.prototype._emitGatheringStateChange=function(){var event=new Event('icegatheringstatechange');this._dispatchEvent('icegatheringstatechange',event);};RTCPeerConnection.prototype.getConfiguration=function(){return this._config;};RTCPeerConnection.prototype.getLocalStreams=function(){return this.localStreams;};RTCPeerConnection.prototype.getRemoteStreams=function(){return this.remoteStreams;};RTCPeerConnection.prototype._createTransceiver=function(kind,doNotAdd){var hasBundleTransport=this.transceivers.length>0;var transceiver={track:null,iceGatherer:null,iceTransport:null,dtlsTransport:null,localCapabilities:null,remoteCapabilities:null,rtpSender:null,rtpReceiver:null,kind:kind,mid:null,sendEncodingParameters:null,recvEncodingParameters:null,stream:null,associatedRemoteMediaStreams:[],wantReceive:true};if(this.usingBundle&&hasBundleTransport){transceiver.iceTransport=this.transceivers[0].iceTransport;transceiver.dtlsTransport=this.transceivers[0].dtlsTransport;}else{var transports=this._createIceAndDtlsTransports();transceiver.iceTransport=transports.iceTransport;transceiver.dtlsTransport=transports.dtlsTransport;}
+if(!doNotAdd){this.transceivers.push(transceiver);}
+return transceiver;};RTCPeerConnection.prototype.addTrack=function(track,stream){if(this._isClosed){throw makeError('InvalidStateError','Attempted to call addTrack on a closed peerconnection.');}
+var alreadyExists=this.transceivers.find(function(s){return s.track===track;});if(alreadyExists){throw makeError('InvalidAccessError','Track already exists.');}
+var transceiver;for(var i=0;i<this.transceivers.length;i++){if(!this.transceivers[i].track&&this.transceivers[i].kind===track.kind){transceiver=this.transceivers[i];}}
+if(!transceiver){transceiver=this._createTransceiver(track.kind);}
+this._maybeFireNegotiationNeeded();if(this.localStreams.indexOf(stream)===-1){this.localStreams.push(stream);}
+transceiver.track=track;transceiver.stream=stream;transceiver.rtpSender=new window.RTCRtpSender(track,transceiver.dtlsTransport);return transceiver.rtpSender;};RTCPeerConnection.prototype.addStream=function(stream){var pc=this;if(edgeVersion>=15025){stream.getTracks().forEach(function(track){pc.addTrack(track,stream);});}else{var clonedStream=stream.clone();stream.getTracks().forEach(function(track,idx){var clonedTrack=clonedStream.getTracks()[idx];track.addEventListener('enabled',function(event){clonedTrack.enabled=event.enabled;});});clonedStream.getTracks().forEach(function(track){pc.addTrack(track,clonedStream);});}};RTCPeerConnection.prototype.removeTrack=function(sender){if(this._isClosed){throw makeError('InvalidStateError','Attempted to call removeTrack on a closed peerconnection.');}
+if(!(sender instanceof window.RTCRtpSender)){throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.');}
+var transceiver=this.transceivers.find(function(t){return t.rtpSender===sender;});if(!transceiver){throw makeError('InvalidAccessError','Sender was not created by this connection.');}
+var stream=transceiver.stream;transceiver.rtpSender.stop();transceiver.rtpSender=null;transceiver.track=null;transceiver.stream=null;var localStreams=this.transceivers.map(function(t){return t.stream;});if(localStreams.indexOf(stream)===-1&&this.localStreams.indexOf(stream)>-1){this.localStreams.splice(this.localStreams.indexOf(stream),1);}
+this._maybeFireNegotiationNeeded();};RTCPeerConnection.prototype.removeStream=function(stream){var pc=this;stream.getTracks().forEach(function(track){var sender=pc.getSenders().find(function(s){return s.track===track;});if(sender){pc.removeTrack(sender);}});};RTCPeerConnection.prototype.getSenders=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpSender;}).map(function(transceiver){return transceiver.rtpSender;});};RTCPeerConnection.prototype.getReceivers=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpReceiver;}).map(function(transceiver){return transceiver.rtpReceiver;});};RTCPeerConnection.prototype._createIceGatherer=function(sdpMLineIndex,usingBundle){var pc=this;if(usingBundle&&sdpMLineIndex>0){return this.transceivers[0].iceGatherer;}else if(this._iceGatherers.length){return this._iceGatherers.shift();}
+var iceGatherer=new window.RTCIceGatherer({iceServers:this._config.iceServers,gatherPolicy:this._config.iceTransportPolicy});Object.defineProperty(iceGatherer,'state',{value:'new',writable:true});this.transceivers[sdpMLineIndex].bufferedCandidateEvents=[];this.transceivers[sdpMLineIndex].bufferCandidates=function(event){var end=!event.candidate||Object.keys(event.candidate).length===0;iceGatherer.state=end?'completed':'gathering';if(pc.transceivers[sdpMLineIndex].bufferedCandidateEvents!==null){pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event);}};iceGatherer.addEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);return iceGatherer;};RTCPeerConnection.prototype._gather=function(mid,sdpMLineIndex){var pc=this;var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer.onlocalcandidate){return;}
+var bufferedCandidateEvents=this.transceivers[sdpMLineIndex].bufferedCandidateEvents;this.transceivers[sdpMLineIndex].bufferedCandidateEvents=null;iceGatherer.removeEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);iceGatherer.onlocalcandidate=function(evt){if(pc.usingBundle&&sdpMLineIndex>0){return;}
+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==='new'||iceGatherer.state==='gathering'){iceGatherer.state='completed';}}else{if(iceGatherer.state==='new'){iceGatherer.state='gathering';}
+cand.component=1;cand.ufrag=iceGatherer.getLocalParameters().usernameFragment;var serializedCandidate=SDPUtils.writeCandidate(cand);event.candidate=Object.assign(event.candidate,SDPUtils.parseCandidate(serializedCandidate));event.candidate.candidate=serializedCandidate;event.candidate.toJSON=function(){return{candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex,usernameFragment:event.candidate.usernameFragment};};}
+var sections=SDPUtils.getMediaSections(pc._localDescription.sdp);if(!end){sections[event.candidate.sdpMLineIndex]+='a='+event.candidate.candidate+'\r\n';}else{sections[event.candidate.sdpMLineIndex]+='a=end-of-candidates\r\n';}
+pc._localDescription.sdp=SDPUtils.getDescription(pc._localDescription.sdp)+
+sections.join('');var complete=pc.transceivers.every(function(transceiver){return transceiver.iceGatherer&&transceiver.iceGatherer.state==='completed';});if(pc.iceGatheringState!=='gathering'){pc.iceGatheringState='gathering';pc._emitGatheringStateChange();}
+if(!end){pc._dispatchEvent('icecandidate',event);}
+if(complete){pc._dispatchEvent('icecandidate',new Event('icecandidate'));pc.iceGatheringState='complete';pc._emitGatheringStateChange();}};window.setTimeout(function(){bufferedCandidateEvents.forEach(function(e){iceGatherer.onlocalcandidate(e);});},0);};RTCPeerConnection.prototype._createIceAndDtlsTransports=function(){var pc=this;var iceTransport=new window.RTCIceTransport(null);iceTransport.onicestatechange=function(){pc._updateIceConnectionState();pc._updateConnectionState();};var dtlsTransport=new window.RTCDtlsTransport(iceTransport);dtlsTransport.ondtlsstatechange=function(){pc._updateConnectionState();};dtlsTransport.onerror=function(){Object.defineProperty(dtlsTransport,'state',{value:'failed',writable:true});pc._updateConnectionState();};return{iceTransport:iceTransport,dtlsTransport:dtlsTransport};};RTCPeerConnection.prototype._disposeIceAndDtlsTransports=function(sdpMLineIndex){var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer){delete iceGatherer.onlocalcandidate;delete this.transceivers[sdpMLineIndex].iceGatherer;}
+var iceTransport=this.transceivers[sdpMLineIndex].iceTransport;if(iceTransport){delete iceTransport.onicestatechange;delete this.transceivers[sdpMLineIndex].iceTransport;}
+var dtlsTransport=this.transceivers[sdpMLineIndex].dtlsTransport;if(dtlsTransport){delete dtlsTransport.ondtlsstatechange;delete dtlsTransport.onerror;delete this.transceivers[sdpMLineIndex].dtlsTransport;}};RTCPeerConnection.prototype._transceive=function(transceiver,send,recv){var params=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);if(send&&transceiver.rtpSender){params.encodings=transceiver.sendEncodingParameters;params.rtcp={cname:SDPUtils.localCName,compound:transceiver.rtcpParameters.compound};if(transceiver.recvEncodingParameters.length){params.rtcp.ssrc=transceiver.recvEncodingParameters[0].ssrc;}
+transceiver.rtpSender.send(params);}
+if(recv&&transceiver.rtpReceiver&&params.codecs.length>0){if(transceiver.kind==='video'&&transceiver.recvEncodingParameters&&edgeVersion<15019){transceiver.recvEncodingParameters.forEach(function(p){delete p.rtx;});}
+if(transceiver.recvEncodingParameters.length){params.encodings=transceiver.recvEncodingParameters;}else{params.encodings=[{}];}
+params.rtcp={compound:transceiver.rtcpParameters.compound};if(transceiver.rtcpParameters.cname){params.rtcp.cname=transceiver.rtcpParameters.cname;}
+if(transceiver.sendEncodingParameters.length){params.rtcp.ssrc=transceiver.sendEncodingParameters[0].ssrc;}
+transceiver.rtpReceiver.receive(params);}};RTCPeerConnection.prototype.setLocalDescription=function(description){var pc=this;if(['offer','answer'].indexOf(description.type)===-1){return Promise.reject(makeError('TypeError','Unsupported type "'+description.type+'"'));}
+if(!isActionAllowedInSignalingState('setLocalDescription',description.type,pc.signalingState)||pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set local '+description.type+' in state '+pc.signalingState));}
+var sections;var sessionpart;if(description.type==='offer'){sections=SDPUtils.splitSections(description.sdp);sessionpart=sections.shift();sections.forEach(function(mediaSection,sdpMLineIndex){var caps=SDPUtils.parseRtpParameters(mediaSection);pc.transceivers[sdpMLineIndex].localCapabilities=caps;});pc.transceivers.forEach(function(transceiver,sdpMLineIndex){pc._gather(transceiver.mid,sdpMLineIndex);});}else if(description.type==='answer'){sections=SDPUtils.splitSections(pc._remoteDescription.sdp);sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver=pc.transceivers[sdpMLineIndex];var iceGatherer=transceiver.iceGatherer;var iceTransport=transceiver.iceTransport;var dtlsTransport=transceiver.dtlsTransport;var localCapabilities=transceiver.localCapabilities;var remoteCapabilities=transceiver.remoteCapabilities;var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;if(!rejected&&!transceiver.rejected){var remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);var remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);if(isIceLite){remoteDtlsParameters.role='server';}
+if(!pc.usingBundle||sdpMLineIndex===0){pc._gather(transceiver.mid,sdpMLineIndex);if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,isIceLite?'controlling':'controlled');}
+if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}}
+var params=getCommonCapabilities(localCapabilities,remoteCapabilities);pc._transceive(transceiver,params.codecs.length>0,false);}});}
+pc._localDescription={type:description.type,sdp:description.sdp};if(description.type==='offer'){pc._updateSignalingState('have-local-offer');}else{pc._updateSignalingState('stable');}
+return Promise.resolve();};RTCPeerConnection.prototype.setRemoteDescription=function(description){var pc=this;if(['offer','answer'].indexOf(description.type)===-1){return Promise.reject(makeError('TypeError','Unsupported type "'+description.type+'"'));}
+if(!isActionAllowedInSignalingState('setRemoteDescription',description.type,pc.signalingState)||pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set remote '+description.type+' in state '+pc.signalingState));}
+var streams={};pc.remoteStreams.forEach(function(stream){streams[stream.id]=stream;});var receiverList=[];var sections=SDPUtils.splitSections(description.sdp);var sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;var usingBundle=SDPUtils.matchPrefix(sessionpart,'a=group:BUNDLE ').length>0;pc.usingBundle=usingBundle;var iceOptions=SDPUtils.matchPrefix(sessionpart,'a=ice-options:')[0];if(iceOptions){pc.canTrickleIceCandidates=iceOptions.substr(14).split(' ').indexOf('trickle')>=0;}else{pc.canTrickleIceCandidates=false;}
+sections.forEach(function(mediaSection,sdpMLineIndex){var lines=SDPUtils.splitLines(mediaSection);var kind=SDPUtils.getKind(mediaSection);var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;var protocol=lines[0].substr(2).split(' ')[2];var direction=SDPUtils.getDirection(mediaSection,sessionpart);var remoteMsid=SDPUtils.parseMsid(mediaSection);var mid=SDPUtils.getMid(mediaSection)||SDPUtils.generateIdentifier();if(rejected||(kind==='application'&&(protocol==='DTLS/SCTP'||protocol==='UDP/DTLS/SCTP'))){pc.transceivers[sdpMLineIndex]={mid:mid,kind:kind,protocol:protocol,rejected:true};return;}
+if(!rejected&&pc.transceivers[sdpMLineIndex]&&pc.transceivers[sdpMLineIndex].rejected){pc.transceivers[sdpMLineIndex]=pc._createTransceiver(kind,true);}
+var transceiver;var iceGatherer;var iceTransport;var dtlsTransport;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 rtcpParameters=SDPUtils.parseRtcpParameters(mediaSection);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'||description.type==='answer')&&!rejected&&usingBundle&&sdpMLineIndex>0&&pc.transceivers[sdpMLineIndex]){pc._disposeIceAndDtlsTransports(sdpMLineIndex);pc.transceivers[sdpMLineIndex].iceGatherer=pc.transceivers[0].iceGatherer;pc.transceivers[sdpMLineIndex].iceTransport=pc.transceivers[0].iceTransport;pc.transceivers[sdpMLineIndex].dtlsTransport=pc.transceivers[0].dtlsTransport;if(pc.transceivers[sdpMLineIndex].rtpSender){pc.transceivers[sdpMLineIndex].rtpSender.setTransport(pc.transceivers[0].dtlsTransport);}
+if(pc.transceivers[sdpMLineIndex].rtpReceiver){pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(pc.transceivers[0].dtlsTransport);}}
+if(description.type==='offer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex]||pc._createTransceiver(kind);transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,usingBundle);}
+if(cands.length&&transceiver.iceTransport.state==='new'){if(isComplete&&(!usingBundle||sdpMLineIndex===0)){transceiver.iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}}
+localCapabilities=window.RTCRtpReceiver.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});}
+sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+2)*1001}];var isNewTrack=false;if(direction==='sendrecv'||direction==='sendonly'){isNewTrack=!transceiver.rtpReceiver;rtpReceiver=transceiver.rtpReceiver||new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);if(isNewTrack){var stream;track=rtpReceiver.track;if(remoteMsid&&remoteMsid.stream==='-'){}else if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();Object.defineProperty(streams[remoteMsid.stream],'id',{get:function(){return remoteMsid.stream;}});}
+Object.defineProperty(track,'id',{get:function(){return remoteMsid.track;}});stream=streams[remoteMsid.stream];}else{if(!streams.default){streams.default=new window.MediaStream();}
+stream=streams.default;}
+if(stream){addTrackToStreamAndFireEvent(track,stream);transceiver.associatedRemoteMediaStreams.push(stream);}
+receiverList.push([track,rtpReceiver,stream]);}}else if(transceiver.rtpReceiver&&transceiver.rtpReceiver.track){transceiver.associatedRemoteMediaStreams.forEach(function(s){var nativeTrack=s.getTracks().find(function(t){return t.id===transceiver.rtpReceiver.track.id;});if(nativeTrack){removeTrackFromStreamAndFireEvent(nativeTrack,s);}});transceiver.associatedRemoteMediaStreams=[];}
+transceiver.localCapabilities=localCapabilities;transceiver.remoteCapabilities=remoteCapabilities;transceiver.rtpReceiver=rtpReceiver;transceiver.rtcpParameters=rtcpParameters;transceiver.sendEncodingParameters=sendEncodingParameters;transceiver.recvEncodingParameters=recvEncodingParameters;pc._transceive(pc.transceivers[sdpMLineIndex],false,isNewTrack);}else if(description.type==='answer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex];iceGatherer=transceiver.iceGatherer;iceTransport=transceiver.iceTransport;dtlsTransport=transceiver.dtlsTransport;rtpReceiver=transceiver.rtpReceiver;sendEncodingParameters=transceiver.sendEncodingParameters;localCapabilities=transceiver.localCapabilities;pc.transceivers[sdpMLineIndex].recvEncodingParameters=recvEncodingParameters;pc.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities;pc.transceivers[sdpMLineIndex].rtcpParameters=rtcpParameters;if(cands.length&&iceTransport.state==='new'){if((isIceLite||isComplete)&&(!usingBundle||sdpMLineIndex===0)){iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}}
+if(!usingBundle||sdpMLineIndex===0){if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,'controlling');}
+if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}}
+var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;}
+pc._transceive(transceiver,direction==='sendrecv'||direction==='recvonly',direction==='sendrecv'||direction==='sendonly');if(rtpReceiver&&(direction==='sendrecv'||direction==='sendonly')){track=rtpReceiver.track;if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();}
+addTrackToStreamAndFireEvent(track,streams[remoteMsid.stream]);receiverList.push([track,rtpReceiver,streams[remoteMsid.stream]]);}else{if(!streams.default){streams.default=new window.MediaStream();}
+addTrackToStreamAndFireEvent(track,streams.default);receiverList.push([track,rtpReceiver,streams.default]);}}else{delete transceiver.rtpReceiver;}}});if(pc._dtlsRole===undefined){pc._dtlsRole=description.type==='offer'?'active':'passive';}
+pc._remoteDescription={type:description.type,sdp:description.sdp};if(description.type==='offer'){pc._updateSignalingState('have-remote-offer');}else{pc._updateSignalingState('stable');}
+Object.keys(streams).forEach(function(sid){var stream=streams[sid];if(stream.getTracks().length){if(pc.remoteStreams.indexOf(stream)===-1){pc.remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;window.setTimeout(function(){pc._dispatchEvent('addstream',event);});}
+receiverList.forEach(function(item){var track=item[0];var receiver=item[1];if(stream.id!==item[2].id){return;}
+fireAddTrack(pc,track,receiver,[stream]);});}});receiverList.forEach(function(item){if(item[2]){return;}
+fireAddTrack(pc,item[0],item[1],[]);});window.setTimeout(function(){if(!(pc&&pc.transceivers)){return;}
+pc.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.iceTransport.state==='new'&&transceiver.iceTransport.getRemoteCandidates().length>0){console.warn('Timeout for addRemoteCandidate. Consider sending '+'an end-of-candidates notification');transceiver.iceTransport.addRemoteCandidate({});}});},4000);return Promise.resolve();};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._isClosed=true;this._updateSignalingState('closed');};RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState;var event=new Event('signalingstatechange');this._dispatchEvent('signalingstatechange',event);};RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){var pc=this;if(this.signalingState!=='stable'||this.needNegotiation===true){return;}
+this.needNegotiation=true;window.setTimeout(function(){if(pc.needNegotiation){pc.needNegotiation=false;var event=new Event('negotiationneeded');pc._dispatchEvent('negotiationneeded',event);}},0);};RTCPeerConnection.prototype._updateIceConnectionState=function(){var newState;var states={'new':0,closed:0,checking:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&!transceiver.rejected){states[transceiver.iceTransport.state]++;}});newState='new';if(states.failed>0){newState='failed';}else if(states.checking>0){newState='checking';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0){newState='connected';}else if(states.completed>0){newState='completed';}
+if(newState!==this.iceConnectionState){this.iceConnectionState=newState;var event=new Event('iceconnectionstatechange');this._dispatchEvent('iceconnectionstatechange',event);}};RTCPeerConnection.prototype._updateConnectionState=function(){var newState;var states={'new':0,closed:0,connecting:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.dtlsTransport&&!transceiver.rejected){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){newState='connecting';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0){newState='connected';}
+if(newState!==this.connectionState){this.connectionState=newState;var event=new Event('connectionstatechange');this._dispatchEvent('connectionstatechange',event);}};RTCPeerConnection.prototype.createOffer=function(){var pc=this;if(pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createOffer after close'));}
+var numAudioTracks=pc.transceivers.filter(function(t){return t.kind==='audio';}).length;var numVideoTracks=pc.transceivers.filter(function(t){return t.kind==='video';}).length;var offerOptions=arguments[0];if(offerOptions){if(offerOptions.mandatory||offerOptions.optional){throw new TypeError('Legacy mandatory/optional constraints not supported.');}
+if(offerOptions.offerToReceiveAudio!==undefined){if(offerOptions.offerToReceiveAudio===true){numAudioTracks=1;}else if(offerOptions.offerToReceiveAudio===false){numAudioTracks=0;}else{numAudioTracks=offerOptions.offerToReceiveAudio;}}
+if(offerOptions.offerToReceiveVideo!==undefined){if(offerOptions.offerToReceiveVideo===true){numVideoTracks=1;}else if(offerOptions.offerToReceiveVideo===false){numVideoTracks=0;}else{numVideoTracks=offerOptions.offerToReceiveVideo;}}}
+pc.transceivers.forEach(function(transceiver){if(transceiver.kind==='audio'){numAudioTracks--;if(numAudioTracks<0){transceiver.wantReceive=false;}}else if(transceiver.kind==='video'){numVideoTracks--;if(numVideoTracks<0){transceiver.wantReceive=false;}}});while(numAudioTracks>0||numVideoTracks>0){if(numAudioTracks>0){pc._createTransceiver('audio');numAudioTracks--;}
+if(numVideoTracks>0){pc._createTransceiver('video');numVideoTracks--;}}
+var sdp=SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,pc._sdpSessionVersion++);pc.transceivers.forEach(function(transceiver,sdpMLineIndex){var track=transceiver.track;var kind=transceiver.kind;var mid=transceiver.mid||SDPUtils.generateIdentifier();transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,pc.usingBundle);}
+var localCapabilities=window.RTCRtpSender.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});}
+localCapabilities.codecs.forEach(function(codec){if(codec.name==='H264'&&codec.parameters['level-asymmetry-allowed']===undefined){codec.parameters['level-asymmetry-allowed']='1';}
+if(transceiver.remoteCapabilities&&transceiver.remoteCapabilities.codecs){transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec){if(codec.name.toLowerCase()===remoteCodec.name.toLowerCase()&&codec.clockRate===remoteCodec.clockRate){codec.preferredPayloadType=remoteCodec.payloadType;}});}});localCapabilities.headerExtensions.forEach(function(hdrExt){var remoteExtensions=transceiver.remoteCapabilities&&transceiver.remoteCapabilities.headerExtensions||[];remoteExtensions.forEach(function(rHdrExt){if(hdrExt.uri===rHdrExt.uri){hdrExt.id=rHdrExt.id;}});});var sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+1)*1001}];if(track){if(edgeVersion>=15019&&kind==='video'&&!sendEncodingParameters[0].rtx){sendEncodingParameters[0].rtx={ssrc:sendEncodingParameters[0].ssrc+1};}}
+if(transceiver.wantReceive){transceiver.rtpReceiver=new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);}
+transceiver.localCapabilities=localCapabilities;transceiver.sendEncodingParameters=sendEncodingParameters;});if(pc._config.bundlePolicy!=='max-compat'){sdp+='a=group:BUNDLE '+pc.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';}
+sdp+='a=ice-options:trickle\r\n';pc.transceivers.forEach(function(transceiver,sdpMLineIndex){sdp+=writeMediaSection(transceiver,transceiver.localCapabilities,'offer',transceiver.stream,pc._dtlsRole);sdp+='a=rtcp-rsize\r\n';if(transceiver.iceGatherer&&pc.iceGatheringState!=='new'&&(sdpMLineIndex===0||!pc.usingBundle)){transceiver.iceGatherer.getLocalCandidates().forEach(function(cand){cand.component=1;sdp+='a='+SDPUtils.writeCandidate(cand)+'\r\n';});if(transceiver.iceGatherer.state==='completed'){sdp+='a=end-of-candidates\r\n';}}});var desc=new window.RTCSessionDescription({type:'offer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.createAnswer=function(){var pc=this;if(pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createAnswer after close'));}
+if(!(pc.signalingState==='have-remote-offer'||pc.signalingState==='have-local-pranswer')){return Promise.reject(makeError('InvalidStateError','Can not call createAnswer in signalingState '+pc.signalingState));}
+var sdp=SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,pc._sdpSessionVersion++);if(pc.usingBundle){sdp+='a=group:BUNDLE '+pc.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';}
+sdp+='a=ice-options:trickle\r\n';var mediaSectionsInOffer=SDPUtils.getMediaSections(pc._remoteDescription.sdp).length;pc.transceivers.forEach(function(transceiver,sdpMLineIndex){if(sdpMLineIndex+1>mediaSectionsInOffer){return;}
+if(transceiver.rejected){if(transceiver.kind==='application'){if(transceiver.protocol==='DTLS/SCTP'){sdp+='m=application 0 DTLS/SCTP 5000\r\n';}else{sdp+='m=application 0 '+transceiver.protocol+' webrtc-datachannel\r\n';}}else if(transceiver.kind==='audio'){sdp+='m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n'+'a=rtpmap:0 PCMU/8000\r\n';}else if(transceiver.kind==='video'){sdp+='m=video 0 UDP/TLS/RTP/SAVPF 120\r\n'+'a=rtpmap:120 VP8/90000\r\n';}
+sdp+='c=IN IP4 0.0.0.0\r\n'+'a=inactive\r\n'+'a=mid:'+transceiver.mid+'\r\n';return;}
+if(transceiver.stream){var localTrack;if(transceiver.kind==='audio'){localTrack=transceiver.stream.getAudioTracks()[0];}else if(transceiver.kind==='video'){localTrack=transceiver.stream.getVideoTracks()[0];}
+if(localTrack){if(edgeVersion>=15019&&transceiver.kind==='video'&&!transceiver.sendEncodingParameters[0].rtx){transceiver.sendEncodingParameters[0].rtx={ssrc:transceiver.sendEncodingParameters[0].ssrc+1};}}}
+var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;}
+sdp+=writeMediaSection(transceiver,commonCapabilities,'answer',transceiver.stream,pc._dtlsRole);if(transceiver.rtcpParameters&&transceiver.rtcpParameters.reducedSize){sdp+='a=rtcp-rsize\r\n';}});var desc=new window.RTCSessionDescription({type:'answer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.addIceCandidate=function(candidate){var pc=this;var sections;if(candidate&&!(candidate.sdpMLineIndex!==undefined||candidate.sdpMid)){return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required'));}
+return new Promise(function(resolve,reject){if(!pc._remoteDescription){return reject(makeError('InvalidStateError','Can not add ICE candidate without a remote description'));}else if(!candidate||candidate.candidate===''){for(var j=0;j<pc.transceivers.length;j++){if(pc.transceivers[j].rejected){continue;}
+pc.transceivers[j].iceTransport.addRemoteCandidate({});sections=SDPUtils.getMediaSections(pc._remoteDescription.sdp);sections[j]+='a=end-of-candidates\r\n';pc._remoteDescription.sdp=SDPUtils.getDescription(pc._remoteDescription.sdp)+
+sections.join('');if(pc.usingBundle){break;}}}else{var sdpMLineIndex=candidate.sdpMLineIndex;if(candidate.sdpMid){for(var i=0;i<pc.transceivers.length;i++){if(pc.transceivers[i].mid===candidate.sdpMid){sdpMLineIndex=i;break;}}}
+var transceiver=pc.transceivers[sdpMLineIndex];if(transceiver){if(transceiver.rejected){return resolve();}
+var cand=Object.keys(candidate.candidate).length>0?SDPUtils.parseCandidate(candidate.candidate):{};if(cand.protocol==='tcp'&&(cand.port===0||cand.port===9)){return resolve();}
+if(cand.component&&cand.component!==1){return resolve();}
+if(sdpMLineIndex===0||(sdpMLineIndex>0&&transceiver.iceTransport!==pc.transceivers[0].iceTransport)){if(!maybeAddCandidate(transceiver.iceTransport,cand)){return reject(makeError('OperationError','Can not add ICE candidate'));}}
+var candidateString=candidate.candidate.trim();if(candidateString.indexOf('a=')===0){candidateString=candidateString.substr(2);}
+sections=SDPUtils.getMediaSections(pc._remoteDescription.sdp);sections[sdpMLineIndex]+='a='+
+(cand.type?candidateString:'end-of-candidates')
++'\r\n';pc._remoteDescription.sdp=SDPUtils.getDescription(pc._remoteDescription.sdp)+
+sections.join('');}else{return reject(makeError('OperationError','Can not add ICE candidate'));}}
+resolve();});};RTCPeerConnection.prototype.getStats=function(selector){if(selector&&selector instanceof window.MediaStreamTrack){var senderOrReceiver=null;this.transceivers.forEach(function(transceiver){if(transceiver.rtpSender&&transceiver.rtpSender.track===selector){senderOrReceiver=transceiver.rtpSender;}else if(transceiver.rtpReceiver&&transceiver.rtpReceiver.track===selector){senderOrReceiver=transceiver.rtpReceiver;}});if(!senderOrReceiver){throw makeError('InvalidAccessError','Invalid selector.');}
+return senderOrReceiver.getStats();}
+var promises=[];this.transceivers.forEach(function(transceiver){['rtpSender','rtpReceiver','iceGatherer','iceTransport','dtlsTransport'].forEach(function(method){if(transceiver[method]){promises.push(transceiver[method].getStats());}});});return Promise.all(promises).then(function(allStats){var results=new Map();allStats.forEach(function(stats){stats.forEach(function(stat){results.set(stat.id,stat);});});return results;});};var ortcObjects=['RTCRtpSender','RTCRtpReceiver','RTCIceGatherer','RTCIceTransport','RTCDtlsTransport'];ortcObjects.forEach(function(ortcObjectName){var obj=window[ortcObjectName];if(obj&&obj.prototype&&obj.prototype.getStats){var nativeGetstats=obj.prototype.getStats;obj.prototype.getStats=function(){return nativeGetstats.apply(this).then(function(nativeStats){var mapStats=new Map();Object.keys(nativeStats).forEach(function(id){nativeStats[id].type=fixStatsType(nativeStats[id]);mapStats.set(id,nativeStats[id]);});return mapStats;});};}});var methods=['createOffer','createAnswer'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[0]==='function'||typeof args[1]==='function'){return nativeMethod.apply(this,[arguments[2]]).then(function(description){if(typeof args[0]==='function'){args[0].apply(null,[description]);}},function(error){if(typeof args[1]==='function'){args[1].apply(null,[error]);}});}
+return nativeMethod.apply(this,arguments);};});methods=['setLocalDescription','setRemoteDescription','addIceCandidate'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'||typeof args[2]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}},function(error){if(typeof args[2]==='function'){args[2].apply(null,[error]);}});}
+return nativeMethod.apply(this,arguments);};});['getStats'].forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}});}
+return nativeMethod.apply(this,arguments);};});return RTCPeerConnection;};},{"sdp":17}],17:[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.getDescription=function(blob){var sections=SDPUtils.splitSections(blob);return sections&&sections[0];};SDPUtils.getMediaSections=function(blob){var sections=SDPUtils.splitSections(blob);sections.shift();return sections;};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:parseInt(parts[1],10),protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],address: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;case'ufrag':candidate.ufrag=parts[i+1];candidate.usernameFragment=parts[i+1];break;default:candidate[parts[i]]=parts[i+1];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.address||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);}
+if(candidate.usernameFragment||candidate.ufrag){sdp.push('ufrag');sdp.push(candidate.usernameFragment||candidate.ufrag);}
+return'candidate:'+sdp.join(' ');};SDPUtils.parseIceOptions=function(line){return line.substr(14).split(' ');};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.channels=parts.length===3?parseInt(parts[2],10):1;parsed.numChannels=parsed.channels;return parsed;};SDPUtils.writeRtpMap=function(codec){var pt=codec.payloadType;if(codec.preferredPayloadType!==undefined){pt=codec.preferredPayloadType;}
+var channels=codec.channels||codec.numChannels||1;return'a=rtpmap:'+pt+' '+codec.name+'/'+codec.clockRate+
+(channels!==1?'/'+channels:'')+'\r\n';};SDPUtils.parseExtmap=function(line){var parts=line.substr(9).split(' ');return{id:parseInt(parts[0],10),direction:parts[0].indexOf('/')>0?parts[0].split('/')[1]:'sendrecv',uri:parts[1]};};SDPUtils.writeExtmap=function(headerExtension){return'a=extmap:'+(headerExtension.id||headerExtension.preferredId)+
+(headerExtension.direction&&headerExtension.direction!=='sendrecv'?'/'+headerExtension.direction:'')+' '+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){if(codec.parameters[param]){params.push(param+'='+codec.parameters[param]);}else{params.push(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.parseSsrcGroup=function(line){var parts=line.substr(13).split(' ');return{semantics:parts.shift(),ssrcs:parts.map(function(ssrc){return parseInt(ssrc,10);})};};SDPUtils.getMid=function(mediaSection){var mid=SDPUtils.matchPrefix(mediaSection,'a=mid:')[0];if(mid){return mid.substr(6);}};SDPUtils.parseFingerprint=function(line){var parts=line.substr(14).split(' ');return{algorithm:parts[0].toLowerCase(),value:parts[1]};};SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.matchPrefix(mediaSection+sessionpart,'a=fingerprint:');return{role:'auto',fingerprints:lines.map(SDPUtils.parseFingerprint)};};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);});var maxptime=0;caps.codecs.forEach(function(codec){if(codec.maxptime>maxptime){maxptime=codec.maxptime;}});if(maxptime>0){sdp+='a=maxptime:'+maxptime+'\r\n';}
+sdp+='a=rtcp-mux\r\n';if(caps.headerExtensions){caps.headerExtensions.forEach(function(extension){sdp+=SDPUtils.writeExtmap(extension);});}
+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.substr(17).split(' ');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)};if(primarySsrc&&secondarySsrc){encParam.rtx={ssrc:secondarySsrc};}
+encodingParameters.push(encParam);if(hasRed){encParam=JSON.parse(JSON.stringify(encParam));encParam.fec={ssrc:primarySsrc,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)*1000*0.95
+-(50*40*8);}else{bandwidth=undefined;}
+encodingParameters.forEach(function(params){params.maxBitrate=bandwidth;});}
+return encodingParameters;};SDPUtils.parseRtcpParameters=function(mediaSection){var rtcpParameters={};var remoteSsrc=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(obj){return obj.attribute==='cname';})[0];if(remoteSsrc){rtcpParameters.cname=remoteSsrc.value;rtcpParameters.ssrc=remoteSsrc.ssrc;}
+var rsize=SDPUtils.matchPrefix(mediaSection,'a=rtcp-rsize');rtcpParameters.reducedSize=rsize.length>0;rtcpParameters.compound=rsize.length===0;var mux=SDPUtils.matchPrefix(mediaSection,'a=rtcp-mux');rtcpParameters.mux=mux.length>0;return rtcpParameters;};SDPUtils.parseMsid=function(mediaSection){var parts;var spec=SDPUtils.matchPrefix(mediaSection,'a=msid:');if(spec.length===1){parts=spec[0].substr(7).split(' ');return{stream:parts[0],track:parts[1]};}
+var planB=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(msidParts){return msidParts.attribute==='msid';});if(planB.length>0){parts=planB[0].value.split(' ');return{stream:parts[0],track:parts[1]};}};SDPUtils.generateSessionId=function(){return Math.random().toString().substr(2,21);};SDPUtils.writeSessionBoilerplate=function(sessId,sessVer,sessUser){var sessionId;var version=sessVer!==undefined?sessVer:2;if(sessId){sessionId=sessId;}else{sessionId=SDPUtils.generateSessionId();}
+var user=sessUser||'thisisadapterortc';return'v=0\r\n'+'o='+user+' '+sessionId+' '+version+' 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.direction){sdp+='a='+transceiver.direction+'\r\n';}else 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;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+
+transceiver.sendEncodingParameters[0].ssrc+' '+
+transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}}
+sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.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';};SDPUtils.getKind=function(mediaSection){var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');return mline[0].substr(2);};SDPUtils.isRejected=function(mediaSection){return mediaSection.split(' ',2)[1]==='0';};SDPUtils.parseMLine=function(mediaSection){var lines=SDPUtils.splitLines(mediaSection);var parts=lines[0].substr(2).split(' ');return{kind:parts[0],port:parseInt(parts[1],10),protocol:parts[2],fmt:parts.slice(3).join(' ')};};SDPUtils.parseOLine=function(mediaSection){var line=SDPUtils.matchPrefix(mediaSection,'o=')[0];var parts=line.substr(2).split(' ');return{username:parts[0],sessionId:parts[1],sessionVersion:parseInt(parts[2],10),netType:parts[3],addressType:parts[4],address:parts[5]};};SDPUtils.isValidSDP=function(blob){if(typeof blob!=='string'||blob.length===0){return false;}
+var lines=SDPUtils.splitLines(blob);for(var i=0;i<lines.length;i++){if(lines[i].length<2||lines[i].charAt(1)!=='='){return false;}}
+return true;};if(typeof module==='object'){module.exports=SDPUtils;}},{}]},{},[1])(1)});
\ No newline at end of file
index 2ac8e89e7ed7c5777e3265cf4f52814ae155b29b..7a6a42b8a0c26694ecf3dc65c8ecbd01ce5a4906 100644 (file)
-/*
- * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
- *
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- *
- * The Initial Developer of the Original Code is
- * Anthony Minessale II <anthm@freeswitch.org>
- * Portions created by the Initial Developer are Copyright (C)
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Anthony Minessale II <anthm@freeswitch.org>
- *
- * jquery.FSRTC.js - WebRTC Glue code
- *
- */
 
-(function($) {
-
-    // Find the line in sdpLines that starts with |prefix|, and, if specified,
-    // contains |substr| (case-insensitive search).
-    function findLine(sdpLines, prefix, substr) {
-        return findLineInRange(sdpLines, 0, -1, prefix, substr);
-    }
-
-    // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
-    // and, if specified, contains |substr| (case-insensitive search).
-    function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
-        var realEndLine = (endLine != -1) ? endLine : sdpLines.length;
-        for (var i = startLine; i < realEndLine; ++i) {
-            if (sdpLines[i].indexOf(prefix) === 0) {
-                if (!substr || sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
-                    return i;
-                }
-            }
-        }
-        return null;
-    }
-
-    // Gets the codec payload type from an a=rtpmap:X line.
-    function getCodecPayloadType(sdpLine) {
-        var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
-        var result = sdpLine.match(pattern);
-        return (result && result.length == 2) ? result[1] : null;
-    }
-    
-    // Returns a new m= line with the specified codec as the first one.
-    function setDefaultCodec(mLine, payload) {
-        var elements = mLine.split(' ');
-        var newLine = [];
-        var index = 0;
-        for (var i = 0; i < elements.length; i++) {
-            if (index === 3) { // Format of media starts from the fourth.
-                newLine[index++] = payload; // Put target payload to the first.
-            }
-            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() {}
-            },
-      useStream: null,
-        }, 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;
-    };
-
-    // Sets Opus in stereo if stereo is enabled, by adding the stereo=1 fmtp param.
-    $.FSRTC.prototype.stereoHack = function(sdp) {
-        var self = this;
-
-        if (!self.options.useStereo) {
-            return sdp;
-        }
-
-        var sdpLines = sdp.split('\r\n');
-
-        // Find opus payload.
-        var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'), opusPayload;
-
-        if (!opusIndex) {
-           return sdp;
-       } else {
-            opusPayload = getCodecPayloadType(sdpLines[opusIndex]);
-        }
-
-        // Find the payload in fmtp line.
-        var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString());
-
-        if (fmtpLineIndex === null) {
-           // create an fmtp line
-           sdpLines[opusIndex] = sdpLines[opusIndex] + '\r\na=fmtp:' + opusPayload.toString() + " stereo=1; sprop-stereo=1"
-       } else {
-            // Append stereo=1 to fmtp line.
-            sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1');
-       }
-
-        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 onRemoteStreamSuccess(self, stream) {
-      console.log("Remote Stream Success");
-      doCallback(self, "onRemoteStream", stream);
-    }
-
-    function onICE(self, candidate) {
-        self.mediaData.candidate = candidate;
-        self.mediaData.candidateList.push(self.mediaData.candidate);
-
-        doCallback(self, "onICE");
-    }
-
-    function doCallback(self, func, arg) {
-        if (func in self.options.callbacks) {
-            self.options.callbacks[func](self, arg);
-        }
-    }
-
-    function onICEComplete(self, candidate) {
-        console.log("ICE Complete");
-        doCallback(self, "onICEComplete");
-    }
-
-    function onChannelError(self, e) {
-        console.error("Channel Error", e);
-        doCallback(self, "onError", e);
-    }
-
-    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 (typeof element.srcObject !== 'undefined') {
-           element.srcObject = stream;
-       } else {
-           console.error('Error attaching stream to element.');
-       }
-    }
-    
-    function onRemoteStream(self, stream) {
-        if (self.options.useVideo) {
-            self.options.useVideo.style.display = 'block';
-
-           // Hacks for Mobile Safari
-           var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
-
-           if (iOS) {
-               self.options.useVideo.setAttribute("playsinline", true);
-           }
-        }
-
-        var element = self.options.useAudio;
-        console.log("REMOTE STREAM", stream, element);
-
-       FSRTCattachMediaStream(element, stream);
-       
-
-       
-        //self.options.useAudio.play();
-        self.remoteStream = stream;
-        onRemoteStreamSuccess(self, 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';
-            self.options.useVideo['src'] = '';
-        }
-
-        if (self.localStream && !self.options.useStream) {
-            if(typeof self.localStream.stop == 'function') {
-                self.localStream.stop();
-            } else {
-               if (self.localStream.active){
-                    var tracks = self.localStream.getTracks();
-                    console.log(tracks);
-                   tracks.forEach(function(track, index){
-                       console.log(track);
-                       track.stop();
-                   })
-                }
-            }
-            self.localStream = null;
-        }
-
-        if (self.options.localVideo) {
-      deactivateLocalVideo(self.options.localVideo);
-        }
-
-       if (self.options.localVideoStream && !self.options.useStream) {
-            if(typeof self.options.localVideoStream.stop == 'function') {
-               self.options.localVideoStream.stop();
-            } else {
-               if (self.options.localVideoStream.active){
-                    var tracks = self.options.localVideoStream.getTracks();
-                    console.log(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;
-    if (!self.localStream) {
-        return false;
-    }
-       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;
-           }
-
-           self.audioEnabled = audioTracks[i].enabled;
-       }
-
-       return !self.audioEnabled;
-    }
-
-    $.FSRTC.prototype.getVideoMute = function() {
-       var self = this;
-       return self.videoEnabled;
-    }
-
-    $.FSRTC.prototype.setVideoMute = function(what) {
-       var self = this;
-    if (!self.localStream) {
-        return false;
-    }
-       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 = 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
-                },
-                turnServer: self.options.turnServer
-            });
-
-            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 (self.options.useVideo && self.options.localVideo && !self.options.useStream) {
-            getUserMedia({
-               constraints: {
-                    audio: false,
-                    video: { deviceId: params.useCamera },
-               },
-               localVideo: self.options.localVideo,
-               onsuccess: function(e) {self.options.localVideoStream = e; console.log("local video ready");},
-               onerror: function(e) {console.error("local video error!");}
-            });
-       }
-
-    if (self.options.useStream) {
-      if (self.options.useVideo) {
-        self.options.localVideoStream = self.options.useStream;
-        if (self.options.localVideo) {
-          activateLocalVideo(self.options.localVideo, self.options.useStream);
-        }
-      }
-      onSuccess(self.options.useStream);
-    }
-    else {
-      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) {//obj.options.videoParams.chromeMediaSource == 'desktop') {
-           console.error("SCREEN SHARE", obj.options.videoParams);
-           audio = false;
-       } else {
-           audio = {
-           };
-
-           if (obj.options.audioParams) {
-               audio = obj.options.audioParams;
-            }
-
-           if (obj.options.useMic !== "any") {
-               //audio.optional = [{sourceId: obj.options.useMic}];
-               audio.deviceId = {exact: obj.options.useMic};
-           }
-       }
-
-    if (obj.options.useVideo && obj.options.localVideo && !obj.options.useStream) {
-            getUserMedia({
-               constraints: {
-                    audio: false,
-                    video: { deviceId: obj.options.useCamera },
-               },
-               localVideo: obj.options.localVideo,
-               onsuccess: function(e) {obj.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) {
-           if (!obj.options.useCamera && !!navigator.mozGetUserMedia) {
-               //This is an issue, only FireFox needs to ask this additional question if its screen or window we need a better way
-               var dowin = window.confirm("Do you want to share an application window?  If not you can share an entire screen.");
-
-               video = {
-                   width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth},
-                   height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight},
-                   mediaSource: dowin ? "window" : "screen"
-               }
-           } else {
-               var opt = [];
-               if (obj.options.useCamera) {
-                   opt.push({sourceId: obj.options.useCamera});
-               }
-               
-               if (bestFrameRate) {
-                   opt.push({minFrameRate: bestFrameRate});
-                   opt.push({maxFrameRate: bestFrameRate});
-               }
-               
-               video = {
-                   mandatory: obj.options.videoParams,
-                   optional: opt               
-               };
-          // NOTE: This is a workaround for
-          // https://bugs.chromium.org/p/chromium/issues/detail?id=862325
-          if (!!navigator.userAgent.match(/Android/i)) {
-            delete video.frameRate.min;
-          }
-           }
-       } else {
-
-           video = {
-               //mandatory: obj.options.videoParams,
-               width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth},
-               height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight}
-           };
-           
-            
-           
-           var useVideo = obj.options.useVideo;
-
-           if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") {
-               //if (!video.optional) {
-               //video.optional = [];
-               //}
-
-               
-               if (obj.options.useCamera !== "any") {
-                   //video.optional.push({sourceId: obj.options.useCamera});
-        video.deviceId = {
-          exact: obj.options.useCamera,
-        };
-               }
-
-               if (bestFrameRate) {
-                   //video.optional.push({minFrameRate: bestFrameRate});
-                   //video.optional.push({maxFrameRate: bestFrameRate});
-                   video.frameRate = {ideal: bestFrameRate, min: minFrameRate, max: 30};
-               }
-
-           } else {
-               console.log("Camera Disabled");
-               video = false;
-               useVideo = false;
-           }
-       }
-
-       return {audio: audio, video: video, useVideo: useVideo};
-    }
-    
-    $.FSRTC.prototype.call = function(profile) {
-        checkCompat();
-       
-        var self = this;
-       var screen = false;
-
-        self.type = "offer";
-
-       if (self.options.videoParams && self.options.screenShare) { //self.options.videoParams.chromeMediaSource == 'desktop') {
-           screen = true;
-       }
-
-        function onSuccess(stream) {
-           self.localStream = stream;
-           
-           if (screen) {
-               self.constraints.offerToReceiveVideo = false;
-               self.constraints.offerToReceiveAudio = false;
-               self.constraints.offerToSendAudio = 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,
-                turnServer: self.options.turnServer
-            });
-
-            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 (self.options.useStream) {
-      if (self.options.useVideo) {
-        self.options.localVideoStream = self.options.useStream;
-        if (self.options.localVideo) {
-          activateLocalVideo(self.options.localVideo, self.options.useStream);
-        }
-      }
-      onSuccess(self.options.useStream);
-    }
-    else if (mediaParams.audio || mediaParams.video) {
-
-            getUserMedia({
-               constraints: {
-                    audio: mediaParams.audio,
-                    video: mediaParams.video
-               },
-               video: mediaParams.useVideo,
-               onsuccess: onSuccess,
-               onerror: onError
-            });
-
-       } else {
-           onSuccess(null);
-       }
-
-
-
-        /*
-        navigator.getUserMedia({
-            video: self.options.useVideo,
-            audio: true
-        }, onSuccess, onError);
-        */
-
-    };
-
-    // DERIVED from RTCPeerConnection-v1.5
-    // 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
-    
-    function FSRTCPeerConnection(options) {
-       var gathering = false, done = false;
-       var config = {};
-        var default_ice = [{ urls: ['stun:stun.l.google.com:19302'] }];
-
-        if (self.options.turnServer) {
-          default_ice.push(self.options.turnServer)
-        }
-
-        if (options.iceServers) {
-            if (typeof(options.iceServers) === "boolean") {
-               config.iceServers = default_ice;
-            } else {
-               config.iceServers = options.iceServers;
-           }
-        }
-
-       config.bundlePolicy = "max-compat";
-
-        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();
-           }
-        };
-
-        // attachStream = MediaStream;
-        if (options.attachStream) peer.addStream(options.attachStream);
-
-        // attachStreams[0] = audio-stream;
-        // attachStreams[1] = video-stream;
-        // attachStreams[2] = screen-capturing-stream;
-        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;
-
-            // onRemoteStreamEnded(MediaStream)
-            remoteMediaStream.oninactive = function () {
-                if (options.onRemoteStreamEnded) options.onRemoteStreamEnded(remoteMediaStream);
-            };
-
-            // onRemoteStream(MediaStream)
-            if (options.onRemoteStream) options.onRemoteStream(remoteMediaStream);
-
-            //console.debug('on:add:stream', remoteMediaStream);
-        };
-
-        //var constraints = options.constraints || {
-         //  offerToReceiveAudio: true,
-           //offerToReceiveVideo: true   
-        //};
-
-        // onOfferSDP(RTCSessionDescription)
-        function createOffer() {
-            if (!options.onOfferSDP) return;
-
-            peer.createOffer(function(sessionDescription) {
-                sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
-                peer.setLocalDescription(sessionDescription);
-                options.onOfferSDP(sessionDescription);
-            },
-                               onSdpError, options.constraints);
-        }
-
-        // onAnswerSDP(RTCSessionDescription)
-        function createAnswer() {
-            if (options.type != "answer") return;
-
-            //options.offerSDP.sdp = addStereo(options.offerSDP.sdp);
-            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();
-        }
-
-        // DataChannel Bandwidth
-        function setBandwidth(sdp) {
-            // remove existing bandwidth lines
-            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;
-        }
-
-        // old: FF<>Chrome interoperability management
-        function getInteropSDP(sdp) {
-            var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
-            extractedChars = '';
-
-            function getChars() {
-                extractedChars += chars[parseInt(Math.random() * 40)] || '';
-                if (extractedChars.length < 40) getChars();
-
-                return extractedChars;
-            }
-
-            // usually audio-only streaming failure occurs out of audio-specific crypto line
-            // a=crypto:1 AES_CM_128_HMAC_SHA1_32 --------- kAttributeCryptoVoice
-            if (options.onAnswerSDP) sdp = sdp.replace(/(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g, '');
-
-            // video-specific crypto line i.e. SHA1_80
-            // a=crypto:1 AES_CM_128_HMAC_SHA1_80 --------- kAttributeCryptoVideo
-            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;
-        }
-
-        // DataChannel management
-        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);
-            };
-        }
-
-        function openAnswererChannel() {
-            peer.ondatachannel = function(event) {
-                channel = event.channel;
-                channel.binaryType = 'blob';
-                setChannelEvents();
-            };
-
-            return;
-        }
-
-        // fake:true is also available on chrome under a flag!
-        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 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;
-                  }
-                }
-            }
-
-        };
-    }
-
-    // getUserMedia
-    var video_constraints = {
-        //mandatory: {},
-        //optional: []
-    };
-
-  function activateLocalVideo(el, stream) {
-    el.srcObject = stream;
-    el.style.display = 'block';
-  }
-
-  function deactivateLocalVideo(el) {
-    el.srcObject = null;
-    el.style.display = 'none';
-  }
-
-    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) {
-        activateLocalVideo(options.localVideo, stream);
-            }
-
-            if (options.onsuccess) {
-                options.onsuccess(stream);
-            }
-
-            media = stream;
-        }
-
-        return media;
-    }
-
-    $.FSRTC.resSupported = function(w, h) {
-       for (var i in $.FSRTC.validRes) {
-           if ($.FSRTC.validRes[i][0] == w && $.FSRTC.validRes[i][1] == h) {
-               return true;
-           }
-       }
-
-       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 = [[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;
-       }
-
-       w = resList[resI][0];
-       h = resList[resI][1];
-       resI++;
-
-       var video = {
-           width: {exact: w},
-           height: {exact: h}
-       };
-
-  if (cam !== "any") {
-    video.deviceId = {
-      exact: cam,
-    };
-  }
-
-       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.warn( 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);
-    }
-
-    $.FSRTC.checkPerms = function (runtime, check_audio, check_video) {
-       getUserMedia({
-           constraints: {
-               audio: check_audio,
-               video: check_video,
-           },
-           onsuccess: function(e) {
-
-               e.getTracks().forEach(function(track) {track.stop();});
-               
-               console.info("media perm init complete"); 
-               if (runtime) {
-                    setTimeout(runtime, 100, true);
-               }
-            },
-           onerror: function(e) {
-               if (check_video && check_audio) {
-                   console.error("error, retesting with audio params only");
-                   return $.FSRTC.checkPerms(runtime, check_audio, false);
-               }
-
-               console.error("media perm init error");
-
-               if (runtime) {
-                   runtime(false)
-               }
-           }
-       });
-    }
-
-})(jQuery);
-/*
- * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
- *
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is jquery.jsonrpclient.js modified for Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- *
- * The Initial Developer of the Original Code is
- * Textalk AB http://textalk.se/
- * Portions created by the Initial Developer are Copyright (C)
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Anthony Minessale II <anthm@freeswitch.org>
- *
- * jquery.jsonrpclient.js - JSON RPC client code
- *
- */
-/**
- * This plugin requires jquery.json.js to be available, or at least the methods $.toJSON and
- * $.parseJSON.
- *
- * The plan is to make use of websockets if they are available, but work just as well with only
- * http if not.
- *
- * Usage example:
- *
- *   var foo = new $.JsonRpcClient({ ajaxUrl: '/backend/jsonrpc' });
- *   foo.call(
- *     'bar', [ 'A parameter', 'B parameter' ],
- *     function(result) { alert('Foo bar answered: ' + result.my_answer); },
- *     function(error)  { console.log('There was an error', error); }
- *   );
- *
- * More examples are available in README.md
- */
-(function($) {
-  /**
-   * @fn new
-   * @memberof $.JsonRpcClient
-   *
-   * @param options An object stating the backends:
-   *                ajaxUrl    A url (relative or absolute) to a http(s) backend.
-   *                socketUrl  A url (relative of absolute) to a ws(s) backend.
-   *                onmessage  A socket message handler for other messages (non-responses).
-   *                getSocket  A function returning a WebSocket or null.
-   *                           It must take an onmessage_cb and bind it to the onmessage event
-   *                           (or chain it before/after some other onmessage handler).
-   *                           Or, it could return null if no socket is available.
-   *                           The returned instance must have readyState <= 1, and if less than 1,
-   *                           react to onopen binding.
-   */
-    $.JsonRpcClient = function(options) {
-        var self = this;
-        this.options = $.extend({
-            ajaxUrl       : null,
-            socketUrl     : null, ///< The ws-url for default getSocket.
-            onmessage     : null, ///< Other onmessage-handler.
-            login         : null, /// auth login
-            passwd        : null, /// auth passwd
-            sessid        : null,
-           loginParams   : null,
-           userVariables : null,
-            getSocket     : function(onmessage_cb) { return self._getSocket(onmessage_cb); }
-        }, options);
-
-        self.ws_cnt = 0;
-
-        // Declare an instance version of the onmessage callback to wrap 'this'.
-        this.wsOnMessage = function(event) { self._wsOnMessage(event); };
-    };
-
-    /// Holding the WebSocket on default getsocket.
-    $.JsonRpcClient.prototype._ws_socket = null;
-
-    /// Object <id>: { success_cb: cb, error_cb: cb }
-    $.JsonRpcClient.prototype._ws_callbacks = {};
-
-    /// The next JSON-RPC request id.
-    $.JsonRpcClient.prototype._current_id = 1;
-
-
-    $.JsonRpcClient.prototype.speedTest = function (bytes, cb) {
-        var socket = this.options.getSocket(this.wsOnMessage);
-       if (socket !== null) {
-           this.speedCB = cb;
-           this.speedBytes = bytes;
-           socket.send("#SPU " + bytes);
-
-           var loops = bytes / 1024;
-           var rem = bytes % 1024;
-           var i;
-           var data = new Array(1024).join(".");
-           for (i = 0; i < loops; i++) {
-               socket.send("#SPB " + data);
-           }
-           
-           if (rem) {
-               socket.send("#SPB " + data);
-           }
-
-           socket.send("#SPE");
-       }
-    };
-
-
-
-    /**
-     * @fn call
-     * @memberof $.JsonRpcClient
-     *
-     * @param method     The method to run on JSON-RPC server.
-     * @param params     The params; an array or object.
-     * @param success_cb A callback for successful request.
-     * @param error_cb   A callback for error.
-     */
-    $.JsonRpcClient.prototype.call = function(method, params, success_cb, error_cb) {
-    // Construct the JSON-RPC 2.0 request.
-
-        if (!params) {
-            params = {};
-        }
-
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        var request = {
-            jsonrpc : '2.0',
-            method  : method,
-            params  : params,
-            id      : this._current_id++  // Increase the id counter to match request/response
-        };
-
-        if (!success_cb) {
-            success_cb = function(e){console.log("Success: ", e);};
-        }
-
-        if (!error_cb) {
-            error_cb = function(e){console.log("Error: ", e);};
-        }
-
-        // Try making a WebSocket call.
-        var socket = this.options.getSocket(this.wsOnMessage);
-        if (socket !== null) {
-            this._wsCall(socket, request, success_cb, error_cb);
-            return;
-        }
-
-        // No WebSocket, and no HTTP backend?  This won't work.
-        if (this.options.ajaxUrl === null) {
-            throw "$.JsonRpcClient.call used with no websocket and no http endpoint.";
-        }
-
-        $.ajax({
-            type     : 'POST',
-            url      : this.options.ajaxUrl,
-            data     : $.toJSON(request),
-            dataType : 'json',
-            cache    : false,
-
-            success  : function(data) {
-                if ('error' in data) error_cb(data.error, this);
-                success_cb(data.result, this);
-            },
-
-            // JSON-RPC Server could return non-200 on error
-            error    : function(jqXHR, textStatus, errorThrown) {
-                try {
-                    var response = $.parseJSON(jqXHR.responseText);
-
-                    if ('console' in window) console.log(response);
-
-                    error_cb(response.error, this);
-                } catch (err) {
-                    // Perhaps the responseText wasn't really a jsonrpc-error.
-                    error_cb({ error: jqXHR.responseText }, this);
-                }
-            }
-        });
-    };
-
-    /**
-     * Notify sends a command to the server that won't need a response.  In http, there is probably
-     * an empty response - that will be dropped, but in ws there should be no response at all.
-     *
-     * This is very similar to call, but has no id and no handling of callbacks.
-     *
-     * @fn notify
-     * @memberof $.JsonRpcClient
-     *
-     * @param method     The method to run on JSON-RPC server.
-     * @param params     The params; an array or object.
-     */
-    $.JsonRpcClient.prototype.notify = function(method, params) {
-        // Construct the JSON-RPC 2.0 request.
-
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        var request = {
-            jsonrpc: '2.0',
-            method:  method,
-            params:  params
-        };
-
-        // Try making a WebSocket call.
-        var socket = this.options.getSocket(this.wsOnMessage);
-        if (socket !== null) {
-            this._wsCall(socket, request);
-            return;
-        }
-
-        // No WebSocket, and no HTTP backend?  This won't work.
-        if (this.options.ajaxUrl === null) {
-            throw "$.JsonRpcClient.notify used with no websocket and no http endpoint.";
-        }
-
-        $.ajax({
-            type     : 'POST',
-            url      : this.options.ajaxUrl,
-            data     : $.toJSON(request),
-            dataType : 'json',
-            cache    : false
-        });
-    };
-
-    /**
-     * Make a batch-call by using a callback.
-     *
-     * The callback will get an object "batch" as only argument.  On batch, you can call the methods
-     * "call" and "notify" just as if it was a normal $.JsonRpcClient object, and all calls will be
-     * sent as a batch call then the callback is done.
-     *
-     * @fn batch
-     * @memberof $.JsonRpcClient
-     *
-     * @param callback    The main function which will get a batch handler to run call and notify on.
-     * @param all_done_cb A callback function to call after all results have been handled.
-     * @param error_cb    A callback function to call if there is an error from the server.
-     *                    Note, that batch calls should always get an overall success, and the
-     *                    only error
-     */
-    $.JsonRpcClient.prototype.batch = function(callback, all_done_cb, error_cb) {
-        var batch = new $.JsonRpcClient._batchObject(this, all_done_cb, error_cb);
-        callback(batch);
-        batch._execute();
-    };
-
-    /**
-     * The default getSocket handler.
-     *
-     * @param onmessage_cb The callback to be bound to onmessage events on the socket.
-     *
-     * @fn _getSocket
-     * @memberof $.JsonRpcClient
-     */
-
-    $.JsonRpcClient.prototype.socketReady = function() {
-        if (this._ws_socket === null || this._ws_socket.readyState > 1) {
-            return false;
-        }
-
-        return true;
-    };
-
-    $.JsonRpcClient.prototype.closeSocket = function() {
-       var self = this;
-        if (self.socketReady()) {
-            self._ws_socket.onclose = function (w) {console.log("Closing Socket");};
-            self._ws_socket.close();
-        }
-    };
-
-    $.JsonRpcClient.prototype.loginData = function(params) {
-       var self = this;
-        self.options.login = params.login;
-        self.options.passwd = params.passwd;
-       self.options.loginParams = params.loginParams;
-       self.options.userVariables = params.userVariables;
-    };
-
-    $.JsonRpcClient.prototype.connectSocket = function(onmessage_cb) {
-        var self = this;
-
-        if (self.to) {
-            clearTimeout(self.to);
-        }
-
-        if (!self.socketReady()) {
-            self.authing = false;
-
-            if (self._ws_socket) {
-                delete self._ws_socket;
-            }
-
-            // No socket, or dying socket, let's get a new one.
-            self._ws_socket = new WebSocket(self.options.socketUrl);
-
-            if (self._ws_socket) {
-                // Set up onmessage handler.
-                self._ws_socket.onmessage = onmessage_cb;
-                self._ws_socket.onclose = function (w) {
-                    if (!self.ws_sleep) {
-                        self.ws_sleep = 1000;
-                    }
-
-                    if (self.options.onWSClose) {
-                        self.options.onWSClose(self);
-                    }
-
-                    if (self.ws_cnt > 10 && self.options.wsFallbackURL) {
-                      self.options.socketUrl = self.options.wsFallbackURL;
-                    }
-
-                    console.error("Websocket Lost " + self.ws_cnt + " sleep: " + self.ws_sleep + "msec");
-
-                    self.to = setTimeout(function() {
-                        console.log("Attempting Reconnection....");
-                        self.connectSocket(onmessage_cb);
-                    }, self.ws_sleep);
-
-                    self.ws_cnt++;
-
-                    if (self.ws_sleep < 3000 && (self.ws_cnt % 10) === 0) {
-                        self.ws_sleep += 1000;
-                    }
-                };
-
-                // Set up sending of message for when the socket is open.
-                self._ws_socket.onopen = function() {
-                    if (self.to) {
-                        clearTimeout(self.to);
-                    }
-                    self.ws_sleep = 1000;
-                    self.ws_cnt = 0;
-                    if (self.options.onWSConnect) {
-                        self.options.onWSConnect(self);
-                    }
-
-                    var req;
-                    // Send the requests.
-                    while ((req = $.JsonRpcClient.q.pop())) {
-                        self._ws_socket.send(req);
-                    }
-                };
-            }
-        }
-
-        return self._ws_socket ? true : false;
-    };
-
-    $.JsonRpcClient.prototype.stopRetrying = function() {
-      if (self.to)
-        clearTimeout(self.to);
-    }
-
-    $.JsonRpcClient.prototype._getSocket = function(onmessage_cb) {
-        // If there is no ws url set, we don't have a socket.
-        // Likewise, if there is no window.WebSocket.
-        if (this.options.socketUrl === null || !("WebSocket" in window)) return null;
-
-        this.connectSocket(onmessage_cb);
-
-        return this._ws_socket;
-    };
-
-    /**
-     * Queue to save messages delivered when websocket is not ready
-     */
-    $.JsonRpcClient.q = [];
-
-    /**
-     * Internal handler to dispatch a JRON-RPC request through a websocket.
-     *
-     * @fn _wsCall
-     * @memberof $.JsonRpcClient
-     */
-    $.JsonRpcClient.prototype._wsCall = function(socket, request, success_cb, error_cb) {
-        var request_json = $.toJSON(request);
-
-        if (socket.readyState < 1) {
-            // The websocket is not open yet; we have to set sending of the message in onopen.
-            self = this; // In closure below, this is set to the WebSocket.  Use self instead.
-            $.JsonRpcClient.q.push(request_json);
-        } else {
-            // We have a socket and it should be ready to send on.
-            socket.send(request_json);
-        }
-
-        // Setup callbacks.  If there is an id, this is a call and not a notify.
-        if ('id' in request && typeof success_cb !== 'undefined') {
-            this._ws_callbacks[request.id] = { request: request_json, request_obj: request, success_cb: success_cb, error_cb: error_cb };
-        }
-    };
-
-    /**
-     * Internal handler for the websocket messages.  It determines if the message is a JSON-RPC
-     * response, and if so, tries to couple it with a given callback.  Otherwise, it falls back to
-     * given external onmessage-handler, if any.
-     *
-     * @param event The websocket onmessage-event.
-     */
-    $.JsonRpcClient.prototype._wsOnMessage = function(event) {
-        // Check if this could be a JSON RPC message.
-        var response;
-
-       // Special sub proto
-       if (event.data[0] == "#" && event.data[1] == "S" && event.data[2] == "P") {
-           if (event.data[3] == "U") {
-               this.up_dur = parseInt(event.data.substring(4));
-           } else if (this.speedCB && event.data[3] == "D") {
-               this.down_dur = parseInt(event.data.substring(4));
-
-               var up_kps = (((this.speedBytes * 8) / (this.up_dur / 1000)) / 1024).toFixed(0);
-               var down_kps = (((this.speedBytes * 8) / (this.down_dur / 1000)) / 1024).toFixed(0);
-               
-               console.info("Speed Test: Up: " + up_kps + " Down: " + down_kps);
-               var cb = this.speedCB;
-               this.speedCB = null;
-               cb(event, {
-                       upDur: this.up_dur,
-                       downDur: this.down_dur,
-                       upKPS: up_kps,
-                       downKPS: down_kps
-               });
-           }
-           
-           return;
-       }
-
-
-        try {
-            response = $.parseJSON(event.data);
-
-            /// @todo Make using the jsonrcp 2.0 check optional, to use this on JSON-RPC 1 backends.
-
-            if (typeof response === 'object' &&
-                'jsonrpc' in response &&
-                response.jsonrpc === '2.0') {
-
-                /// @todo Handle bad response (without id).
-
-                // If this is an object with result, it is a response.
-                if ('result' in response && this._ws_callbacks[response.id]) {
-                    // Get the success callback.
-                    var success_cb = this._ws_callbacks[response.id].success_cb;
-
-    /*
-                    // set the sessid if present
-                    if ('sessid' in response.result && !this.options.sessid || (this.options.sessid != response.result.sessid)) {
-                        this.options.sessid = response.result.sessid;
-                        if (this.options.sessid) {
-                            console.log("setting session UUID to: " + this.options.sessid);
-                        }
-                    }
-    */
-                    // Delete the callback from the storage.
-                    delete this._ws_callbacks[response.id];
-
-                    // Run callback with result as parameter.
-                    success_cb(response.result, this);
-                    return;
-                } else if ('error' in response && this._ws_callbacks[response.id]) {
-                // If this is an object with error, it is an error response.
-
-                // Get the error callback.
-                    var error_cb = this._ws_callbacks[response.id].error_cb;
-                    var orig_req = this._ws_callbacks[response.id].request;
-
-                    // if this is an auth request, send the credentials and resend the failed request
-                    if (!self.authing && response.error.code == -32000 && self.options.login && self.options.passwd) {
-                        self.authing = true;
-
-                        this.call("login", { login: self.options.login, passwd: self.options.passwd, loginParams: self.options.loginParams,
-                                            userVariables: self.options.userVariables},
-                            this._ws_callbacks[response.id].request_obj.method == "login" ?
-                            function(e) {
-                                self.authing = false;
-                                console.log("logged in");
-                                delete self._ws_callbacks[response.id];
-
-                                if (self.options.onWSLogin) {
-                                    self.options.onWSLogin(true, self);
-                                }
-                            }
-
-                            :
-
-                            function(e) {
-                                self.authing = false;
-                                console.log("logged in, resending request id: " + response.id);
-                                var socket = self.options.getSocket(self.wsOnMessage);
-                                if (socket !== null) {
-                                    socket.send(orig_req);
-                                }
-                                if (self.options.onWSLogin) {
-                                    self.options.onWSLogin(true, self);
-                                }
-                            },
-
-                            function(e) {
-                                console.log("error logging in, request id:", response.id);
-                                delete self._ws_callbacks[response.id];
-                                error_cb(response.error, this);
-                                if (self.options.onWSLogin) {
-                                self.options.onWSLogin(false, self);
-                                }
-                            });
-                            return;
-                        }
-
-                        // Delete the callback from the storage.
-                        delete this._ws_callbacks[response.id];
-
-                        // Run callback with the error object as parameter.
-                        error_cb(response.error, this);
-                        return;
-                    }
-                }
-            } catch (err) {
-            // Probably an error while parsing a non json-string as json.  All real JSON-RPC cases are
-            // handled above, and the fallback method is called below.
-            console.log("ERROR: "+ err);
-            return;
-        }
-
-        // This is not a JSON-RPC response.  Call the fallback message handler, if given.
-        if (typeof this.options.onmessage === 'function') {
-            event.eventData = response;
-            if (!event.eventData) {
-                event.eventData = {};
-            }
-
-            var reply = this.options.onmessage(event);
-
-            if (reply && typeof reply === "object" && event.eventData.id) {
-                var msg = {
-                    jsonrpc: "2.0",
-                    id: event.eventData.id,
-                    result: reply
-                };
-
-                var socket = self.options.getSocket(self.wsOnMessage);
-                if (socket !== null) {
-                    socket.send($.toJSON(msg));
-                }
-            }
-        }
-    };
-
-
-    /************************************************************************************************
-     * Batch object with methods
-     ************************************************************************************************/
-
-    /**
-     * Handling object for batch calls.
-     */
-    $.JsonRpcClient._batchObject = function(jsonrpcclient, all_done_cb, error_cb) {
-        // Array of objects to hold the call and notify requests.  Each objects will have the request
-        // object, and unless it is a notify, success_cb and error_cb.
-        this._requests   = [];
-
-        this.jsonrpcclient = jsonrpcclient;
-        this.all_done_cb = all_done_cb;
-        this.error_cb    = typeof error_cb === 'function' ? error_cb : function() {};
-
-    };
-
-    /**
-     * @sa $.JsonRpcClient.prototype.call
-     */
-    $.JsonRpcClient._batchObject.prototype.call = function(method, params, success_cb, error_cb) {
-
-        if (!params) {
-            params = {};
-        }
-
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        if (!success_cb) {
-            success_cb = function(e){console.log("Success: ", e);};
-        }
-
-        if (!error_cb) {
-        error_cb = function(e){console.log("Error: ", e);};
-        }
-
-        this._requests.push({
-            request    : {
-            jsonrpc : '2.0',
-            method  : method,
-            params  : params,
-            id      : this.jsonrpcclient._current_id++  // Use the client's id series.
-        },
-            success_cb : success_cb,
-            error_cb   : error_cb
-        });
-    };
-
-    /**
-     * @sa $.JsonRpcClient.prototype.notify
-     */
-    $.JsonRpcClient._batchObject.prototype.notify = function(method, params) {
-        if (this.options.sessid) {
-            params.sessid = this.options.sessid;
-        }
-
-        this._requests.push({
-            request    : {
-                jsonrpc : '2.0',
-                method  : method,
-                params  : params
-            }
-        });
-    };
-
-    /**
-     * Executes the batched up calls.
-     */
-    $.JsonRpcClient._batchObject.prototype._execute = function() {
-        var self = this;
-
-        if (this._requests.length === 0) return; // All done :P
-
-        // Collect all request data and sort handlers by request id.
-        var batch_request = [];
-        var handlers = {};
-        var i = 0;
-        var call;
-        var success_cb;
-        var error_cb;
-
-        // If we have a WebSocket, just send the requests individually like normal calls.
-        var socket = self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);
-        if (socket !== null) {
-            for (i = 0; i < this._requests.length; i++) {
-                call = this._requests[i];
-                success_cb = ('success_cb' in call) ? call.success_cb : undefined;
-                error_cb   = ('error_cb'   in call) ? call.error_cb   : undefined;
-                self.jsonrpcclient._wsCall(socket, call.request, success_cb, error_cb);
-            }
-
-            if (typeof all_done_cb === 'function') all_done_cb(result);
-            return;
-        }
-
-        for (i = 0; i < this._requests.length; i++) {
-            call = this._requests[i];
-            batch_request.push(call.request);
-
-            // If the request has an id, it should handle returns (otherwise it's a notify).
-            if ('id' in call.request) {
-                handlers[call.request.id] = {
-                    success_cb : call.success_cb,
-                    error_cb   : call.error_cb
-                };
-            }
-        }
-
-        success_cb = function(data) { self._batchCb(data, handlers, self.all_done_cb); };
-
-        // No WebSocket, and no HTTP backend?  This won't work.
-        if (self.jsonrpcclient.options.ajaxUrl === null) {
-            throw "$.JsonRpcClient.batch used with no websocket and no http endpoint.";
-        }
-
-        // Send request
-        $.ajax({
-            url      : self.jsonrpcclient.options.ajaxUrl,
-            data     : $.toJSON(batch_request),
-            dataType : 'json',
-            cache    : false,
-            type     : 'POST',
-
-            // Batch-requests should always return 200
-            error    : function(jqXHR, textStatus, errorThrown) {
-                self.error_cb(jqXHR, textStatus, errorThrown);
-            },
-            success  : success_cb
-        });
-    };
-
-    /**
-     * Internal helper to match the result array from a batch call to their respective callbacks.
-     *
-     * @fn _batchCb
-     * @memberof $.JsonRpcClient
-     */
-    $.JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, all_done_cb) {
-        for (var i = 0; i < result.length; i++) {
-            var response = result[i];
-
-            // Handle error
-            if ('error' in response) {
-                if (response.id === null || !(response.id in handlers)) {
-                    // An error on a notify?  Just log it to the console.
-                    if ('console' in window) console.log(response);
-                } else {
-                    handlers[response.id].error_cb(response.error, this);
-                }
-            } else {
-                // Here we should always have a correct id and no error.
-                if (!(response.id in handlers) && 'console' in window) {
-                    console.log(response);
-                } else {
-                    handlers[response.id].success_cb(response.result, this);
-                }
-            }
-        }
-
-        if (typeof all_done_cb === 'function') all_done_cb(result);
-    };
-
-})(jQuery);
-
-/*
- * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
- *
- * Version: MPL 1.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
- *
- * The Initial Developer of the Original Code is
- * Anthony Minessale II <anthm@freeswitch.org>
- * Portions created by the Initial Developer are Copyright (C)
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Anthony Minessale II <anthm@freeswitch.org>
- *
- * jquery.verto.js - Main interface
- *
- */
-
-(function($) {
-    var sources = [];
-
-    var generateGUID = (typeof(window.crypto) !== 'undefined' && typeof(window.crypto.getRandomValues) !== 'undefined') ?
-    function() {
-        // If we have a cryptographically secure PRNG, use that
-        // http://stackoverflow.com/questions/6906916/collisions-when-generating-uuids-in-javascript
-        var buf = new Uint16Array(8);
-        window.crypto.getRandomValues(buf);
-        var S4 = function(num) {
-            var ret = num.toString(16);
-            while (ret.length < 4) {
-                ret = "0" + ret;
-            }
-            return ret;
-        };
-        return (S4(buf[0]) + S4(buf[1]) + "-" + S4(buf[2]) + "-" + S4(buf[3]) + "-" + S4(buf[4]) + "-" + S4(buf[5]) + S4(buf[6]) + S4(buf[7]));
-    }
-
-    :
-
-    function() {
-        // Otherwise, just use Math.random
-        // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
-        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
-            var r = Math.random() * 16 | 0,
-            v = c == 'x' ? r : (r & 0x3 | 0x8);
-            return v.toString(16);
-        });
-    };
-
-    /// MASTER OBJ
-    $.verto = function(options, callbacks) {
-        var verto = this;
-
-        $.verto.saved.push(verto);
-
-        verto.options = $.extend({
-            login: null,
-            passwd: null,
-            socketUrl: null,
-            tag: null,
-           localTag: null,
-            videoParams: {},
-            audioParams: {},
-           loginParams: {},
-           deviceParams: {onResCheck: null},
-           userVariables: {},
-            iceServers: false,
-            ringSleep: 6000,
-      sessid: null,
-      useStream: null
-        }, options);
-
-       if (verto.options.deviceParams.useCamera) {
-           $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, verto.options.deviceParams.onResCheck);
-       }
-
-       if (!verto.options.deviceParams.useMic) {
-           verto.options.deviceParams.useMic = "any";
-       }
-
-       if (!verto.options.deviceParams.useSpeak) {
-           verto.options.deviceParams.useSpeak = "any";
-       }
-
-       if (verto.options.sessid) {
-           verto.sessid = verto.options.sessid;
-       } else {
-            verto.sessid = localStorage.getItem("verto_session_uuid") || generateGUID();
-           localStorage.setItem("verto_session_uuid", verto.sessid);
-       }
-
-        verto.dialogs = {};
-        verto.callbacks = callbacks || {};
-        verto.eventSUBS = {};
-
-        verto.rpcClient = new $.JsonRpcClient({
-            login: verto.options.login,
-            passwd: verto.options.passwd,
-            socketUrl: verto.options.socketUrl,
-            wsFallbackURL: verto.options.wsFallbackURL,
-            turnServer: verto.options.turnServer,
-           loginParams: verto.options.loginParams,
-           userVariables: verto.options.userVariables,
-            sessid: verto.sessid,
-            onmessage: function(e) {
-                return verto.handleMessage(e.eventData);
-            },
-            onWSConnect: function(o) {
-                o.call('login', {});
-            },
-            onWSLogin: function(success) {
-                if (verto.callbacks.onWSLogin) {
-                    verto.callbacks.onWSLogin(verto, success);
-                }
-            },
-            onWSClose: function(success) {
-                if (verto.callbacks.onWSClose) {
-                    verto.callbacks.onWSClose(verto, success);
-                }
-                verto.purge();
-            }
-        });
-
-        var tag = verto.options.tag;
-        if (typeof(tag) === "function") {
-          tag = tag();
-        }
-
-        if (verto.options.ringFile && verto.options.tag) {
-            verto.ringer = $("#" + tag);
-        }
-
-        verto.rpcClient.call('login', {});
-
-    };
-
-
-    $.verto.prototype.deviceParams = function(obj) {
-        var verto = this;
-
-       for (var i in obj) {
-            verto.options.deviceParams[i] = obj[i];
-       }
-
-       if (obj.useCamera) {
-           $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, obj ? obj.onResCheck : undefined);
-       }
-    };
-
-    $.verto.prototype.videoParams = function(obj) {
-        var verto = this;
-
-       for (var i in obj) {
-            verto.options.videoParams[i] = obj[i];
-       }
-    };
-
-    $.verto.prototype.iceServers = function(obj) {
-        var verto = this;
-        verto.options.iceServers = obj;
-    };
-
-    $.verto.prototype.loginData = function(params) {
-       var verto = this;
-        verto.options.login = params.login;
-        verto.options.passwd = params.passwd;
-        verto.rpcClient.loginData(params);
-    };
-
-    $.verto.prototype.logout = function(msg) {
-        var verto = this;
-        verto.rpcClient.closeSocket();
-        if (verto.callbacks.onWSClose) {
-            verto.callbacks.onWSClose(verto, false);
-        }
-        verto.purge();
-    };
-
-    $.verto.prototype.login = function(msg) {
-        var verto = this;
-        verto.logout();
-        verto.rpcClient.call('login', {});
-    };
-
-    $.verto.prototype.message = function(msg) {
-        var verto = this;
-        var err = 0;
-
-        if (!msg.to) {
-            console.error("Missing To");
-            err++;
-        }
-
-        if (!msg.body) {
-            console.error("Missing Body");
-            err++;
-        }
-
-        if (err) {
-            return false;
-        }
-
-        verto.sendMethod("verto.info", {
-            msg: msg
-        });
-
-        return true;
-    };
-
-    $.verto.prototype.processReply = function(method, success, e) {
-        var verto = this;
-        var i;
-
-        //console.log("Response: " + method, success, e);
-
-        switch (method) {
-        case "verto.subscribe":
-            for (i in e.unauthorizedChannels) {
-                drop_bad(verto, e.unauthorizedChannels[i]);
-            }
-            for (i in e.subscribedChannels) {
-                mark_ready(verto, e.subscribedChannels[i]);
-            }
-
-            break;
-        case "verto.unsubscribe":
-            //console.error(e);
-            break;
-        }
-    };
-
-    $.verto.prototype.sendMethod = function(method, params) {
-        var verto = this;
-
-        verto.rpcClient.call(method, params,
-
-        function(e) {
-            /* Success */
-            verto.processReply(method, true, e);
-        },
-
-        function(e) {
-            /* Error */
-            verto.processReply(method, false, e);
-        });
-    };
-
-    function do_sub(verto, channel, obj) {
-
-    }
-
-    function drop_bad(verto, channel) {
-        console.error("drop unauthorized channel: " + channel);
-        delete verto.eventSUBS[channel];
-    }
-
-    function mark_ready(verto, channel) {
-        for (var j in verto.eventSUBS[channel]) {
-            verto.eventSUBS[channel][j].ready = true;
-            console.log("subscribed to channel: " + channel);
-            if (verto.eventSUBS[channel][j].readyHandler) {
-                verto.eventSUBS[channel][j].readyHandler(verto, channel);
-            }
-        }
-    }
-
-    var SERNO = 1;
-
-    function do_subscribe(verto, channel, subChannels, sparams) {
-        var params = sparams || {};
-
-        var local = params.local;
-
-        var obj = {
-            eventChannel: channel,
-            userData: params.userData,
-            handler: params.handler,
-            ready: false,
-            readyHandler: params.readyHandler,
-            serno: SERNO++
-        };
-
-        var isnew = false;
-
-        if (!verto.eventSUBS[channel]) {
-            verto.eventSUBS[channel] = [];
-            subChannels.push(channel);
-            isnew = true;
-        }
-
-        verto.eventSUBS[channel].push(obj);
-
-        if (local) {
-            obj.ready = true;
-            obj.local = true;
-        }
-
-        if (!isnew && verto.eventSUBS[channel][0].ready) {
-            obj.ready = true;
-            if (obj.readyHandler) {
-                obj.readyHandler(verto, channel);
-            }
-        }
-
-        return {
-            serno: obj.serno,
-            eventChannel: channel
-        };
-
-    }
-
-    $.verto.prototype.subscribe = function(channel, sparams) {
-        var verto = this;
-        var r = [];
-        var subChannels = [];
-        var params = sparams || {};
-
-        if (typeof(channel) === "string") {
-            r.push(do_subscribe(verto, channel, subChannels, params));
-        } else {
-            for (var i in channel) {
-                r.push(do_subscribe(verto, channel, subChannels, params));
-            }
-        }
-
-        if (subChannels.length) {
-            verto.sendMethod("verto.subscribe", {
-                eventChannel: subChannels.length == 1 ? subChannels[0] : subChannels,
-                subParams: params.subParams
-            });
-        }
-
-        return r;
-    };
-
-    $.verto.prototype.unsubscribe = function(handle) {
-        var verto = this;
-        var i;
-
-        if (!handle) {
-            for (i in verto.eventSUBS) {
-                if (verto.eventSUBS[i]) {
-                    verto.unsubscribe(verto.eventSUBS[i]);
-                }
-            }
-        } else {
-            var unsubChannels = {};
-            var sendChannels = [];
-            var channel;
-
-            if (typeof(handle) == "string") {
-                delete verto.eventSUBS[handle];
-                unsubChannels[handle]++;
-            } else {
-                for (i in handle) {
-                    if (typeof(handle[i]) == "string") {
-                        channel = handle[i];
-                        delete verto.eventSUBS[channel];
-                        unsubChannels[channel]++;
-                    } else {
-                        var repl = [];
-                        channel = handle[i].eventChannel;
-
-                        for (var j in verto.eventSUBS[channel]) {
-                            if (verto.eventSUBS[channel][j].serno == handle[i].serno) {} else {
-                                repl.push(verto.eventSUBS[channel][j]);
-                            }
-                        }
-
-                        verto.eventSUBS[channel] = repl;
-
-                        if (verto.eventSUBS[channel].length === 0) {
-                            delete verto.eventSUBS[channel];
-                            unsubChannels[channel]++;
-                        }
-                    }
-                }
-            }
-
-            for (var u in unsubChannels) {
-                console.log("Sending Unsubscribe for: ", u);
-                sendChannels.push(u);
-            }
-
-            if (sendChannels.length) {
-                verto.sendMethod("verto.unsubscribe", {
-                    eventChannel: sendChannels.length == 1 ? sendChannels[0] : sendChannels
-                });
-            }
-        }
-    };
-
-    $.verto.prototype.broadcast = function(channel, params) {
-        var verto = this;
-        var msg = {
-            eventChannel: channel,
-            data: {}
-        };
-        for (var i in params) {
-            msg.data[i] = params[i];
-        }
-        verto.sendMethod("verto.broadcast", msg);
-    };
-
-    $.verto.prototype.purge = function(callID) {
-        var verto = this;
-        var x = 0;
-        var i;
-
-        for (i in verto.dialogs) {
-            if (!x) {
-                console.log("purging dialogs");
-            }
-            x++;
-            verto.dialogs[i].setState($.verto.enum.state.purge);
-        }
-
-        for (i in verto.eventSUBS) {
-            if (verto.eventSUBS[i]) {
-                console.log("purging subscription: " + i);
-                delete verto.eventSUBS[i];
-            }
-        }
-    };
-
-    $.verto.prototype.hangup = function(callID) {
-        var verto = this;
-        if (callID) {
-            var dialog = verto.dialogs[callID];
-
-            if (dialog) {
-                dialog.hangup();
-            }
-        } else {
-            for (var i in verto.dialogs) {
-                verto.dialogs[i].hangup();
-            }
-        }
-    };
-
-    $.verto.prototype.newCall = function(args, callbacks) {
-        var verto = this;
-
-        if (!verto.rpcClient.socketReady()) {
-            console.error("Not Connected...");
-            return;
-        }
-
-        if (args["useCamera"]) {
-            verto.options.deviceParams["useCamera"] = args["useCamera"];
-        }
-
-        var dialog = new $.verto.dialog($.verto.enum.direction.outbound, this, args);
-
-        if (callbacks) {
-            dialog.callbacks = callbacks;
-        }
-
-        dialog.invite();
-
-        return dialog;
-    };
-
-    $.verto.prototype.handleMessage = function(data) {
-        var verto = this;
-
-        if (!(data && data.method)) {
-            console.error("Invalid Data", data);
-            return;
-        }
-
-        if (data.params.callID) {
-            var dialog = verto.dialogs[data.params.callID];
-           
-           if (data.method === "verto.attach" && dialog) {
-               delete dialog.verto.dialogs[dialog.callID];
-               dialog.rtc.stop();
-               dialog = null;
-           }
-
-            if (dialog) {
-
-                switch (data.method) {
-                case 'verto.bye':
-                    dialog.hangup(data.params);
-                    break;
-                case 'verto.answer':
-                    dialog.handleAnswer(data.params);
-                    break;
-                case 'verto.media':
-                    dialog.handleMedia(data.params);
-                    break;
-                case 'verto.display':
-                    dialog.handleDisplay(data.params);
-                    break;
-                case 'verto.info':
-                    dialog.handleInfo(data.params);
-                    break;
-                default:
-                    console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED", dialog, data.method);
-                    break;
-                }
-            } else {
-
-                switch (data.method) {
-                case 'verto.attach':
-                    data.params.attach = true;
-
-                    if (data.params.sdp && data.params.sdp.indexOf("m=video") > 0) {
-                        data.params.useVideo = true;
-                    }
-
-                    if (data.params.sdp && data.params.sdp.indexOf("stereo=1") > 0) {
-                        data.params.useStereo = true;
-                    }
-
-                    dialog = new $.verto.dialog($.verto.enum.direction.inbound, verto, data.params);
-                    dialog.setState($.verto.enum.state.recovering);
-
-                    break;
-                case 'verto.invite':
-
-                    if (data.params.sdp && data.params.sdp.indexOf("m=video") > 0) {
-                        data.params.wantVideo = true;
-                    }
-
-                    if (data.params.sdp && data.params.sdp.indexOf("stereo=1") > 0) {
-                        data.params.useStereo = true;
-                    }
-
-                    dialog = new $.verto.dialog($.verto.enum.direction.inbound, verto, data.params);
-                    break;
-                default:
-                    console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED");
-                    break;
-                }
-            }
-
-            return {
-                method: data.method
-            };
-        } else {
-            switch (data.method) {
-            case 'verto.punt':
-                verto.purge();
-               verto.logout();
-               break;
-            case 'verto.event':
-                var list = null;
-                var key = null;
-
-                if (data.params) {
-                    key = data.params.eventChannel;
-                }
-
-                if (key) {
-                    list = verto.eventSUBS[key];
-
-                    if (!list) {
-                        list = verto.eventSUBS[key.split(".")[0]];
-                    }
-                }
-
-                if (!list && key && key === verto.sessid) {
-                    if (verto.callbacks.onMessage) {
-                        verto.callbacks.onMessage(verto, null, $.verto.enum.message.pvtEvent, data.params);
-                    }
-                } else if (!list && key && verto.dialogs[key]) {
-                    verto.dialogs[key].sendMessage($.verto.enum.message.pvtEvent, data.params);
-                } else if (!list) {
-                    if (!key) {
-                        key = "UNDEFINED";
-                    }
-                    console.error("UNSUBBED or invalid EVENT " + key + " IGNORED");
-                } else {
-                    for (var i in list) {
-                        var sub = list[i];
-
-                        if (!sub || !sub.ready) {
-                            console.error("invalid EVENT for " + key + " IGNORED");
-                        } else if (sub.handler) {
-                            sub.handler(verto, data.params, sub.userData);
-                        } else if (verto.callbacks.onEvent) {
-                            verto.callbacks.onEvent(verto, data.params, sub.userData);
-                        } else {
-                            console.log("EVENT:", data.params);
-                        }
-                    }
-                }
-
-                break;
-
-            case "verto.info":
-                if (verto.callbacks.onMessage) {
-                    verto.callbacks.onMessage(verto, null, $.verto.enum.message.info, data.params.msg);
-                }
-                //console.error(data);
-                console.debug("MESSAGE from: " + data.params.msg.from, data.params.msg.body);
-
-                break;
-
-            case 'verto.clientReady':
-                if (verto.callbacks.onMessage) {
-                    verto.callbacks.onMessage(verto, null, $.verto.enum.message.clientReady, data.params);
-                }
-                console.debug("CLIENT READY", data.params);
-                break;
-
-            default:
-                console.error("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED", data.method);
-                break;
-            }
-        }
-    };
-
-    var del_array = function(array, name) {
-        var r = [];
-        var len = array.length;
-
-        for (var i = 0; i < len; i++) {
-            if (array[i] != name) {
-                r.push(array[i]);
-            }
-        }
-
-        return r;
-    };
-
-    var hashArray = function() {
-        var vha = this;
-
-        var hash = {};
-        var array = [];
-
-        vha.reorder = function(a) {
-            array = a;
-            var h = hash;
-            hash = {};
-
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                var key = array[i];
-                if (h[key]) {
-                    hash[key] = h[key];
-                    delete h[key];
-                }
-            }
-            h = undefined;
-        };
-
-        vha.clear = function() {
-            hash = undefined;
-            array = undefined;
-            hash = {};
-            array = [];
-        };
-
-        vha.add = function(name, val, insertAt) {
-            var redraw = false;
-
-            if (!hash[name]) {
-                if (insertAt === undefined || insertAt < 0 || insertAt >= array.length) {
-                    array.push(name);
-                } else {
-                    var x = 0;
-                    var n = [];
-                    var len = array.length;
-
-                    for (var i = 0; i < len; i++) {
-                        if (x++==insertAt) {
-                            n.push(name);
-                        }
-                        n.push(array[i]);
-                    }
-
-                    array = undefined;
-                    array = n;
-                    n = undefined;
-                    redraw = true;
-                }
-            }
-
-            hash[name] = val;
-
-            return redraw;
-        };
-
-        vha.del = function(name) {
-            var r = false;
-
-            if (hash[name]) {
-                array = del_array(array, name);
-                delete hash[name];
-                r = true;
-            } else {
-                console.error("can't del nonexistant key " + name);
-            }
-
-            return r;
-        };
-
-        vha.get = function(name) {
-            return hash[name];
-        };
-
-        vha.order = function() {
-            return array;
-        };
-
-        vha.hash = function() {
-            return hash;
-        };
-
-        vha.indexOf = function(name) {
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                if (array[i] == name) {
-                    return i;
-                }
-            }
-        };
-
-        vha.arrayLen = function() {
-            return array.length;
-        };
-
-        vha.asArray = function() {
-            var r = [];
-
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                var key = array[i];
-                r.push(hash[key]);
-            }
-
-            return r;
-        };
-
-        vha.each = function(cb) {
-            var len = array.length;
-
-            for (var i = 0; i < len; i++) {
-                cb(array[i], hash[array[i]]);
-            }
-        };
-
-        vha.dump = function(html) {
-            var str = "";
-
-            vha.each(function(name, val) {
-                str += "name: " + name + " val: " + JSON.stringify(val) + (html ? "<br>" : "\n");
-            });
-
-            return str;
-        };
-
-    };
-
-    $.verto.liveArray = function(verto, context, name, config) {
-        var la = this;
-        var lastSerno = 0;
-        var binding = null;
-        var user_obj = config.userObj;
-        var local = false;
-
-        // Inherit methods of hashArray
-        hashArray.call(la);
-
-        // Save the hashArray add, del, reorder, clear methods so we can make our own.
-        la._add = la.add;
-        la._del = la.del;
-        la._reorder = la.reorder;
-        la._clear = la.clear;
-
-        la.context = context;
-        la.name = name;
-        la.user_obj = user_obj;
-
-        la.verto = verto;
-        la.broadcast = function(channel, obj) {
-            verto.broadcast(channel, obj);
-        };
-        la.errs = 0;
-
-        la.clear = function() {
-            la._clear();
-            lastSerno = 0;
-
-            if (la.onChange) {
-                la.onChange(la, {
-                    action: "clear"
-                });
-            }
-        };
-
-        la.checkSerno = function(serno) {
-            if (serno < 0) {
-                return true;
-            }
-
-            if (lastSerno > 0 && serno != (lastSerno + 1)) {
-                if (la.onErr) {
-                    la.onErr(la, {
-                        lastSerno: lastSerno,
-                        serno: serno
-                    });
-                }
-                la.errs++;
-                console.debug(la.errs);
-                if (la.errs < 3) {
-                    la.bootstrap(la.user_obj);
-                }
-                return false;
-            } else {
-                lastSerno = serno;
-                return true;
-            }
-        };
-
-        la.reorder = function(serno, a) {
-            if (la.checkSerno(serno)) {
-                la._reorder(a);
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "reorder"
-                    });
-                }
-            }
-        };
-
-        la.init = function(serno, val, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "init",
-                        index: index,
-                        key: key,
-                        data: val
-                    });
-                }
-            }
-        };
-
-        la.bootObj = function(serno, val) {
-            if (la.checkSerno(serno)) {
-
-                //la.clear();
-                for (var i in val) {
-                    la._add(val[i][0], val[i][1]);
-                }
-
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "bootObj",
-                        data: val,
-                        redraw: true
-                    });
-                }
-            }
-        };
-
-        // @param serno  La is the serial number for la particular request.
-        // @param key    If looking at it as a hash table, la represents the key in the hashArray object where you want to store the val object.
-        // @param index  If looking at it as an array, la represents the position in the array where you want to store the val object.
-        // @param val    La is the object you want to store at the key or index location in the hash table / array.
-        la.add = function(serno, val, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                var redraw = la._add(key, val, index);
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "add",
-                        index: index,
-                        key: key,
-                        data: val,
-                        redraw: redraw
-                    });
-                }
-            }
-        };
-
-        la.modify = function(serno, val, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                la._add(key, val, index);
-                if (la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "modify",
-                        key: key,
-                        data: val,
-                        index: index
-                    });
-                }
-            }
-        };
-
-        la.del = function(serno, key, index) {
-            if (key === null || key === undefined) {
-                key = serno;
-            }
-            if (la.checkSerno(serno)) {
-                if (index === null || index < 0 || index === undefined) {
-                    index = la.indexOf(key);
-                }
-                var ok = la._del(key);
-
-                if (ok && la.onChange) {
-                    la.onChange(la, {
-                        serno: serno,
-                        action: "del",
-                        key: key,
-                        index: index
-                    });
-                }
-            }
-        };
-
-        var eventHandler = function(v, e, la) {
-            var packet = e.data;
-
-            //console.error("READ:", packet);
-
-            if (packet.name != la.name) {
-                return;
-            }
-
-            switch (packet.action) {
-
-            case "init":
-                la.init(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex);
-                break;
-
-            case "bootObj":
-                la.bootObj(packet.wireSerno, packet.data);
-                break;
-            case "add":
-                la.add(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex);
-                break;
-
-            case "modify":
-                if (! (packet.arrIndex || packet.hashKey)) {
-                    console.error("Invalid Packet", packet);
-                } else {
-                    la.modify(packet.wireSerno, packet.data, packet.hashKey, packet.arrIndex);
-                }
-                break;
-            case "del":
-                if (! (packet.arrIndex || packet.hashKey)) {
-                    console.error("Invalid Packet", packet);
-                } else {
-                    la.del(packet.wireSerno, packet.hashKey, packet.arrIndex);
-                }
-                break;
-
-            case "clear":
-                la.clear();
-                break;
-
-            case "reorder":
-                la.reorder(packet.wireSerno, packet.order);
-                break;
-
-            default:
-                if (la.checkSerno(packet.wireSerno)) {
-                    if (la.onChange) {
-                        la.onChange(la, {
-                            serno: packet.wireSerno,
-                            action: packet.action,
-                            data: packet.data
-                        });
-                    }
-                }
-                break;
-            }
-        };
-
-        if (la.context) {
-            binding = la.verto.subscribe(la.context, {
-                handler: eventHandler,
-                userData: la,
-                subParams: config.subParams
-            });
-        }
-
-        la.destroy = function() {
-            la._clear();
-            la.verto.unsubscribe(binding);
-        };
-
-        la.sendCommand = function(cmd, obj) {
-            var self = la;
-            self.broadcast(self.context, {
-                liveArray: {
-                    command: cmd,
-                    context: self.context,
-                    name: self.name,
-                    obj: obj
-                }
-            });
-        };
-
-        la.bootstrap = function(obj) {
-            var self = la;
-            la.sendCommand("bootstrap", obj);
-            //self.heartbeat();
-        };
-
-        la.changepage = function(obj) {
-            var self = la;
-            self.clear();
-            self.broadcast(self.context, {
-                liveArray: {
-                    command: "changepage",
-                    context: la.context,
-                    name: la.name,
-                    obj: obj
-                }
-            });
-        };
-
-        la.heartbeat = function(obj) {
-            var self = la;
-
-            var callback = function() {
-                self.heartbeat.call(self, obj);
-            };
-            self.broadcast(self.context, {
-                liveArray: {
-                    command: "heartbeat",
-                    context: self.context,
-                    name: self.name,
-                    obj: obj
-                }
-            });
-            self.hb_pid = setTimeout(callback, 30000);
-        };
-
-        la.bootstrap(la.user_obj);
-    };
-
-    $.verto.liveTable = function(verto, context, name, jq, config) {
-        var dt;
-        var la = new $.verto.liveArray(verto, context, name, {
-            subParams: config.subParams
-        });
-        var lt = this;
-
-        lt.liveArray = la;
-        lt.dataTable = dt;
-        lt.verto = verto;
-
-        lt.destroy = function() {
-            if (dt) {
-                dt.fnDestroy();
-            }
-            if (la) {
-                la.destroy();
-            }
-
-            dt = null;
-            la = null;
-        };
-
-        la.onErr = function(obj, args) {
-            console.error("Error: ", obj, args);
-        };
-
-       /* back compat so jsonstatus can always be enabled */
-       function genRow(data) {
-           if (typeof(data[4]) === "string" && data[4].indexOf("{") > -1) {
-               var tmp = $.parseJSON(data[4]);
-               data[4] = tmp.oldStatus;
-               data[5] = null;
-           }
-           return data;
-       }
-
-       function genArray(obj) {
-           var data = obj.asArray();
-
-            for (var i in data) {
-               data[i] = genRow(data[i]);
-           }
-
-           return data;
-       }
-
-
-        la.onChange = function(obj, args) {
-            var index = 0;
-            var iserr = 0;
-
-            if (!dt) {
-                if (!config.aoColumns) {
-                    if (args.action != "init") {
-                        return;
-                    }
-
-                    config.aoColumns = [];
-
-                    for (var i in args.data) {
-                        config.aoColumns.push({
-                            "sTitle": args.data[i]
-                        });
-                    }
-                }
-
-                dt = jq.dataTable(config);
-            }
-
-            if (dt && (args.action == "del" || args.action == "modify")) {
-                index = args.index;
-
-                if (index === undefined && args.key) {
-                    index = la.indexOf(args.key);
-                }
-
-                if (index === undefined) {
-                    console.error("INVALID PACKET Missing INDEX\n", args);
-                    return;
-                }
-            }
-
-            if (config.onChange) {
-                config.onChange(obj, args);
-            }
-
-            try {
-                switch (args.action) {
-                case "bootObj":
-                    if (!args.data) {
-                        console.error("missing data");
-                        return;
-                    }
-                    dt.fnClearTable();
-                    dt.fnAddData(genArray(obj));
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "add":
-                    if (!args.data) {
-                        console.error("missing data");
-                        return;
-                    }
-                    if (args.redraw > -1) {
-                        // specific position, more costly
-                        dt.fnClearTable();
-                        dt.fnAddData(genArray(obj));
-                    } else {
-                        dt.fnAddData(genRow(args.data));
-                    }
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "modify":
-                    if (!args.data) {
-                        return;
-                    }
-                    //console.debug(args, index);
-                    dt.fnUpdate(genRow(args.data), index);
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "del":
-                    dt.fnDeleteRow(index);
-                    dt.fnAdjustColumnSizing();
-                    break;
-                case "clear":
-                    dt.fnClearTable();
-                    break;
-                case "reorder":
-                    // specific position, more costly
-                    dt.fnClearTable();
-                    dt.fnAddData(genArray(obj));
-                    break;
-                case "hide":
-                    jq.hide();
-                    break;
-
-                case "show":
-                    jq.show();
-                    break;
-
-                }
-            } catch(err) {
-                console.error("ERROR: " + err);
-                iserr++;
-            }
-
-            if (iserr) {
-                obj.errs++;
-                if (obj.errs < 3) {
-                    obj.bootstrap(obj.user_obj);
-                }
-            } else {
-                obj.errs = 0;
-            }
-
-        };
-
-        la.onChange(la, {
-            action: "init"
-        });
-
-    };
-
-    var CONFMAN_SERNO = 1;
-
-    /*
-        Conference Manager without jQuery table.
-     */
-
-    $.verto.conf = function(verto, params) {
-        var conf = this;
-
-        conf.params = $.extend({
-            dialog: null,
-            hasVid: false,
-            laData: null,
-            onBroadcast: null,
-            onLaChange: null,
-            onLaRow: null
-        }, params);
-
-        conf.verto = verto;
-        conf.serno = CONFMAN_SERNO++;
-
-        createMainModeratorMethods();
-
-        verto.subscribe(conf.params.laData.modChannel, {
-            handler: function(v, e) {
-                if (conf.params.onBroadcast) {
-                    conf.params.onBroadcast(verto, conf, e.data);
-                }
-            }
-        });
-
-        verto.subscribe(conf.params.laData.infoChannel, {
-            handler: function(v, e) {
-                if (typeof(conf.params.infoCallback) === "function") {
-                    conf.params.infoCallback(v,e);
-                }
-            }
-        });
-
-        verto.subscribe(conf.params.laData.chatChannel, {
-            handler: function(v, e) {
-                if (typeof(conf.params.chatCallback) === "function") {
-                    conf.params.chatCallback(v,e);
-                }
-            }
-        });
-    };
-
-    $.verto.conf.prototype.modCommand = function(cmd, id, value) {
-        var conf = this;
-
-        conf.verto.rpcClient.call("verto.broadcast", {
-            "eventChannel": conf.params.laData.modChannel,
-            "data": {
-                "application": "conf-control",
-                "command": cmd,
-                "id": id,
-                "value": value
-            }
-        });
-    };
-
-    $.verto.conf.prototype.destroy = function() {
-        var conf = this;
-
-        conf.destroyed = true;
-        conf.params.onBroadcast(conf.verto, conf, 'destroy');
-
-        if (conf.params.laData.modChannel) {
-            conf.verto.unsubscribe(conf.params.laData.modChannel);
-        }
-
-        if (conf.params.laData.chatChannel) {
-            conf.verto.unsubscribe(conf.params.laData.chatChannel);
-        }
-
-        if (conf.params.laData.infoChannel) {
-            conf.verto.unsubscribe(conf.params.laData.infoChannel);
-        }
-    };
-
-    function createMainModeratorMethods() {
-        $.verto.conf.prototype.listVideoLayouts = function() {
-            this.modCommand("list-videoLayouts", null, null);
-        };
-
-        $.verto.conf.prototype.play = function(file) {
-            this.modCommand("play", null, file);
-        };
-
-        $.verto.conf.prototype.stop = function() {
-            this.modCommand("stop", null, "all");
-        };
-
-        $.verto.conf.prototype.deaf = function(memberID) {
-            this.modCommand("deaf", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.undeaf = function(memberID) {
-            this.modCommand("undeaf", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.record = function(file) {
-            this.modCommand("recording", null, ["start", file]);
-        };
-
-        $.verto.conf.prototype.stopRecord = function() {
-            this.modCommand("recording", null, ["stop", "all"]);
-        };
-
-        $.verto.conf.prototype.snapshot = function(file) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-write-png", null, file);
-        };
-
-        $.verto.conf.prototype.setVideoLayout = function(layout, canvasID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-           if (canvasID) {
-               this.modCommand("vid-layout", null, [layout, canvasID]);
-           } else {
-               this.modCommand("vid-layout", null, layout);
-           }
-        };
-
-        $.verto.conf.prototype.kick = function(memberID) {
-            this.modCommand("kick", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.muteMic = function(memberID) {
-            this.modCommand("tmute", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.muteVideo = function(memberID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("tvmute", parseInt(memberID));
-        };
-
-        $.verto.conf.prototype.presenter = function(memberID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-res-id", parseInt(memberID), "presenter");
-        };
-
-        $.verto.conf.prototype.videoFloor = function(memberID) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-floor", parseInt(memberID), "force");
-        };
-
-        $.verto.conf.prototype.banner = function(memberID, text) {
-            if (!this.params.hasVid) {
-                throw 'Conference has no video';
-            }
-            this.modCommand("vid-banner", parseInt(memberID), escape(text));
-        };
-
-        $.verto.conf.prototype.volumeDown = function(memberID) {
-            this.modCommand("volume_out", parseInt(memberID), "down");
-        };
-
-        $.verto.conf.prototype.volumeUp = function(memberID) {
-            this.modCommand("volume_out", parseInt(memberID), "up");
-        };
-
-        $.verto.conf.prototype.gainDown = function(memberID) {
-            this.modCommand("volume_in", parseInt(memberID), "down");
-        };
-
-        $.verto.conf.prototype.gainUp = function(memberID) {
-            this.modCommand("volume_in", parseInt(memberID), "up");
-        };
-
-        $.verto.conf.prototype.transfer = function(memberID, exten) {
-            this.modCommand("transfer", parseInt(memberID), exten);
-        };
-
-        $.verto.conf.prototype.sendChat = function(message, type) {
-            var conf = this;
-            conf.verto.rpcClient.call("verto.broadcast", {
-                "eventChannel": conf.params.laData.chatChannel,
-                "data": {
-                    "action": "send",
-                    "message": message,
-                    "type": type
-                }
-            });
-        };
-            
-    }
-
-    $.verto.modfuncs = {};
-
-    $.verto.confMan = function(verto, params) {
-        var confMan = this;
-
-        confMan.params = $.extend({
-            tableID: null,
-            statusID: null,
-            mainModID: null,
-            dialog: null,
-            hasVid: false,
-            laData: null,
-            onBroadcast: null,
-            onLaChange: null,
-            onLaRow: null
-        }, params);
-
-        confMan.verto = verto;
-        confMan.serno = CONFMAN_SERNO++;
-       confMan.canvasCount = confMan.params.laData.canvasCount;
-       
-        function genMainMod(jq) {
-            var play_id = "play_" + confMan.serno;
-            var stop_id = "stop_" + confMan.serno;
-            var recording_id = "recording_" + confMan.serno;
-            var snapshot_id = "snapshot_" + confMan.serno;
-            var rec_stop_id = "recording_stop" + confMan.serno;
-            var div_id = "confman_" + confMan.serno;
-
-            var html =  "<div id='" + div_id + "'><br>" +
-               "<button class='ctlbtn' id='" + play_id + "'>Play</button>" +
-               "<button class='ctlbtn' id='" + stop_id + "'>Stop</button>" +
-               "<button class='ctlbtn' id='" + recording_id + "'>Record</button>" +
-               "<button class='ctlbtn' id='" + rec_stop_id + "'>Record Stop</button>" +
-               (confMan.params.hasVid ? "<button class='ctlbtn' id='" + snapshot_id + "'>PNG Snapshot</button>" : "") +
-               "<br><br></div>";
-
-            jq.html(html);
-
-           $.verto.modfuncs.change_video_layout = function(id, canvas_id) {        
-               var val = $("#" + id + " option:selected").text();
-               if (val !== "none") {
-                    confMan.modCommand("vid-layout", null, [val, canvas_id]);
-               }
-           };
-
-           if (confMan.params.hasVid) {
-               for (var j = 0; j < confMan.canvasCount; j++) {
-                   var vlayout_id = "confman_vid_layout_" + j + "_" + confMan.serno;
-                   var vlselect_id = "confman_vl_select_" + j + "_" + confMan.serno;
-               
-
-                   var vlhtml =  "<div id='" + vlayout_id + "'><br>" +
-                       "<b>Video Layout Canvas " + (j+1) + 
-                       "</b> <select onChange='$.verto.modfuncs.change_video_layout(\"" + vlayout_id + "\", \"" + (j+1) + "\")' id='" + vlselect_id + "'></select> " +
-                       "<br><br></div>";
-                   jq.append(vlhtml);
-               }
-
-               $("#" + snapshot_id).click(function() {
-                    var file = prompt("Please enter file name", "");
-                   if (file) {
-                       confMan.modCommand("vid-write-png", null, file);
-                   }
-               });
-           }
-
-            $("#" + play_id).click(function() {
-                var file = prompt("Please enter file name", "");
-               if (file) {
-                    confMan.modCommand("play", null, file);
-               }
-            });
-
-            $("#" + stop_id).click(function() {
-                confMan.modCommand("stop", null, "all");
-            });
-
-            $("#" + recording_id).click(function() {
-                var file = prompt("Please enter file name", "");
-               if (file) {
-                    confMan.modCommand("recording", null, ["start", file]);
-               }
-            });
-
-            $("#" + rec_stop_id).click(function() {
-                confMan.modCommand("recording", null, ["stop", "all"]);
-            });
-
-        }
-
-        function genControls(jq, rowid) {
-            var x = parseInt(rowid);
-            var kick_id = "kick_" + x;
-            var canvas_in_next_id = "canvas_in_next_" + x;
-            var canvas_in_prev_id = "canvas_in_prev_" + x;
-            var canvas_out_next_id = "canvas_out_next_" + x;
-            var canvas_out_prev_id = "canvas_out_prev_" + x;
-
-            var canvas_in_set_id = "canvas_in_set_" + x;
-            var canvas_out_set_id = "canvas_out_set_" + x;
-
-            var layer_set_id = "layer_set_" + x;
-            var layer_next_id = "layer_next_" + x;
-            var layer_prev_id = "layer_prev_" + x;
-           
-            var tmute_id = "tmute_" + x;
-            var tvmute_id = "tvmute_" + x;
-            var vbanner_id = "vbanner_" + x;
-            var tvpresenter_id = "tvpresenter_" + x;
-            var tvfloor_id = "tvfloor_" + x;
-            var box_id = "box_" + x;
-            var gainup_id = "gain_in_up" + x;
-            var gaindn_id = "gain_in_dn" + x;
-            var volup_id = "vol_in_up" + x;
-            var voldn_id = "vol_in_dn" + x;
-            var transfer_id = "transfer" + x;
-           
-
-            var html = "<div id='" + box_id + "'>";
-
-           html += "<b>General Controls</b><hr noshade>";
-
-            html += "<button class='ctlbtn' id='" + kick_id + "'>Kick</button>" +
-                "<button class='ctlbtn' id='" + tmute_id + "'>Mute</button>" +
-                "<button class='ctlbtn' id='" + gainup_id + "'>Gain -</button>" +
-                "<button class='ctlbtn' id='" + gaindn_id + "'>Gain +</button>" +
-                "<button class='ctlbtn' id='" + voldn_id + "'>Vol -</button>" +
-                "<button class='ctlbtn' id='" + volup_id + "'>Vol +</button>" +
-                "<button class='ctlbtn' id='" + transfer_id + "'>Transfer</button>";
-               
-           if (confMan.params.hasVid) {
-               html += "<br><br><b>Video Controls</b><hr noshade>";
-
-
-                html += "<button class='ctlbtn' id='" + tvmute_id + "'>VMute</button>" +
-                    "<button class='ctlbtn' id='" + tvpresenter_id + "'>Presenter</button>" +
-                    "<button class='ctlbtn' id='" + tvfloor_id + "'>Vid Floor</button>" +
-                    "<button class='ctlbtn' id='" + vbanner_id + "'>Banner</button>";
-
-               if (confMan.canvasCount > 1) {
-                    html += "<br><br><b>Canvas Controls</b><hr noshade>" +
-                       "<button class='ctlbtn' id='" + canvas_in_set_id + "'>Set Input Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_in_prev_id + "'>Prev Input Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_in_next_id + "'>Next Input Canvas</button>" +
-                       
-                   "<br>" +
-                       
-                   "<button class='ctlbtn' id='" + canvas_out_set_id + "'>Set Watching Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_out_prev_id + "'>Prev Watching Canvas</button>" +
-                       "<button class='ctlbtn' id='" + canvas_out_next_id + "'>Next Watching Canvas</button>";
-               }
-               
-               html += "<br>" +
-
-                "<button class='ctlbtn' id='" + layer_set_id + "'>Set Layer</button>" +
-                    "<button class='ctlbtn' id='" + layer_prev_id + "'>Prev Layer</button>" +
-                    "<button class='ctlbtn' id='" + layer_next_id + "'>Next Layer</button>" +
-
-
-
-                    "</div>";
-            }
-
-            jq.html(html);
-
-
-            if (!jq.data("mouse")) {
-                $("#" + box_id).hide();
-            }
-
-            jq.mouseover(function(e) {
-                jq.data({"mouse": true});
-                $("#" + box_id).show();
-            });
-
-            jq.mouseout(function(e) {
-                jq.data({"mouse": false});
-                $("#" + box_id).hide();
-            });
-
-            $("#" + transfer_id).click(function() {
-                var xten = prompt("Enter Extension");
-               if (xten) {
-                    confMan.modCommand("transfer", x, xten);
-               }
-            });
-
-            $("#" + kick_id).click(function() {
-                confMan.modCommand("kick", x);
-            });
-
-
-            $("#" + layer_set_id).click(function() {
-                var cid = prompt("Please enter layer ID", "");
-               if (cid) {
-                    confMan.modCommand("vid-layer", x, cid);
-               }
-            });
-
-            $("#" + layer_next_id).click(function() {
-                confMan.modCommand("vid-layer", x, "next");
-            });
-            $("#" + layer_prev_id).click(function() {
-                confMan.modCommand("vid-layer", x, "prev");
-            });
-
-            $("#" + canvas_in_set_id).click(function() {
-                var cid = prompt("Please enter canvas ID", "");
-               if (cid) {
-                    confMan.modCommand("vid-canvas", x, cid);
-               }
-            });
-
-            $("#" + canvas_out_set_id).click(function() {
-                var cid = prompt("Please enter canvas ID", "");
-               if (cid) {
-                    confMan.modCommand("vid-watching-canvas", x, cid);
-               }
-            });
-
-            $("#" + canvas_in_next_id).click(function() {
-                confMan.modCommand("vid-canvas", x, "next");
-            });
-            $("#" + canvas_in_prev_id).click(function() {
-                confMan.modCommand("vid-canvas", x, "prev");
-            });
-
-
-            $("#" + canvas_out_next_id).click(function() {
-                confMan.modCommand("vid-watching-canvas", x, "next");
-            });
-            $("#" + canvas_out_prev_id).click(function() {
-                confMan.modCommand("vid-watching-canvas", x, "prev");
-            });
-           
-            $("#" + tmute_id).click(function() {
-                confMan.modCommand("tmute", x);
-            });
-
-           if (confMan.params.hasVid) {
-               $("#" + tvmute_id).click(function() {
-                    confMan.modCommand("tvmute", x);
-               });
-               $("#" + tvpresenter_id).click(function() {
-                    confMan.modCommand("vid-res-id", x, "presenter");
-               });
-               $("#" + tvfloor_id).click(function() {
-                    confMan.modCommand("vid-floor", x, "force");
-               });
-               $("#" + vbanner_id).click(function() {
-                    var text = prompt("Please enter text", "");
-                   if (text) {
-                       confMan.modCommand("vid-banner", x, escape(text));
-                   }
-               });
-           }
-
-            $("#" + gainup_id).click(function() {
-                confMan.modCommand("volume_in", x, "up");
-            });
-
-            $("#" + gaindn_id).click(function() {
-                confMan.modCommand("volume_in", x, "down");
-            });
-
-            $("#" + volup_id).click(function() {
-                confMan.modCommand("volume_out", x, "up");
-            });
-
-            $("#" + voldn_id).click(function() {
-                confMan.modCommand("volume_out", x, "down");
-            });
-
-            return html;
-        }
-
-        var atitle = "";
-        var awidth = 0;
-
-        //$(".jsDataTable").width(confMan.params.hasVid ? "900px" : "800px");
-
-        verto.subscribe(confMan.params.laData.infoChannel, {
-            handler: function(v, e) {
-                if (typeof(confMan.params.infoCallback) === "function") {
-                    confMan.params.infoCallback(v,e);
-                }
-            }
-        });
-
-       verto.subscribe(confMan.params.laData.chatChannel, {
-           handler: function(v, e) {
-               if (typeof(confMan.params.chatCallback) === "function") {
-                   confMan.params.chatCallback(v,e);
-               }
-           }
-       });
-
-        if (confMan.params.laData.role === "moderator") {
-            atitle = "Action";
-            awidth = 600;
-
-            if (confMan.params.mainModID) {
-                genMainMod($(confMan.params.mainModID));
-                $(confMan.params.displayID).html("Moderator Controls Ready<br><br>");
-            } else {
-                $(confMan.params.mainModID).html("");
-            }
-
-            verto.subscribe(confMan.params.laData.modChannel, {
-                handler: function(v, e) {
-                    //console.error("MODDATA:", e.data);
-                    if (confMan.params.onBroadcast) {
-                        confMan.params.onBroadcast(verto, confMan, e.data);
-                    }
-
-                   if (e.data["conf-command"] === "list-videoLayouts") {
-                       for (var j = 0; j < confMan.canvasCount; j++) {
-                           var vlselect_id = "#confman_vl_select_" + j + "_" + confMan.serno;
-                           var vlayout_id = "#confman_vid_layout_" + j + "_" + confMan.serno;
-                           
-                           var x = 0;
-                           var options;
-                           
-                           $(vlselect_id).selectmenu({});
-                           $(vlselect_id).selectmenu("enable");
-                           $(vlselect_id).empty();
-                           
-                           $(vlselect_id).append(new Option("Choose a Layout", "none"));
-
-                           if (e.data.responseData) {
-                               var rdata = [];
-
-                               for (var i in e.data.responseData) {
-                                   rdata.push(e.data.responseData[i].name);
-                               }
-                               
-                               options = rdata.sort(function(a, b) {
-                                   var ga = a.substring(0, 6) == "group:" ? true : false;
-                                   var gb = b.substring(0, 6) == "group:" ? true : false;
-                                   
-                                   if ((ga || gb) && ga != gb) {
-                                       return ga ? -1 : 1;
-                                   }
-                                   
-                                   return ( ( a == b ) ? 0 : ( ( a > b ) ? 1 : -1 ) );
-                               });
-
-                               for (var i in options) {
-                                   $(vlselect_id).append(new Option(options[i], options[i]));
-                                   x++;
-                               }
-                           }
-
-                           if (x) {
-                               $(vlselect_id).selectmenu('refresh', true);
-                           } else {
-                               $(vlayout_id).hide();
-                           }
-                       }
-                   } else {
-
-                       if (!confMan.destroyed && confMan.params.displayID) {
-                            $(confMan.params.displayID).html(e.data.response + "<br><br>");
-                            if (confMan.lastTimeout) {
-                               clearTimeout(confMan.lastTimeout);
-                               confMan.lastTimeout = 0;
-                            }
-                            confMan.lastTimeout = setTimeout(function() { $(confMan.params.displayID).html(confMan.destroyed ? "" : "Moderator Controls Ready<br><br>");}, 4000);
-                       }
-                   }
-                }
-            });
-
-
-           if (confMan.params.hasVid) {
-               confMan.modCommand("list-videoLayouts", null, null);
-           }
-        }
-
-        var row_callback = null;
-
-        if (confMan.params.laData.role === "moderator") {
-            row_callback = function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
-                if (!aData[5]) {
-                    var $row = $('td:eq(5)', nRow);
-                    genControls($row, aData);
-
-                    if (confMan.params.onLaRow) {
-                        confMan.params.onLaRow(verto, confMan, $row, aData);
-                    }
-                }
-            };
-        }
-
-        confMan.lt = new $.verto.liveTable(verto, confMan.params.laData.laChannel, confMan.params.laData.laName, $(confMan.params.tableID), {
-            subParams: {
-                callID: confMan.params.dialog ? confMan.params.dialog.callID : null
-            },
-
-            "onChange": function(obj, args) {
-                $(confMan.params.statusID).text("Conference Members: " + " (" + obj.arrayLen() + " Total)");
-                if (confMan.params.onLaChange) {
-                    confMan.params.onLaChange(verto, confMan, $.verto.enum.confEvent.laChange, obj, args);
-                }
-            },
-
-            "aaData": [],
-            "aoColumns": [
-                {
-                    "sTitle": "ID",
-                    "sWidth": "50"
-                },
-                {
-                    "sTitle": "Number",
-                   "sWidth": "250"
-                },
-                {
-                    "sTitle": "Name",
-                   "sWidth": "250"
-                },
-                {
-                    "sTitle": "Codec",
-                    "sWidth": "100"
-                },
-                {
-                    "sTitle": "Status",
-                    "sWidth": confMan.params.hasVid ? "200px" : "150px"
-                },
-                {
-                    "sTitle": atitle,
-                    "sWidth": awidth,
-                }
-            ],
-            "bAutoWidth": true,
-            "bDestroy": true,
-            "bSort": false,
-            "bInfo": false,
-            "bFilter": false,
-            "bLengthChange": false,
-            "bPaginate": false,
-            "iDisplayLength": 1400,
-
-            "oLanguage": {
-                "sEmptyTable": "The Conference is Empty....."
-            },
-
-            "fnRowCallback": row_callback
-
-        });
-    };
-
-    $.verto.confMan.prototype.modCommand = function(cmd, id, value) {
-        var confMan = this;
-
-        confMan.verto.rpcClient.call("verto.broadcast", {
-            "eventChannel": confMan.params.laData.modChannel,
-            "data": {
-               "application": "conf-control",
-               "command": cmd,
-               "id": id,
-               "value": value
-            }
-       });
-    };
-
-    $.verto.confMan.prototype.sendChat = function(message, type) {
-        var confMan = this;
-        confMan.verto.rpcClient.call("verto.broadcast", {
-            "eventChannel": confMan.params.laData.chatChannel,
-            "data": {
-               "action": "send",
-               "message": message,
-               "type": type
-            }
-       });
-    };
-
-
-    $.verto.confMan.prototype.destroy = function() {
-        var confMan = this;
-
-        confMan.destroyed = true;
-
-        if (confMan.lt) {
-            confMan.lt.destroy();
-        }
-
-        if (confMan.params.laData.chatChannel) {
-            confMan.verto.unsubscribe(confMan.params.laData.chatChannel);
-        }
-
-        if (confMan.params.laData.modChannel) {
-            confMan.verto.unsubscribe(confMan.params.laData.modChannel);
-        }
-
-        if (confMan.params.mainModID) {
-            $(confMan.params.mainModID).html("");
-        }
-    };
-
-    $.verto.dialog = function(direction, verto, params) {
-        var dialog = this;
-
-        dialog.params = $.extend({
-            useVideo: verto.options.useVideo,
-            useStereo: verto.options.useStereo,
-           screenShare: false,
-           useCamera: false,
-           useMic: verto.options.deviceParams.useMic,
-           useSpeak: verto.options.deviceParams.useSpeak,
-            tag: verto.options.tag,
-            localTag: verto.options.localTag,
-            login: verto.options.login,
-      videoParams: verto.options.videoParams,
-      useStream: verto.options.useStream,
-        }, params);
-       
-
-       if (!dialog.params.screenShare) {
-           dialog.params.useCamera = verto.options.deviceParams.useCamera;
-       }
-
-        dialog.verto = verto;
-        dialog.direction = direction;
-        dialog.lastState = null;
-        dialog.state = dialog.lastState = $.verto.enum.state.new;
-        dialog.callbacks = verto.callbacks;
-        dialog.answered = false;
-        dialog.attach = params.attach || false;
-       dialog.screenShare = params.screenShare || false;
-       dialog.useCamera = dialog.params.useCamera;
-       dialog.useMic = dialog.params.useMic;
-       dialog.useSpeak = dialog.params.useSpeak;
-       
-        if (dialog.params.callID) {
-            dialog.callID = dialog.params.callID;
-        } else {
-            dialog.callID = dialog.params.callID = generateGUID();
-        }
-
-       if (typeof(dialog.params.tag) === "function") {
-               dialog.params.tag = dialog.params.tag();
-       }
-       
-        if (dialog.params.tag) {
-            dialog.audioStream = document.getElementById(dialog.params.tag);
-
-            if (dialog.params.useVideo) {
-                dialog.videoStream = dialog.audioStream;
-            }
-        } //else conjure one TBD
-
-        if (dialog.params.localTag) {
-           dialog.localVideo = document.getElementById(dialog.params.localTag);
-       }
-
-        dialog.verto.dialogs[dialog.callID] = dialog;
-
-        var RTCcallbacks = {};
-
-        if (dialog.direction == $.verto.enum.direction.inbound) {
-            if (dialog.params.display_direction === "outbound") {
-                dialog.params.remote_caller_id_name = dialog.params.caller_id_name;
-                dialog.params.remote_caller_id_number = dialog.params.caller_id_number;
-            } else {
-                dialog.params.remote_caller_id_name = dialog.params.callee_id_name;
-                dialog.params.remote_caller_id_number = dialog.params.callee_id_number;
-            }
-
-            if (!dialog.params.remote_caller_id_name) {
-                dialog.params.remote_caller_id_name = "Nobody";
-            }
-
-            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 { //answer
-                dialog.setState($.verto.enum.state.answering);
-
-                dialog.sendMethod(dialog.attach ? "verto.attach" : "verto.answer", {
-                    sdp: dialog.rtc.mediaData.SDP
-                });
-            }
-        };
-
-        RTCcallbacks.onICE = function(rtc) {
-            //console.log("cand", rtc.mediaData.candidate);
-            if (rtc.type == "offer") {
-                console.log("offer", rtc.mediaData.candidate);
-                return;
-            }
-        };
-
-        RTCcallbacks.onStream = function(rtc, stream) {
-            if (dialog.callbacks.permissionCallback &&
-                typeof dialog.callbacks.permissionCallback.onGranted === 'function') {
-                dialog.callbacks.permissionCallback.onGranted(stream);
-            }
-            else if (dialog.verto.options.permissionCallback &&
-                typeof dialog.verto.options.permissionCallback.onGranted === 'function'){
-                dialog.verto.options.permissionCallback.onGranted(stream);
-            }
-            console.log("stream started");
-        };
-
-        RTCcallbacks.onRemoteStream = function(rtc, stream) {
-          if (typeof dialog.callbacks.onRemoteStream === 'function') {
-            dialog.callbacks.onRemoteStream(stream, dialog);
-          }
-          console.log("remote stream started");
-        };
-
-        RTCcallbacks.onError = function(e) {
-            if (dialog.callbacks.permissionCallback &&
-                typeof dialog.callbacks.permissionCallback.onDenied === 'function') {
-                dialog.callbacks.permissionCallback.onDenied();
-            }
-            else 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,
-      turnServer: verto.options.turnServer,
-      useStream: dialog.params.useStream
-        });
-
-        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 ((obj.noDialogParams && i != "callID")) {
-               continue;
-           }
-
-           obj.dialogParams[i] = dialog.params[i];
-        }
-       
-       delete obj.noDialogParams;
-
-        dialog.verto.rpcClient.call(method, obj,
-
-        function(e) {
-            /* Success */
-            dialog.processReply(method, true, e);
-        },
-
-        function(e) {
-            /* Error */
-            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;
-    }
-
-
-    // Attach audio output device to video element using device/sink ID.                                                                                           
-    function find_name(id) {
-       for (var i in $.verto.audioOutDevices) {
-           var source = $.verto.audioOutDevices[i];
-           if (source.id === id) {
-               return(source.label);
-           }
-       }
-
-       return id;
-    }
-
-    $.verto.dialog.prototype.setAudioPlaybackDevice = function(sinkId, callback, arg) {
-       var dialog = this;
-       var element = dialog.audioStream;
-
-       if (typeof element.sinkId !== 'undefined') {
-           var devname = find_name(sinkId);
-           console.info("Dialog: " + dialog.callID + " Setting speaker:", element, devname);
-
-           element.setSinkId(sinkId)
-               .then(function() {
-                   console.log("Dialog: " + dialog.callID + ' Success, audio output device attached: ' + sinkId);
-                   if (callback) {
-                       callback(true, devname, arg);
-                   }
-               })
-               .catch(function(error) {
-                   var errorMessage = error;
-                   if (error.name === 'SecurityError') {
-                       errorMessage = "Dialog: " + dialog.callID + ' You need to use HTTPS for selecting audio output ' +
-                           'device: ' + error;
-                   }
-                   if (callback) {
-                       callback(false, null, arg);
-                   }
-                   console.error(errorMessage);
-               });
-       } else {
-           console.warn("Dialog: " + dialog.callID + ' Browser does not support output device selection.');
-           if (callback) {
-               callback(false, null, arg);
-           }
-       }
-    }
-
-    $.verto.dialog.prototype.setState = function(state) {
-        var dialog = this;
-
-        if (dialog.state == $.verto.enum.state.ringing) {
-            dialog.stopRinging();
-        }
-
-        if (dialog.state == state || !checkStateChange(dialog.state, state)) {
-            console.error("Dialog " + dialog.callID + ": INVALID state change from " + dialog.state.name + " to " + state.name);
-            dialog.hangup();
-            return false;
-        }
-
-        console.log("Dialog " + dialog.callID + ": state change from " + dialog.state.name + " to " + state.name);
-
-        dialog.lastState = dialog.state;
-        dialog.state = state;
-
-        if (dialog.callbacks.onDialogState) {
-            dialog.callbacks.onDialogState(this);
-        }
-
-        switch (dialog.state) {
-
-        case $.verto.enum.state.early:
-        case $.verto.enum.state.active:
-
-           var speaker = dialog.useSpeak;
-           console.info("Using Speaker: ", speaker);
-
-           if (speaker && speaker !== "any" && speaker !== "none") {
-               setTimeout(function() {
-                   dialog.setAudioPlaybackDevice(speaker);
-               }, 500);
-           }
-
-           break;
-
-        case $.verto.enum.state.trying:
-            setTimeout(function() {
-                if (dialog.state == $.verto.enum.state.trying) {
-                    dialog.setState($.verto.enum.state.hangup);
-                }
-            }, 30000);
-            break;
-        case $.verto.enum.state.purge:
-            dialog.setState($.verto.enum.state.destroy);
-            break;
-        case $.verto.enum.state.hangup:
-
-            if (dialog.lastState.val > $.verto.enum.state.requesting.val && dialog.lastState.val < $.verto.enum.state.hangup.val) {
-                dialog.sendMethod("verto.bye", {});
-            }
-
-            dialog.setState($.verto.enum.state.destroy);
-            break;
-        case $.verto.enum.state.destroy:
-
-            if (typeof(dialog.verto.options.tag) === "function") {
-              $('#' + dialog.params.tag).remove();
-            }
-
-            delete dialog.verto.dialogs[dialog.callID];
-           if (dialog.params.screenShare) {
-               dialog.rtc.stopPeer();
-           } else {
-               dialog.rtc.stop();
-           }
-            break;
-        }
-
-        return true;
-    };
-
-    $.verto.dialog.prototype.processReply = function(method, success, e) {
-        var dialog = this;
-
-        //console.log("Response: " + method + " State:" + dialog.state.name, success, e);
-
-        switch (method) {
-
-        case "verto.answer":
-        case "verto.attach":
-            if (success) {
-                dialog.setState($.verto.enum.state.active);
-            } else {
-                dialog.hangup();
-            }
-            break;
-        case "verto.invite":
-            if (success) {
-                dialog.setState($.verto.enum.state.trying);
-            } else {
-                dialog.setState($.verto.enum.state.destroy);
-            }
-            break;
-
-        case "verto.bye":
-            dialog.hangup();
-            break;
-
-        case "verto.modify":
-            if (e.holdState) {
-                if (e.holdState == "held") {
-                    if (dialog.state != $.verto.enum.state.held) {
-                        dialog.setState($.verto.enum.state.held);
-                    }
-                } else if (e.holdState == "active") {
-                    if (dialog.state != $.verto.enum.state.active) {
-                        dialog.setState($.verto.enum.state.active);
-                    }
-                }
-            }
-
-            if (success) {}
-
-            break;
-
-        default:
-            break;
-        }
-
-    };
-
-    $.verto.dialog.prototype.hangup = function(params) {
-        var dialog = this;
-
-        if (params) {
-            if (params.causeCode) {
-               dialog.causeCode = params.causeCode;
-            }
-
-            if (params.cause) {
-               dialog.cause = params.cause;
-            }
-        }
-
-       if (!dialog.cause && !dialog.causeCode) {
-           dialog.cause = "NORMAL_CLEARING";
-       }
-
-        if (dialog.state.val >= $.verto.enum.state.new.val && dialog.state.val < $.verto.enum.state.hangup.val) {
-            dialog.setState($.verto.enum.state.hangup);
-        } else if (dialog.state.val < $.verto.enum.state.destroy) {
-            dialog.setState($.verto.enum.state.destroy);
-        }
-    };
-
-    $.verto.dialog.prototype.stopRinging = function() {
-        var dialog = this;
-        if (dialog.verto.ringer) {
-            dialog.verto.ringer.stop();
-        }
-    };
-
-    $.verto.dialog.prototype.indicateRing = function() {
-        var dialog = this;
-
-        if (dialog.verto.ringer) {
-            dialog.verto.ringer.attr("src", dialog.verto.options.ringFile)[0].play();
-
-            setTimeout(function() {
-                dialog.stopRinging();
-                if (dialog.state == $.verto.enum.state.ringing) {
-                    dialog.indicateRing();
-                }
-            },
-            dialog.verto.options.ringSleep);
-        }
-    };
-
-    $.verto.dialog.prototype.ring = function() {
-        var dialog = this;
-
-        dialog.setState($.verto.enum.state.ringing);
-        dialog.indicateRing();
-    };
-
-    $.verto.dialog.prototype.useVideo = function(on) {
-        var dialog = this;
-
-        dialog.params.useVideo = on;
-
-        if (on) {
-            dialog.videoStream = dialog.audioStream;
-        } else {
-            dialog.videoStream = null;
-        }
-
-        dialog.rtc.useVideo(dialog.videoStream, dialog.localVideo);
-
-    };
-
-    $.verto.dialog.prototype.setMute = function(what) {
-       var dialog = this;
-       return dialog.rtc.setMute(what);
-    };
-
-    $.verto.dialog.prototype.getMute = function() {
-       var dialog = this; 
-       return dialog.rtc.getMute();
-    };
-
-    $.verto.dialog.prototype.setVideoMute = function(what) {
-       var dialog = this;
-       return dialog.rtc.setVideoMute(what);
-    };
-
-    $.verto.dialog.prototype.getVideoMute = function() {
-       var dialog = this; 
-       return dialog.rtc.getVideoMute();
-    };
-
-    $.verto.dialog.prototype.useStereo = function(on) {
-        var dialog = this;
-
-        dialog.params.useStereo = on;
-        dialog.rtc.useStereo(on);
-    };
-
-    $.verto.dialog.prototype.dtmf = function(digits) {
-        var dialog = this;
-        if (digits) {
-            dialog.sendMethod("verto.info", {
-                dtmf: digits
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.rtt = function(obj) {
-        var dialog = this;
-       var pobj = {};
-
-       if (!obj) {
-           return false;
-       }
-
-       pobj.code = obj.code;
-       pobj.chars = obj.chars;
-
-
-        if (pobj.chars || pobj.code) {
-            dialog.sendMethod("verto.info", {
-                txt: obj,
-               noDialogParams: true
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.transfer = function(dest, params) {
-        var dialog = this;
-        if (dest) {
-            dialog.sendMethod("verto.modify", {
-                action: "transfer",
-                destination: dest,
-                params: params
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.replace = function(replaceCallID, params) {
-        var dialog = this;
-        if (replaceCallID) {
-            dialog.sendMethod("verto.modify", {
-                action: "replace",
-                replaceCallID: replaceCallID,
-                params: params
-            });
-        }
-    };
-
-    $.verto.dialog.prototype.hold = function(params) {
-        var dialog = this;
-
-        dialog.sendMethod("verto.modify", {
-            action: "hold",
-            params: params
-        });
-    };
-
-    $.verto.dialog.prototype.unhold = function(params) {
-        var dialog = this;
-
-        dialog.sendMethod("verto.modify", {
-            action: "unhold",
-            params: params
-        });
-    };
-
-    $.verto.dialog.prototype.toggleHold = function(params) {
-        var dialog = this;
-
-        dialog.sendMethod("verto.modify", {
-            action: "toggleHold",
-            params: params
-        });
-    };
-
-    $.verto.dialog.prototype.message = function(msg) {
-        var dialog = this;
-        var err = 0;
-
-        msg.from = dialog.params.login;
-
-        if (!msg.to) {
-            console.error("Missing To");
-            err++;
-        }
-
-        if (!msg.body) {
-            console.error("Missing Body");
-            err++;
-        }
-
-        if (err) {
-            return false;
-        }
-
-        dialog.sendMethod("verto.info", {
-            msg: msg
-        });
-
-        return true;
-    };
-
-    $.verto.dialog.prototype.answer = function(params) {
-        var dialog = this;
-       
-        if (!dialog.answered) {
-           if (!params) {
-               params = {};
-           }
-
-           params.sdp = dialog.params.sdp;
-
-            if (params) {
-                if (params.useVideo) {
-                    dialog.useVideo(true);
-                }
-               dialog.params.callee_id_name = params.callee_id_name;
-               dialog.params.callee_id_number = params.callee_id_number;
-
-               if (params.useCamera) {
-                   dialog.useCamera = params.useCamera;
-               }
-
-               if (params.useMic) {
-                   dialog.useMic = params.useMic;
-               }
-
-               if (params.useSpeak) {
-                   dialog.useSpeak = params.useSpeak;
-               }
-            }
-           
-            dialog.rtc.createAnswer(params);
-            dialog.answered = true;
-        }
-    };
-
-    $.verto.dialog.prototype.handleAnswer = function(params) {
-        var dialog = this;
-
-        dialog.gotAnswer = true;
-
-        if (dialog.state.val >= $.verto.enum.state.active.val) {
-            return;
-        }
-
-        if (dialog.state.val >= $.verto.enum.state.early.val) {
-            dialog.setState($.verto.enum.state.active);
-        } else {
-            if (dialog.gotEarly) {
-                console.log("Dialog " + dialog.callID + " Got answer while still establishing early media, delaying...");
-            } else {
-                console.log("Dialog " + dialog.callID + " Answering Channel");
-                dialog.rtc.answer(params.sdp, function() {
-                    dialog.setState($.verto.enum.state.active);
-                }, function(e) {
-                    console.error(e);
-                    dialog.hangup();
-                });
-                console.log("Dialog " + dialog.callID + "ANSWER SDP", params.sdp);
-            }
-        }
-
-
-    };
-
-    $.verto.dialog.prototype.cidString = function(enc) {
-        var dialog = this;
-        var party = dialog.params.remote_caller_id_name + (enc ? " &lt;" : " <") + dialog.params.remote_caller_id_number + (enc ? "&gt;" : ">");
-        return party;
-    };
-
-    $.verto.dialog.prototype.sendMessage = function(msg, params) {
-        var dialog = this;
-
-        if (dialog.callbacks.onMessage) {
-            dialog.callbacks.onMessage(dialog.verto, dialog, msg, params);
-        }
-    };
-
-    $.verto.dialog.prototype.handleInfo = function(params) {
-        var dialog = this;
-
-        dialog.sendMessage($.verto.enum.message.info, params);
-
-    };
-
-    $.verto.dialog.prototype.handleDisplay = function(params) {
-        var dialog = this;
-
-        if (params.display_name) {
-            dialog.params.remote_caller_id_name = params.display_name;
-        }
-        if (params.display_number) {
-            dialog.params.remote_caller_id_number = params.display_number;
-        }
-
-        dialog.sendMessage($.verto.enum.message.display, {});
-    };
-
-    $.verto.dialog.prototype.handleMedia = function(params) {
-        var dialog = this;
-
-        if (dialog.state.val >= $.verto.enum.state.early.val) {
-            return;
-        }
-
-        dialog.gotEarly = true;
-
-        dialog.rtc.answer(params.sdp, function() {
-            console.log("Dialog " + dialog.callID + "Establishing early media");
-            dialog.setState($.verto.enum.state.early);
-
-            if (dialog.gotAnswer) {
-                console.log("Dialog " + dialog.callID + "Answering Channel");
-                dialog.setState($.verto.enum.state.active);
-            }
-        }, function(e) {
-            console.error(e);
-            dialog.hangup();
-        });
-        console.log("Dialog " + dialog.callID + "EARLY SDP", params.sdp);
-    };
-
-    $.verto.ENUM = function(s) {
-        var i = 0,
-        o = {};
-        s.split(" ").map(function(x) {
-            o[x] = {
-                name: x,
-                val: i++
-            };
-        });
-        return Object.freeze(o);
-    };
-
-    $.verto.enum = {};
-
-    $.verto.enum.states = Object.freeze({
-        new: {
-            requesting: 1,
-            recovering: 1,
-            ringing: 1,
-            destroy: 1,
-            answering: 1,
-           hangup: 1
-        },
-        requesting: {
-            trying: 1,
-            hangup: 1,
-           active: 1
-        },
-        recovering: {
-            answering: 1,
-            hangup: 1
-        },
-        trying: {
-            active: 1,
-            early: 1,
-            hangup: 1
-        },
-        ringing: {
-            answering: 1,
-            hangup: 1
-        },
-        answering: {
-            active: 1,
-            hangup: 1
-        },
-        active: {
-            answering: 1,
-            requesting: 1,
-            hangup: 1,
-            held: 1
-        },
-        held: {
-            hangup: 1,
-            active: 1
-        },
-        early: {
-            hangup: 1,
-            active: 1
-        },
-        hangup: {
-            destroy: 1
-        },
-        destroy: {},
-        purge: {
-            destroy: 1
-        }
-    });
-
-    $.verto.enum.state = $.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge");
-    $.verto.enum.direction = $.verto.ENUM("inbound outbound");
-    $.verto.enum.message = $.verto.ENUM("display info pvtEvent clientReady");
-
-    $.verto.enum = Object.freeze($.verto.enum);
-
-    $.verto.saved = [];
-    
-    $.verto.unloadJobs = [];
-
-    var unloadEventName = 'beforeunload';
-    // Hacks for Mobile Safari
-    var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
-    if (iOS) {
-      unloadEventName = 'pagehide';
-    }
-
-    $(window).bind(unloadEventName, function() {
-        for (var f in $.verto.unloadJobs) {
-          $.verto.unloadJobs[f]();
-        }
-
-        if ($.verto.haltClosure)
-          return $.verto.haltClosure();
-
-        for (var i in $.verto.saved) {
-            var verto = $.verto.saved[i];
-            if (verto) {
-                verto.purge();
-                verto.logout();
-            }
-        }
-
-        return $.verto.warnOnUnload;
-    });
-
-    $.verto.videoDevices = [];
-    $.verto.audioInDevices = [];
-    $.verto.audioOutDevices = [];
-
-    var checkDevices = function(runtime) {
-       console.info("enumerating devices");
-       var aud_in = [], aud_out = [], vid = [];        
-       var has_video = 0, has_audio = 0;
-       var Xstream;
-
-       function gotDevices(deviceInfos) {
-             // Handles being called several times to update labels. Preserve values.
-           for (var i = 0; i !== deviceInfos.length; ++i) {
-               var deviceInfo = deviceInfos[i];
-               var text = "";
-
-               console.log(deviceInfo);
-               console.log(deviceInfo.kind + ": " + deviceInfo.label + " id = " + deviceInfo.deviceId);
-
-               if (deviceInfo.kind === 'audioinput') {
-                   text = deviceInfo.label || 'microphone ' + (aud_in.length + 1);
-                   aud_in.push({id: deviceInfo.deviceId, kind: "audio_in", label: text});
-               } else if (deviceInfo.kind === 'audiooutput') {
-                   text = deviceInfo.label || 'speaker ' + (aud_out.length + 1);
-                   aud_out.push({id: deviceInfo.deviceId, kind: "audio_out", label: text});
-               } else if (deviceInfo.kind === 'videoinput') {
-                   text = deviceInfo.label || 'camera ' + (vid.length + 1);
-                   vid.push({id: deviceInfo.deviceId, kind: "video", label: text});
-               } else {
-                   console.log('Some other kind of source/device: ', deviceInfo);
-               }
-           }
-
-
-           $.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);
-
-           if (Xstream) {
-               Xstream.getTracks().forEach(function(track) {track.stop();});
-           }
-
-           if (runtime) {
-               runtime(true);
-           }
-       }
-
-
-       
-
-       function handleError(error) {
-           console.log('device enumeration error: ', error);
-           if (runtime) runtime(false);
-       }
-
-
-       function checkTypes(devs) {
-           for (var i = 0; i !== devs.length; ++i) {
-
-               if (devs[i].kind === 'audioinput') {
-                   has_audio++;
-               } else if (devs[i].kind === 'videoinput') {
-                   has_video++;
-               }
-           }
-
-           navigator.getUserMedia({ audio: (has_audio > 0 ? true : false), video: (has_video > 0 ? true : false)},
-                                  function(stream) {
-                                      Xstream = stream;
-                                      navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
-                                  },
-                                  function(err) {
-                                      console.log("The following error occurred: " + err.name);
-                                  }
-                                 );
-       }
-
-       navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);
-
-  };
-
-    $.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);
-(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){
-/*
- *  Copyright (c) 2017 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');
-
-function writeMediaSection(transceiver, caps, type, stream, dtlsRole) {
-  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' : dtlsRole || '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) {
-    // spec.
-    var msid = 'msid:' + stream.id + ' ' +
-        transceiver.rtpSender.track.id + '\r\n';
-    sdp += 'a=' + msid;
-
-    // for Chrome.
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-        ' ' + msid;
-    if (transceiver.sendEncodingParameters[0].rtx) {
-      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-          ' ' + msid;
-      sdp += 'a=ssrc-group:FID ' +
-          transceiver.sendEncodingParameters[0].ssrc + ' ' +
-          transceiver.sendEncodingParameters[0].rtx.ssrc +
-          '\r\n';
-    }
-  }
-  // FIXME: this should be written by writeRtpDescription.
-  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-      ' cname:' + SDPUtils.localCName + '\r\n';
-  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-        ' cname:' + SDPUtils.localCName + '\r\n';
-  }
-  return sdp;
-}
-
-// Edge does not like
-// 1) stun: filtered after 14393 unless ?transport=udp is present
-// 2) turn: that does not have all of turn:host:port?transport=udp
-// 3) turn: with ipv6 addresses
-// 4) turn: occurring muliple times
-function filterIceServers(iceServers, edgeVersion) {
-  var hasTurn = false;
-  iceServers = JSON.parse(JSON.stringify(iceServers));
-  return iceServers.filter(function(server) {
-    if (server && (server.urls || server.url)) {
-      var urls = server.urls || server.url;
-      if (server.url && !server.urls) {
-        console.warn('RTCIceServer.url is deprecated! Use urls instead.');
-      }
-      var isString = typeof urls === 'string';
-      if (isString) {
-        urls = [urls];
-      }
-      urls = urls.filter(function(url) {
-        var validTurn = url.indexOf('turn:') === 0 &&
-            url.indexOf('transport=udp') !== -1 &&
-            url.indexOf('turn:[') === -1 &&
-            !hasTurn;
-
-        if (validTurn) {
-          hasTurn = true;
-          return true;
-        }
-        return url.indexOf('stun:') === 0 && edgeVersion >= 14393 &&
-            url.indexOf('?transport=udp') === -1;
-      });
-
-      delete server.url;
-      server.urls = isString ? urls[0] : urls;
-      return !!urls.length;
-    }
-  });
-}
-
-// Determines the intersection of local and remote capabilities.
-function getCommonCapabilities(localCapabilities, remoteCapabilities) {
-  var commonCapabilities = {
-    codecs: [],
-    headerExtensions: [],
-    fecMechanisms: []
-  };
-
-  var findCodecByPayloadType = function(pt, codecs) {
-    pt = parseInt(pt, 10);
-    for (var i = 0; i < codecs.length; i++) {
-      if (codecs[i].payloadType === pt ||
-          codecs[i].preferredPayloadType === pt) {
-        return codecs[i];
-      }
-    }
-  };
-
-  var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
-    var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
-    var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
-    return lCodec && rCodec &&
-        lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
-  };
-
-  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) {
-        if (lCodec.name.toLowerCase() === 'rtx' &&
-            lCodec.parameters && rCodec.parameters.apt) {
-          // for RTX we need to find the local rtx that has a apt
-          // which points to the same local codec as the remote one.
-          if (!rtxCapabilityMatches(lCodec, rCodec,
-              localCapabilities.codecs, remoteCapabilities.codecs)) {
-            continue;
-          }
-        }
-        rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
-        // number of channels is the highest common number of channels
-        rCodec.numChannels = Math.min(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;
-}
-
-// is action=setLocalDescription with type allowed in signalingState
-function isActionAllowedInSignalingState(action, type, signalingState) {
-  return {
-    offer: {
-      setLocalDescription: ['stable', 'have-local-offer'],
-      setRemoteDescription: ['stable', 'have-remote-offer']
-    },
-    answer: {
-      setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],
-      setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']
-    }
-  }[type][action].indexOf(signalingState) !== -1;
-}
-
-function maybeAddCandidate(iceTransport, candidate) {
-  // Edge's internal representation adds some fields therefore
-  // not all fieldÑ• are taken into account.
-  var alreadyAdded = iceTransport.getRemoteCandidates()
-      .find(function(remoteCandidate) {
-        return candidate.foundation === remoteCandidate.foundation &&
-            candidate.ip === remoteCandidate.ip &&
-            candidate.port === remoteCandidate.port &&
-            candidate.priority === remoteCandidate.priority &&
-            candidate.protocol === remoteCandidate.protocol &&
-            candidate.type === remoteCandidate.type;
-      });
-  if (!alreadyAdded) {
-    iceTransport.addRemoteCandidate(candidate);
-  }
-  return !alreadyAdded;
-}
-
-
-// https://w3c.github.io/mediacapture-main/#mediastream
-// Helper function to add the track to the stream and
-// dispatch the event ourselves.
-function addTrackToStreamAndFireEvent(track, stream) {
-  stream.addTrack(track);
-  var e = new Event('addtrack'); // TODO: MediaStreamTrackEvent
-  e.track = track;
-  stream.dispatchEvent(e);
-}
-
-function removeTrackFromStreamAndFireEvent(track, stream) {
-  stream.removeTrack(track);
-  var e = new Event('removetrack'); // TODO: MediaStreamTrackEvent
-  e.track = track;
-  stream.dispatchEvent(e);
-}
-
-function fireAddTrack(pc, track, receiver, streams) {
-  var trackEvent = new Event('track');
-  trackEvent.track = track;
-  trackEvent.receiver = receiver;
-  trackEvent.transceiver = {receiver: receiver};
-  trackEvent.streams = streams;
-  window.setTimeout(function() {
-    pc._dispatchEvent('track', trackEvent);
-  });
-}
-
-function makeError(name, description) {
-  var e = new Error(description);
-  e.name = name;
-  return e;
-}
-
-module.exports = function(window, edgeVersion) {
-  var RTCPeerConnection = function(config) {
-    var pc = this;
-
-    var _eventTarget = document.createDocumentFragment();
-    ['addEventListener', 'removeEventListener', 'dispatchEvent']
-        .forEach(function(method) {
-          pc[method] = _eventTarget[method].bind(_eventTarget);
-        });
-
-    this.canTrickleIceCandidates = null;
-
-    this.needNegotiation = false;
-
-    this.localStreams = [];
-    this.remoteStreams = [];
-
-    this.localDescription = null;
-    this.remoteDescription = null;
-
-    this.signalingState = 'stable';
-    this.iceConnectionState = 'new';
-    this.iceGatheringState = 'new';
-
-    config = JSON.parse(JSON.stringify(config || {}));
-
-    this.usingBundle = config.bundlePolicy === 'max-bundle';
-    if (config.rtcpMuxPolicy === 'negotiate') {
-      throw(makeError('NotSupportedError',
-          'rtcpMuxPolicy \'negotiate\' is not supported'));
-    } else if (!config.rtcpMuxPolicy) {
-      config.rtcpMuxPolicy = 'require';
-    }
-
-    switch (config.iceTransportPolicy) {
-      case 'all':
-      case 'relay':
-        break;
-      default:
-        config.iceTransportPolicy = 'all';
-        break;
-    }
-
-    switch (config.bundlePolicy) {
-      case 'balanced':
-      case 'max-compat':
-      case 'max-bundle':
-        break;
-      default:
-        config.bundlePolicy = 'balanced';
-        break;
-    }
-
-    config.iceServers = filterIceServers(config.iceServers || [], edgeVersion);
-
-    this._iceGatherers = [];
-    if (config.iceCandidatePoolSize) {
-      for (var i = config.iceCandidatePoolSize; i > 0; i--) {
-        this._iceGatherers = new window.RTCIceGatherer({
-          iceServers: config.iceServers,
-          gatherPolicy: config.iceTransportPolicy
-        });
-      }
-    } else {
-      config.iceCandidatePoolSize = 0;
-    }
-
-    this._config = config;
-
-    // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
-    // everything that is needed to describe a SDP m-line.
-    this.transceivers = [];
-
-    this._sdpSessionId = SDPUtils.generateSessionId();
-    this._sdpSessionVersion = 0;
-
-    this._dtlsRole = undefined; // role for a=setup to use in answers.
-
-    this._isClosed = false;
-  };
-
-  // set up event handlers on prototype
-  RTCPeerConnection.prototype.onicecandidate = null;
-  RTCPeerConnection.prototype.onaddstream = null;
-  RTCPeerConnection.prototype.ontrack = null;
-  RTCPeerConnection.prototype.onremovestream = null;
-  RTCPeerConnection.prototype.onsignalingstatechange = null;
-  RTCPeerConnection.prototype.oniceconnectionstatechange = null;
-  RTCPeerConnection.prototype.onicegatheringstatechange = null;
-  RTCPeerConnection.prototype.onnegotiationneeded = null;
-  RTCPeerConnection.prototype.ondatachannel = null;
-
-  RTCPeerConnection.prototype._dispatchEvent = function(name, event) {
-    if (this._isClosed) {
-      return;
-    }
-    this.dispatchEvent(event);
-    if (typeof this['on' + name] === 'function') {
-      this['on' + name](event);
-    }
-  };
-
-  RTCPeerConnection.prototype._emitGatheringStateChange = function() {
-    var event = new Event('icegatheringstatechange');
-    this._dispatchEvent('icegatheringstatechange', event);
-  };
-
-  RTCPeerConnection.prototype.getConfiguration = function() {
-    return this._config;
-  };
-
-  RTCPeerConnection.prototype.getLocalStreams = function() {
-    return this.localStreams;
-  };
-
-  RTCPeerConnection.prototype.getRemoteStreams = function() {
-    return this.remoteStreams;
-  };
-
-  // internal helper to create a transceiver object.
-  // (whih is not yet the same as the WebRTC 1.0 transceiver)
-  RTCPeerConnection.prototype._createTransceiver = function(kind) {
-    var hasBundleTransport = this.transceivers.length > 0;
-    var transceiver = {
-      track: null,
-      iceGatherer: null,
-      iceTransport: null,
-      dtlsTransport: null,
-      localCapabilities: null,
-      remoteCapabilities: null,
-      rtpSender: null,
-      rtpReceiver: null,
-      kind: kind,
-      mid: null,
-      sendEncodingParameters: null,
-      recvEncodingParameters: null,
-      stream: null,
-      associatedRemoteMediaStreams: [],
-      wantReceive: true
-    };
-    if (this.usingBundle && hasBundleTransport) {
-      transceiver.iceTransport = this.transceivers[0].iceTransport;
-      transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
-    } else {
-      var transports = this._createIceAndDtlsTransports();
-      transceiver.iceTransport = transports.iceTransport;
-      transceiver.dtlsTransport = transports.dtlsTransport;
-    }
-    this.transceivers.push(transceiver);
-    return transceiver;
-  };
-
-  RTCPeerConnection.prototype.addTrack = function(track, stream) {
-    var alreadyExists = this.transceivers.find(function(s) {
-      return s.track === track;
-    });
-
-    if (alreadyExists) {
-      throw makeError('InvalidAccessError', 'Track already exists.');
-    }
-
-    if (this.signalingState === 'closed') {
-      throw makeError('InvalidStateError',
-          'Attempted to call addTrack on a closed peerconnection.');
-    }
-
-    var transceiver;
-    for (var i = 0; i < this.transceivers.length; i++) {
-      if (!this.transceivers[i].track &&
-          this.transceivers[i].kind === track.kind) {
-        transceiver = this.transceivers[i];
-      }
-    }
-    if (!transceiver) {
-      transceiver = this._createTransceiver(track.kind);
-    }
-
-    this._maybeFireNegotiationNeeded();
-
-    if (this.localStreams.indexOf(stream) === -1) {
-      this.localStreams.push(stream);
-    }
-
-    transceiver.track = track;
-    transceiver.stream = stream;
-    transceiver.rtpSender = new window.RTCRtpSender(track,
-        transceiver.dtlsTransport);
-    return transceiver.rtpSender;
-  };
-
-  RTCPeerConnection.prototype.addStream = function(stream) {
-    var pc = this;
-    if (edgeVersion >= 15025) {
-      stream.getTracks().forEach(function(track) {
-        pc.addTrack(track, stream);
-      });
-    } else {
-      // Clone is necessary for local demos mostly, attaching directly
-      // to two different senders does not work (build 10547).
-      // Fixed in 15025 (or earlier)
-      var clonedStream = stream.clone();
-      stream.getTracks().forEach(function(track, idx) {
-        var clonedTrack = clonedStream.getTracks()[idx];
-        track.addEventListener('enabled', function(event) {
-          clonedTrack.enabled = event.enabled;
-        });
-      });
-      clonedStream.getTracks().forEach(function(track) {
-        pc.addTrack(track, clonedStream);
-      });
-    }
-  };
-
-  RTCPeerConnection.prototype.removeTrack = function(sender) {
-    if (!(sender instanceof window.RTCRtpSender)) {
-      throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' +
-          'does not implement interface RTCRtpSender.');
-    }
-
-    var transceiver = this.transceivers.find(function(t) {
-      return t.rtpSender === sender;
-    });
-
-    if (!transceiver) {
-      throw makeError('InvalidAccessError',
-          'Sender was not created by this connection.');
-    }
-    var stream = transceiver.stream;
-
-    transceiver.rtpSender.stop();
-    transceiver.rtpSender = null;
-    transceiver.track = null;
-    transceiver.stream = null;
-
-    // remove the stream from the set of local streams
-    var localStreams = this.transceivers.map(function(t) {
-      return t.stream;
-    });
-    if (localStreams.indexOf(stream) === -1 &&
-        this.localStreams.indexOf(stream) > -1) {
-      this.localStreams.splice(this.localStreams.indexOf(stream), 1);
-    }
-
-    this._maybeFireNegotiationNeeded();
-  };
-
-  RTCPeerConnection.prototype.removeStream = function(stream) {
-    var pc = this;
-    stream.getTracks().forEach(function(track) {
-      var sender = pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-      if (sender) {
-        pc.removeTrack(sender);
-      }
-    });
-  };
-
-  RTCPeerConnection.prototype.getSenders = function() {
-    return this.transceivers.filter(function(transceiver) {
-      return !!transceiver.rtpSender;
-    })
-    .map(function(transceiver) {
-      return transceiver.rtpSender;
-    });
-  };
-
-  RTCPeerConnection.prototype.getReceivers = function() {
-    return this.transceivers.filter(function(transceiver) {
-      return !!transceiver.rtpReceiver;
-    })
-    .map(function(transceiver) {
-      return transceiver.rtpReceiver;
-    });
-  };
-
-
-  RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex,
-      usingBundle) {
-    var pc = this;
-    if (usingBundle && sdpMLineIndex > 0) {
-      return this.transceivers[0].iceGatherer;
-    } else if (this._iceGatherers.length) {
-      return this._iceGatherers.shift();
-    }
-    var iceGatherer = new window.RTCIceGatherer({
-      iceServers: this._config.iceServers,
-      gatherPolicy: this._config.iceTransportPolicy
-    });
-    Object.defineProperty(iceGatherer, 'state',
-        {value: 'new', writable: true}
-    );
-
-    this.transceivers[sdpMLineIndex].candidates = [];
-    this.transceivers[sdpMLineIndex].bufferCandidates = function(event) {
-      var end = !event.candidate || Object.keys(event.candidate).length === 0;
-      // polyfill since RTCIceGatherer.state is not implemented in
-      // Edge 10547 yet.
-      iceGatherer.state = end ? 'completed' : 'gathering';
-      if (pc.transceivers[sdpMLineIndex].candidates !== null) {
-        pc.transceivers[sdpMLineIndex].candidates.push(event.candidate);
-      }
-    };
-    iceGatherer.addEventListener('localcandidate',
-      this.transceivers[sdpMLineIndex].bufferCandidates);
-    return iceGatherer;
-  };
-
-  // start gathering from an RTCIceGatherer.
-  RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) {
-    var pc = this;
-    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
-    if (iceGatherer.onlocalcandidate) {
-      return;
-    }
-    var candidates = this.transceivers[sdpMLineIndex].candidates;
-    this.transceivers[sdpMLineIndex].candidates = null;
-    iceGatherer.removeEventListener('localcandidate',
-      this.transceivers[sdpMLineIndex].bufferCandidates);
-    iceGatherer.onlocalcandidate = function(evt) {
-      if (pc.usingBundle && sdpMLineIndex > 0) {
-        // if we know that we use bundle we can drop candidates with
-        // Ñ•dpMLineIndex > 0. If we don't do this then our state gets
-        // confused since we dispose the extra ice gatherer.
-        return;
-      }
-      var event = new Event('icecandidate');
-      event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
-
-      var cand = evt.candidate;
-      // Edge emits an empty object for RTCIceCandidateComplete‥
-      var end = !cand || Object.keys(cand).length === 0;
-      if (end) {
-        // polyfill since RTCIceGatherer.state is not implemented in
-        // Edge 10547 yet.
-        if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') {
-          iceGatherer.state = 'completed';
-        }
-      } else {
-        if (iceGatherer.state === 'new') {
-          iceGatherer.state = 'gathering';
-        }
-        // RTCIceCandidate doesn't have a component, needs to be added
-        cand.component = 1;
-        event.candidate.candidate = SDPUtils.writeCandidate(cand);
-      }
-
-      // update local description.
-      var sections = SDPUtils.splitSections(pc.localDescription.sdp);
-      if (!end) {
-        sections[event.candidate.sdpMLineIndex + 1] +=
-            'a=' + event.candidate.candidate + '\r\n';
-      } else {
-        sections[event.candidate.sdpMLineIndex + 1] +=
-            'a=end-of-candidates\r\n';
-      }
-      pc.localDescription.sdp = sections.join('');
-      var complete = pc.transceivers.every(function(transceiver) {
-        return transceiver.iceGatherer &&
-            transceiver.iceGatherer.state === 'completed';
-      });
-
-      if (pc.iceGatheringState !== 'gathering') {
-        pc.iceGatheringState = 'gathering';
-        pc._emitGatheringStateChange();
-      }
-
-      // Emit candidate. Also emit null candidate when all gatherers are
-      // complete.
-      if (!end) {
-        pc._dispatchEvent('icecandidate', event);
-      }
-      if (complete) {
-        pc._dispatchEvent('icecandidate', new Event('icecandidate'));
-        pc.iceGatheringState = 'complete';
-        pc._emitGatheringStateChange();
-      }
-    };
-
-    // emit already gathered candidates.
-    window.setTimeout(function() {
-      candidates.forEach(function(candidate) {
-        var e = new Event('RTCIceGatherEvent');
-        e.candidate = candidate;
-        iceGatherer.onlocalcandidate(e);
-      });
-    }, 0);
-  };
-
-  // Create ICE transport and DTLS transport.
-  RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
-    var pc = this;
-    var iceTransport = new window.RTCIceTransport(null);
-    iceTransport.onicestatechange = function() {
-      pc._updateConnectionState();
-    };
-
-    var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
-    dtlsTransport.ondtlsstatechange = function() {
-      pc._updateConnectionState();
-    };
-    dtlsTransport.onerror = function() {
-      // onerror does not set state to failed by itpc.
-      Object.defineProperty(dtlsTransport, 'state',
-          {value: 'failed', writable: true});
-      pc._updateConnectionState();
-    };
-
-    return {
-      iceTransport: iceTransport,
-      dtlsTransport: dtlsTransport
-    };
-  };
-
-  // Destroy ICE gatherer, ICE transport and DTLS transport.
-  // Without triggering the callbacks.
-  RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
-      sdpMLineIndex) {
-    var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
-    if (iceGatherer) {
-      delete iceGatherer.onlocalcandidate;
-      delete this.transceivers[sdpMLineIndex].iceGatherer;
-    }
-    var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
-    if (iceTransport) {
-      delete iceTransport.onicestatechange;
-      delete this.transceivers[sdpMLineIndex].iceTransport;
-    }
-    var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
-    if (dtlsTransport) {
-      delete dtlsTransport.ondtlsstatechange;
-      delete dtlsTransport.onerror;
-      delete this.transceivers[sdpMLineIndex].dtlsTransport;
-    }
-  };
-
-  // Start the RTP Sender and Receiver for a transceiver.
-  RTCPeerConnection.prototype._transceive = function(transceiver,
-      send, recv) {
-    var params = getCommonCapabilities(transceiver.localCapabilities,
-        transceiver.remoteCapabilities);
-    if (send && transceiver.rtpSender) {
-      params.encodings = transceiver.sendEncodingParameters;
-      params.rtcp = {
-        cname: SDPUtils.localCName,
-        compound: transceiver.rtcpParameters.compound
-      };
-      if (transceiver.recvEncodingParameters.length) {
-        params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
-      }
-      transceiver.rtpSender.send(params);
-    }
-    if (recv && transceiver.rtpReceiver && params.codecs.length > 0) {
-      // remove RTX field in Edge 14942
-      if (transceiver.kind === 'video'
-          && transceiver.recvEncodingParameters
-          && edgeVersion < 15019) {
-        transceiver.recvEncodingParameters.forEach(function(p) {
-          delete p.rtx;
-        });
-      }
-      if (transceiver.recvEncodingParameters.length) {
-        params.encodings = transceiver.recvEncodingParameters;
-      }
-      params.rtcp = {
-        compound: transceiver.rtcpParameters.compound
-      };
-      if (transceiver.rtcpParameters.cname) {
-        params.rtcp.cname = transceiver.rtcpParameters.cname;
-      }
-      if (transceiver.sendEncodingParameters.length) {
-        params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
-      }
-      transceiver.rtpReceiver.receive(params);
-    }
-  };
-
-  RTCPeerConnection.prototype.setLocalDescription = function(description) {
-    var pc = this;
-
-    if (!isActionAllowedInSignalingState('setLocalDescription',
-        description.type, this.signalingState) || this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not set local ' + description.type +
-          ' in state ' + pc.signalingState));
-    }
-
-    var sections;
-    var sessionpart;
-    if (description.type === 'offer') {
-      // 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);
-        pc.transceivers[sdpMLineIndex].localCapabilities = caps;
-      });
-
-      this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-        pc._gather(transceiver.mid, sdpMLineIndex);
-      });
-    } else if (description.type === 'answer') {
-      sections = SDPUtils.splitSections(pc.remoteDescription.sdp);
-      sessionpart = sections.shift();
-      var isIceLite = SDPUtils.matchPrefix(sessionpart,
-          'a=ice-lite').length > 0;
-      sections.forEach(function(mediaSection, sdpMLineIndex) {
-        var transceiver = pc.transceivers[sdpMLineIndex];
-        var iceGatherer = transceiver.iceGatherer;
-        var iceTransport = transceiver.iceTransport;
-        var dtlsTransport = transceiver.dtlsTransport;
-        var localCapabilities = transceiver.localCapabilities;
-        var remoteCapabilities = transceiver.remoteCapabilities;
-
-        // treat bundle-only as not-rejected.
-        var rejected = SDPUtils.isRejected(mediaSection) &&
-            SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
-
-        if (!rejected && !transceiver.isDatachannel) {
-          var remoteIceParameters = SDPUtils.getIceParameters(
-              mediaSection, sessionpart);
-          var remoteDtlsParameters = SDPUtils.getDtlsParameters(
-              mediaSection, sessionpart);
-          if (isIceLite) {
-            remoteDtlsParameters.role = 'server';
-          }
-
-          if (!pc.usingBundle || sdpMLineIndex === 0) {
-            pc._gather(transceiver.mid, sdpMLineIndex);
-            if (iceTransport.state === 'new') {
-              iceTransport.start(iceGatherer, remoteIceParameters,
-                  isIceLite ? 'controlling' : 'controlled');
-            }
-            if (dtlsTransport.state === 'new') {
-              dtlsTransport.start(remoteDtlsParameters);
-            }
-          }
-
-          // Calculate intersection of capabilities.
-          var params = getCommonCapabilities(localCapabilities,
-              remoteCapabilities);
-
-          // Start the RTCRtpSender. The RTCRtpReceiver for this
-          // transceiver has already been started in setRemoteDescription.
-          pc._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 +
-            '"');
-    }
-
-    return Promise.resolve();
-  };
-
-  RTCPeerConnection.prototype.setRemoteDescription = function(description) {
-    var pc = this;
-
-    if (!isActionAllowedInSignalingState('setRemoteDescription',
-        description.type, this.signalingState) || this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not set remote ' + description.type +
-          ' in state ' + pc.signalingState));
-    }
-
-    var streams = {};
-    this.remoteStreams.forEach(function(stream) {
-      streams[stream.id] = stream;
-    });
-    var receiverList = [];
-    var sections = SDPUtils.splitSections(description.sdp);
-    var sessionpart = sections.shift();
-    var isIceLite = SDPUtils.matchPrefix(sessionpart,
-        'a=ice-lite').length > 0;
-    var usingBundle = SDPUtils.matchPrefix(sessionpart,
-        'a=group:BUNDLE ').length > 0;
-    this.usingBundle = usingBundle;
-    var iceOptions = SDPUtils.matchPrefix(sessionpart,
-        'a=ice-options:')[0];
-    if (iceOptions) {
-      this.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
-          .indexOf('trickle') >= 0;
-    } else {
-      this.canTrickleIceCandidates = false;
-    }
-
-    sections.forEach(function(mediaSection, sdpMLineIndex) {
-      var lines = SDPUtils.splitLines(mediaSection);
-      var kind = SDPUtils.getKind(mediaSection);
-      // treat bundle-only as not-rejected.
-      var rejected = SDPUtils.isRejected(mediaSection) &&
-          SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0;
-      var protocol = lines[0].substr(2).split(' ')[2];
-
-      var direction = SDPUtils.getDirection(mediaSection, sessionpart);
-      var remoteMsid = SDPUtils.parseMsid(mediaSection);
-
-      var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier();
-
-      // Reject datachannels which are not implemented yet.
-      if (kind === 'application' && protocol === 'DTLS/SCTP') {
-        pc.transceivers[sdpMLineIndex] = {
-          mid: mid,
-          isDatachannel: true
-        };
-        return;
-      }
-
-      var transceiver;
-      var iceGatherer;
-      var iceTransport;
-      var dtlsTransport;
-      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 rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
-
-      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;
-          });
-
-      // Check if we can use BUNDLE and dispose transports.
-      if ((description.type === 'offer' || description.type === 'answer') &&
-          !rejected && usingBundle && sdpMLineIndex > 0 &&
-          pc.transceivers[sdpMLineIndex]) {
-        pc._disposeIceAndDtlsTransports(sdpMLineIndex);
-        pc.transceivers[sdpMLineIndex].iceGatherer =
-            pc.transceivers[0].iceGatherer;
-        pc.transceivers[sdpMLineIndex].iceTransport =
-            pc.transceivers[0].iceTransport;
-        pc.transceivers[sdpMLineIndex].dtlsTransport =
-            pc.transceivers[0].dtlsTransport;
-        if (pc.transceivers[sdpMLineIndex].rtpSender) {
-          pc.transceivers[sdpMLineIndex].rtpSender.setTransport(
-              pc.transceivers[0].dtlsTransport);
-        }
-        if (pc.transceivers[sdpMLineIndex].rtpReceiver) {
-          pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
-              pc.transceivers[0].dtlsTransport);
-        }
-      }
-      if (description.type === 'offer' && !rejected) {
-        transceiver = pc.transceivers[sdpMLineIndex] ||
-            pc._createTransceiver(kind);
-        transceiver.mid = mid;
-
-        if (!transceiver.iceGatherer) {
-          transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
-              usingBundle);
-        }
-
-        if (cands.length && transceiver.iceTransport.state === 'new') {
-          if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {
-            transceiver.iceTransport.setRemoteCandidates(cands);
-          } else {
-            cands.forEach(function(candidate) {
-              maybeAddCandidate(transceiver.iceTransport, candidate);
-            });
-          }
-        }
-
-        localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);
-
-        // filter RTX until additional stuff needed for RTX is implemented
-        // in adapter.js
-        if (edgeVersion < 15019) {
-          localCapabilities.codecs = localCapabilities.codecs.filter(
-              function(codec) {
-                return codec.name !== 'rtx';
-              });
-        }
-
-        sendEncodingParameters = transceiver.sendEncodingParameters || [{
-          ssrc: (2 * sdpMLineIndex + 2) * 1001
-        }];
-
-        // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
-        var isNewTrack = false;
-        if (direction === 'sendrecv' || direction === 'sendonly') {
-          isNewTrack = !transceiver.rtpReceiver;
-          rtpReceiver = transceiver.rtpReceiver ||
-              new window.RTCRtpReceiver(transceiver.dtlsTransport, kind);
-
-          if (isNewTrack) {
-            var stream;
-            track = rtpReceiver.track;
-            // FIXME: does not work with Plan B.
-            if (remoteMsid && remoteMsid.stream === '-') {
-              // no-op. a stream id of '-' means: no associated stream.
-            } else if (remoteMsid) {
-              if (!streams[remoteMsid.stream]) {
-                streams[remoteMsid.stream] = new window.MediaStream();
-                Object.defineProperty(streams[remoteMsid.stream], 'id', {
-                  get: function() {
-                    return remoteMsid.stream;
-                  }
-                });
-              }
-              Object.defineProperty(track, 'id', {
-                get: function() {
-                  return remoteMsid.track;
-                }
-              });
-              stream = streams[remoteMsid.stream];
-            } else {
-              if (!streams.default) {
-                streams.default = new window.MediaStream();
-              }
-              stream = streams.default;
-            }
-            if (stream) {
-              addTrackToStreamAndFireEvent(track, stream);
-              transceiver.associatedRemoteMediaStreams.push(stream);
-            }
-            receiverList.push([track, rtpReceiver, stream]);
-          }
-        } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) {
-          transceiver.associatedRemoteMediaStreams.forEach(function(s) {
-            var nativeTrack = s.getTracks().find(function(t) {
-              return t.id === transceiver.rtpReceiver.track.id;
-            });
-            if (nativeTrack) {
-              removeTrackFromStreamAndFireEvent(nativeTrack, s);
-            }
-          });
-          transceiver.associatedRemoteMediaStreams = [];
-        }
-
-        transceiver.localCapabilities = localCapabilities;
-        transceiver.remoteCapabilities = remoteCapabilities;
-        transceiver.rtpReceiver = rtpReceiver;
-        transceiver.rtcpParameters = rtcpParameters;
-        transceiver.sendEncodingParameters = sendEncodingParameters;
-        transceiver.recvEncodingParameters = recvEncodingParameters;
-
-        // Start the RTCRtpReceiver now. The RTPSender is started in
-        // setLocalDescription.
-        pc._transceive(pc.transceivers[sdpMLineIndex],
-            false,
-            isNewTrack);
-      } else if (description.type === 'answer' && !rejected) {
-        transceiver = pc.transceivers[sdpMLineIndex];
-        iceGatherer = transceiver.iceGatherer;
-        iceTransport = transceiver.iceTransport;
-        dtlsTransport = transceiver.dtlsTransport;
-        rtpReceiver = transceiver.rtpReceiver;
-        sendEncodingParameters = transceiver.sendEncodingParameters;
-        localCapabilities = transceiver.localCapabilities;
-
-        pc.transceivers[sdpMLineIndex].recvEncodingParameters =
-            recvEncodingParameters;
-        pc.transceivers[sdpMLineIndex].remoteCapabilities =
-            remoteCapabilities;
-        pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
-
-        if (cands.length && iceTransport.state === 'new') {
-          if ((isIceLite || isComplete) &&
-              (!usingBundle || sdpMLineIndex === 0)) {
-            iceTransport.setRemoteCandidates(cands);
-          } else {
-            cands.forEach(function(candidate) {
-              maybeAddCandidate(transceiver.iceTransport, candidate);
-            });
-          }
-        }
-
-        if (!usingBundle || sdpMLineIndex === 0) {
-          if (iceTransport.state === 'new') {
-            iceTransport.start(iceGatherer, remoteIceParameters,
-                'controlling');
-          }
-          if (dtlsTransport.state === 'new') {
-            dtlsTransport.start(remoteDtlsParameters);
-          }
-        }
-
-        pc._transceive(transceiver,
-            direction === 'sendrecv' || direction === 'recvonly',
-            direction === 'sendrecv' || direction === 'sendonly');
-
-        // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
-        if (rtpReceiver &&
-            (direction === 'sendrecv' || direction === 'sendonly')) {
-          track = rtpReceiver.track;
-          if (remoteMsid) {
-            if (!streams[remoteMsid.stream]) {
-              streams[remoteMsid.stream] = new window.MediaStream();
-            }
-            addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]);
-            receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);
-          } else {
-            if (!streams.default) {
-              streams.default = new window.MediaStream();
-            }
-            addTrackToStreamAndFireEvent(track, streams.default);
-            receiverList.push([track, rtpReceiver, streams.default]);
-          }
-        } else {
-          // FIXME: actually the receiver should be created later.
-          delete transceiver.rtpReceiver;
-        }
-      }
-    });
-
-    if (this._dtlsRole === undefined) {
-      this._dtlsRole = description.type === 'offer' ? 'active' : 'passive';
-    }
-
-    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 +
-            '"');
-    }
-    Object.keys(streams).forEach(function(sid) {
-      var stream = streams[sid];
-      if (stream.getTracks().length) {
-        if (pc.remoteStreams.indexOf(stream) === -1) {
-          pc.remoteStreams.push(stream);
-          var event = new Event('addstream');
-          event.stream = stream;
-          window.setTimeout(function() {
-            pc._dispatchEvent('addstream', event);
-          });
-        }
-
-        receiverList.forEach(function(item) {
-          var track = item[0];
-          var receiver = item[1];
-          if (stream.id !== item[2].id) {
-            return;
-          }
-          fireAddTrack(pc, track, receiver, [stream]);
-        });
-      }
-    });
-    receiverList.forEach(function(item) {
-      if (item[2]) {
-        return;
-      }
-      fireAddTrack(pc, item[0], item[1], []);
-    });
-
-    // check whether addIceCandidate({}) was called within four seconds after
-    // setRemoteDescription.
-    window.setTimeout(function() {
-      if (!(pc && pc.transceivers)) {
-        return;
-      }
-      pc.transceivers.forEach(function(transceiver) {
-        if (transceiver.iceTransport &&
-            transceiver.iceTransport.state === 'new' &&
-            transceiver.iceTransport.getRemoteCandidates().length > 0) {
-          console.warn('Timeout for addRemoteCandidate. Consider sending ' +
-              'an end-of-candidates notification');
-          transceiver.iceTransport.addRemoteCandidate({});
-        }
-      });
-    }, 4000);
-
-    return Promise.resolve();
-  };
-
-  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._isClosed = true;
-    this._updateSignalingState('closed');
-  };
-
-  // Update the signaling state.
-  RTCPeerConnection.prototype._updateSignalingState = function(newState) {
-    this.signalingState = newState;
-    var event = new Event('signalingstatechange');
-    this._dispatchEvent('signalingstatechange', event);
-  };
-
-  // Determine whether to fire the negotiationneeded event.
-  RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
-    var pc = this;
-    if (this.signalingState !== 'stable' || this.needNegotiation === true) {
-      return;
-    }
-    this.needNegotiation = true;
-    window.setTimeout(function() {
-      if (pc.needNegotiation === false) {
-        return;
-      }
-      pc.needNegotiation = false;
-      var event = new Event('negotiationneeded');
-      pc._dispatchEvent('negotiationneeded', event);
-    }, 0);
-  };
-
-  // Update the connection state.
-  RTCPeerConnection.prototype._updateConnectionState = function() {
-    var newState;
-    var states = {
-      'new': 0,
-      closed: 0,
-      connecting: 0,
-      checking: 0,
-      connected: 0,
-      completed: 0,
-      disconnected: 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 !== this.iceConnectionState) {
-      this.iceConnectionState = newState;
-      var event = new Event('iceconnectionstatechange');
-      this._dispatchEvent('iceconnectionstatechange', event);
-    }
-  };
-
-  RTCPeerConnection.prototype.createOffer = function() {
-    var pc = this;
-
-    if (this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not call createOffer after close'));
-    }
-
-    var numAudioTracks = this.transceivers.filter(function(t) {
-      return t.kind === 'audio';
-    }).length;
-    var numVideoTracks = this.transceivers.filter(function(t) {
-      return t.kind === 'video';
-    }).length;
-
-    // Determine number of audio and video tracks we need to send/recv.
-    var offerOptions = arguments[0];
-    if (offerOptions) {
-      // Reject Chrome legacy constraints.
-      if (offerOptions.mandatory || offerOptions.optional) {
-        throw new TypeError(
-            'Legacy mandatory/optional constraints not supported.');
-      }
-      if (offerOptions.offerToReceiveAudio !== undefined) {
-        if (offerOptions.offerToReceiveAudio === true) {
-          numAudioTracks = 1;
-        } else if (offerOptions.offerToReceiveAudio === false) {
-          numAudioTracks = 0;
-        } else {
-          numAudioTracks = offerOptions.offerToReceiveAudio;
-        }
-      }
-      if (offerOptions.offerToReceiveVideo !== undefined) {
-        if (offerOptions.offerToReceiveVideo === true) {
-          numVideoTracks = 1;
-        } else if (offerOptions.offerToReceiveVideo === false) {
-          numVideoTracks = 0;
-        } else {
-          numVideoTracks = offerOptions.offerToReceiveVideo;
-        }
-      }
-    }
-
-    this.transceivers.forEach(function(transceiver) {
-      if (transceiver.kind === 'audio') {
-        numAudioTracks--;
-        if (numAudioTracks < 0) {
-          transceiver.wantReceive = false;
-        }
-      } else if (transceiver.kind === 'video') {
-        numVideoTracks--;
-        if (numVideoTracks < 0) {
-          transceiver.wantReceive = false;
-        }
-      }
-    });
-
-    // Create M-lines for recvonly streams.
-    while (numAudioTracks > 0 || numVideoTracks > 0) {
-      if (numAudioTracks > 0) {
-        this._createTransceiver('audio');
-        numAudioTracks--;
-      }
-      if (numVideoTracks > 0) {
-        this._createTransceiver('video');
-        numVideoTracks--;
-      }
-    }
-
-    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId,
-        this._sdpSessionVersion++);
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      // For each track, create an ice gatherer, ice transport,
-      // dtls transport, potentially rtpsender and rtpreceiver.
-      var track = transceiver.track;
-      var kind = transceiver.kind;
-      var mid = SDPUtils.generateIdentifier();
-      transceiver.mid = mid;
-
-      if (!transceiver.iceGatherer) {
-        transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,
-            pc.usingBundle);
-      }
-
-      var localCapabilities = window.RTCRtpSender.getCapabilities(kind);
-      // filter RTX until additional stuff needed for RTX is implemented
-      // in adapter.js
-      if (edgeVersion < 15019) {
-        localCapabilities.codecs = localCapabilities.codecs.filter(
-            function(codec) {
-              return codec.name !== 'rtx';
-            });
-      }
-      localCapabilities.codecs.forEach(function(codec) {
-        // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
-        // by adding level-asymmetry-allowed=1
-        if (codec.name === 'H264' &&
-            codec.parameters['level-asymmetry-allowed'] === undefined) {
-          codec.parameters['level-asymmetry-allowed'] = '1';
-        }
-      });
-
-      // generate an ssrc now, to be used later in rtpSender.send
-      var sendEncodingParameters = transceiver.sendEncodingParameters || [{
-        ssrc: (2 * sdpMLineIndex + 1) * 1001
-      }];
-      if (track) {
-        // add RTX
-        if (edgeVersion >= 15019 && kind === 'video' &&
-            !sendEncodingParameters[0].rtx) {
-          sendEncodingParameters[0].rtx = {
-            ssrc: sendEncodingParameters[0].ssrc + 1
-          };
-        }
-      }
-
-      if (transceiver.wantReceive) {
-        transceiver.rtpReceiver = new window.RTCRtpReceiver(
-            transceiver.dtlsTransport, kind);
-      }
-
-      transceiver.localCapabilities = localCapabilities;
-      transceiver.sendEncodingParameters = sendEncodingParameters;
-    });
-
-    // always offer BUNDLE and dispose on return if not supported.
-    if (this._config.bundlePolicy !== 'max-compat') {
-      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
-        return t.mid;
-      }).join(' ') + '\r\n';
-    }
-    sdp += 'a=ice-options:trickle\r\n';
-
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      sdp += writeMediaSection(transceiver, transceiver.localCapabilities,
-          'offer', transceiver.stream, pc._dtlsRole);
-      sdp += 'a=rtcp-rsize\r\n';
-
-      if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' &&
-          (sdpMLineIndex === 0 || !pc.usingBundle)) {
-        transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) {
-          cand.component = 1;
-          sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n';
-        });
-
-        if (transceiver.iceGatherer.state === 'completed') {
-          sdp += 'a=end-of-candidates\r\n';
-        }
-      }
-    });
-
-    var desc = new window.RTCSessionDescription({
-      type: 'offer',
-      sdp: sdp
-    });
-    return Promise.resolve(desc);
-  };
-
-  RTCPeerConnection.prototype.createAnswer = function() {
-    var pc = this;
-
-    if (this._isClosed) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not call createAnswer after close'));
-    }
-
-    var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId,
-        this._sdpSessionVersion++);
-    if (this.usingBundle) {
-      sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
-        return t.mid;
-      }).join(' ') + '\r\n';
-    }
-    var mediaSectionsInOffer = SDPUtils.splitSections(
-        this.remoteDescription.sdp).length - 1;
-    this.transceivers.forEach(function(transceiver, sdpMLineIndex) {
-      if (sdpMLineIndex + 1 > mediaSectionsInOffer) {
-        return;
-      }
-      if (transceiver.isDatachannel) {
-        sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
-            'c=IN IP4 0.0.0.0\r\n' +
-            'a=mid:' + transceiver.mid + '\r\n';
-        return;
-      }
-
-      // FIXME: look at direction.
-      if (transceiver.stream) {
-        var localTrack;
-        if (transceiver.kind === 'audio') {
-          localTrack = transceiver.stream.getAudioTracks()[0];
-        } else if (transceiver.kind === 'video') {
-          localTrack = transceiver.stream.getVideoTracks()[0];
-        }
-        if (localTrack) {
-          // add RTX
-          if (edgeVersion >= 15019 && transceiver.kind === 'video' &&
-              !transceiver.sendEncodingParameters[0].rtx) {
-            transceiver.sendEncodingParameters[0].rtx = {
-              ssrc: transceiver.sendEncodingParameters[0].ssrc + 1
-            };
-          }
-        }
-      }
-
-      // Calculate intersection of capabilities.
-      var commonCapabilities = getCommonCapabilities(
-          transceiver.localCapabilities,
-          transceiver.remoteCapabilities);
-
-      var hasRtx = commonCapabilities.codecs.filter(function(c) {
-        return c.name.toLowerCase() === 'rtx';
-      }).length;
-      if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
-        delete transceiver.sendEncodingParameters[0].rtx;
-      }
-
-      sdp += writeMediaSection(transceiver, commonCapabilities,
-          'answer', transceiver.stream, pc._dtlsRole);
-      if (transceiver.rtcpParameters &&
-          transceiver.rtcpParameters.reducedSize) {
-        sdp += 'a=rtcp-rsize\r\n';
-      }
-    });
-
-    var desc = new window.RTCSessionDescription({
-      type: 'answer',
-      sdp: sdp
-    });
-    return Promise.resolve(desc);
-  };
-
-  RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
-    var sections;
-    if (!candidate || candidate.candidate === '') {
-      for (var j = 0; j < this.transceivers.length; j++) {
-        if (this.transceivers[j].isDatachannel) {
-          continue;
-        }
-        this.transceivers[j].iceTransport.addRemoteCandidate({});
-        sections = SDPUtils.splitSections(this.remoteDescription.sdp);
-        sections[j + 1] += 'a=end-of-candidates\r\n';
-        this.remoteDescription.sdp = sections.join('');
-        if (this.usingBundle) {
-          break;
-        }
-      }
-    } else if (!(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) {
-      throw new TypeError('sdpMLineIndex or sdpMid required');
-    } else if (!this.remoteDescription) {
-      return Promise.reject(makeError('InvalidStateError',
-          'Can not add ICE candidate without a remote description'));
-    } else {
-      var sdpMLineIndex = candidate.sdpMLineIndex;
-      if (candidate.sdpMid) {
-        for (var i = 0; i < this.transceivers.length; i++) {
-          if (this.transceivers[i].mid === candidate.sdpMid) {
-            sdpMLineIndex = i;
-            break;
-          }
-        }
-      }
-      var transceiver = this.transceivers[sdpMLineIndex];
-      if (transceiver) {
-        if (transceiver.isDatachannel) {
-          return Promise.resolve();
-        }
-        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 Promise.resolve();
-        }
-        // Ignore RTCP candidates, we assume RTCP-MUX.
-        if (cand.component && cand.component !== 1) {
-          return Promise.resolve();
-        }
-        // when using bundle, avoid adding candidates to the wrong
-        // ice transport. And avoid adding candidates added in the SDP.
-        if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 &&
-            transceiver.iceTransport !== this.transceivers[0].iceTransport)) {
-          if (!maybeAddCandidate(transceiver.iceTransport, cand)) {
-            return Promise.reject(makeError('OperationError',
-                'Can not add ICE candidate'));
-          }
-        }
-
-        // update the remoteDescription.
-        var candidateString = candidate.candidate.trim();
-        if (candidateString.indexOf('a=') === 0) {
-          candidateString = candidateString.substr(2);
-        }
-        sections = SDPUtils.splitSections(this.remoteDescription.sdp);
-        sections[sdpMLineIndex + 1] += 'a=' +
-            (cand.type ? candidateString : 'end-of-candidates')
-            + '\r\n';
-        this.remoteDescription.sdp = sections.join('');
-      } else {
-        return Promise.reject(makeError('OperationError',
-            'Can not add ICE candidate'));
-      }
-    }
-    return Promise.resolve();
-  };
-
-  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 fixStatsType = function(stat) {
-      return {
-        inboundrtp: 'inbound-rtp',
-        outboundrtp: 'outbound-rtp',
-        candidatepair: 'candidate-pair',
-        localcandidate: 'local-candidate',
-        remotecandidate: 'remote-candidate'
-      }[stat.type] || stat.type;
-    };
-    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) {
-            result[id].type = fixStatsType(result[id]);
-            results.set(id, result[id]);
-          });
-        });
-        resolve(results);
-      });
-    });
-  };
-
-  // legacy callback shims. Should be moved to adapter.js some days.
-  var methods = ['createOffer', 'createAnswer'];
-  methods.forEach(function(method) {
-    var nativeMethod = RTCPeerConnection.prototype[method];
-    RTCPeerConnection.prototype[method] = function() {
-      var args = arguments;
-      if (typeof args[0] === 'function' ||
-          typeof args[1] === 'function') { // legacy
-        return nativeMethod.apply(this, [arguments[2]])
-        .then(function(description) {
-          if (typeof args[0] === 'function') {
-            args[0].apply(null, [description]);
-          }
-        }, function(error) {
-          if (typeof args[1] === 'function') {
-            args[1].apply(null, [error]);
-          }
-        });
-      }
-      return nativeMethod.apply(this, arguments);
-    };
-  });
-
-  methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'];
-  methods.forEach(function(method) {
-    var nativeMethod = RTCPeerConnection.prototype[method];
-    RTCPeerConnection.prototype[method] = function() {
-      var args = arguments;
-      if (typeof args[1] === 'function' ||
-          typeof args[2] === 'function') { // legacy
-        return nativeMethod.apply(this, arguments)
-        .then(function() {
-          if (typeof args[1] === 'function') {
-            args[1].apply(null);
-          }
-        }, function(error) {
-          if (typeof args[2] === 'function') {
-            args[2].apply(null, [error]);
-          }
-        });
-      }
-      return nativeMethod.apply(this, arguments);
-    };
-  });
-
-  // getStats is special. It doesn't have a spec legacy method yet we support
-  // getStats(something, cb) without error callbacks.
-  ['getStats'].forEach(function(method) {
-    var nativeMethod = RTCPeerConnection.prototype[method];
-    RTCPeerConnection.prototype[method] = function() {
-      var args = arguments;
-      if (typeof args[1] === 'function') {
-        return nativeMethod.apply(this, arguments)
-        .then(function() {
-          if (typeof args[1] === 'function') {
-            args[1].apply(null);
-          }
-        });
-      }
-      return nativeMethod.apply(this, arguments);
-    };
-  });
-
-  return RTCPeerConnection;
-};
-
-},{"sdp":2}],2:[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: parseInt(parts[1], 10),
-    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;
-      case 'ufrag':
-        candidate.ufrag = parts[i + 1]; // for backward compability.
-        candidate.usernameFragment = parts[i + 1];
-        break;
-      default: // extension handling, in particular ufrag
-        candidate[parts[i]] = parts[i + 1];
-        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);
-  }
-  if (candidate.ufrag) {
-    sdp.push('ufrag');
-    sdp.push(candidate.ufrag);
-  }
-  return 'candidate:' + sdp.join(' ');
-};
-
-// Parses an ice-options line, returns an array of option tags.
-// a=ice-options:foo bar
-SDPUtils.parseIceOptions = function(line) {
-  return line.substr(14).split(' ');
-}
-
-// 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
-// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
-SDPUtils.parseExtmap = function(line) {
-  var parts = line.substr(9).split(' ');
-  return {
-    id: parseInt(parts[0], 10),
-    direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
-    uri: parts[1]
-  };
-};
-
-// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
-// RTCRtpHeaderExtension.
-SDPUtils.writeExtmap = function(headerExtension) {
-  return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
-      (headerExtension.direction && headerExtension.direction !== 'sendrecv'
-          ? '/' + headerExtension.direction
-          : '') +
-      ' ' + 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 the MID (RFC 5888) from a media section.
-// returns the MID or undefined if no mid line was found.
-SDPUtils.getMid = function(mediaSection) {
-  var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
-  if (mid) {
-    return mid.substr(6);
-  }
-}
-
-SDPUtils.parseFingerprint = function(line) {
-  var parts = line.substr(14).split(' ');
-  return {
-    algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
-    value: parts[1]
-  };
-};
-
-// 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.matchPrefix(mediaSection + sessionpart,
-      'a=fingerprint:');
-  // Note: a=setup line is ignored since we use the 'auto' role.
-  // Note2: 'algorithm' is not case sensitive except in Edge.
-  return {
-    role: 'auto',
-    fingerprints: lines.map(SDPUtils.parseFingerprint)
-  };
-};
-
-// 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);
-  });
-  var maxptime = 0;
-  caps.codecs.forEach(function(codec) {
-    if (codec.maxptime > maxptime) {
-      maxptime = codec.maxptime;
-    }
-  });
-  if (maxptime > 0) {
-    sdp += 'a=maxptime:' + maxptime + '\r\n';
-  }
-  sdp += 'a=rtcp-mux\r\n';
-
-  caps.headerExtensions.forEach(function(extension) {
-    sdp += SDPUtils.writeExtmap(extension);
-  });
-  // FIXME: write fecMechanisms.
-  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: {
-          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) {
-      // use formula from JSEP to convert b=AS to TIAS value.
-      bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95
-          - (50 * 40 * 8);
-    } else {
-      bandwidth = undefined;
-    }
-    encodingParameters.forEach(function(params) {
-      params.maxBitrate = bandwidth;
-    });
-  }
-  return encodingParameters;
-};
-
-// parses http://draft.ortc.org/#rtcrtcpparameters*
-SDPUtils.parseRtcpParameters = function(mediaSection) {
-  var rtcpParameters = {};
-
-  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) {
-    rtcpParameters.cname = remoteSsrc.value;
-    rtcpParameters.ssrc = remoteSsrc.ssrc;
-  }
-
-  // Edge uses the compound attribute instead of reducedSize
-  // compound is !reducedSize
-  var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
-  rtcpParameters.reducedSize = rsize.length > 0;
-  rtcpParameters.compound = rsize.length === 0;
-
-  // parses the rtcp-mux attrÑ–bute.
-  // Note that Edge does not support unmuxed RTCP.
-  var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
-  rtcpParameters.mux = mux.length > 0;
-
-  return rtcpParameters;
-};
-
-// parses either a=msid: or a=ssrc:... msid lines and returns
-// the id of the MediaStream and MediaStreamTrack.
-SDPUtils.parseMsid = function(mediaSection) {
-  var parts;
-  var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
-  if (spec.length === 1) {
-    parts = spec[0].substr(7).split(' ');
-    return {stream: parts[0], track: parts[1]};
-  }
-  var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
-  .map(function(line) {
-    return SDPUtils.parseSsrcMedia(line);
-  })
-  .filter(function(parts) {
-    return parts.attribute === 'msid';
-  });
-  if (planB.length > 0) {
-    parts = planB[0].value.split(' ');
-    return {stream: parts[0], track: parts[1]};
-  }
-};
-
-// Generate a session ID for SDP.
-// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
-// recommends using a cryptographically random +ve 64-bit value
-// but right now this should be acceptable and within the right range
-SDPUtils.generateSessionId = function() {
-  return Math.random().toString().substr(2, 21);
-};
-
-// Write boilder plate for start of SDP
-// sessId argument is optional - if not supplied it will
-// be generated randomly
-// sessVersion is optional and defaults to 2
-SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) {
-  var sessionId;
-  var version = sessVer !== undefined ? sessVer : 2;
-  if (sessId) {
-    sessionId = sessId;
-  } else {
-    sessionId = SDPUtils.generateSessionId();
-  }
-  // FIXME: sess-id should be an NTP timestamp.
-  return 'v=0\r\n' +
-      'o=thisisadapterortc ' + sessionId + ' ' + version + ' 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.direction) {
-    sdp += 'a=' + transceiver.direction + '\r\n';
-  } else 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) {
-    // spec.
-    var msid = 'msid:' + stream.id + ' ' +
-        transceiver.rtpSender.track.id + '\r\n';
-    sdp += 'a=' + msid;
-
-    // for Chrome.
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-        ' ' + msid;
-    if (transceiver.sendEncodingParameters[0].rtx) {
-      sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
-          ' ' + msid;
-      sdp += 'a=ssrc-group:FID ' +
-          transceiver.sendEncodingParameters[0].ssrc + ' ' +
-          transceiver.sendEncodingParameters[0].rtx.ssrc +
-          '\r\n';
-    }
-  }
-  // FIXME: this should be written by writeRtpDescription.
-  sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
-      ' cname:' + SDPUtils.localCName + '\r\n';
-  if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
-    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.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';
-};
-
-SDPUtils.getKind = function(mediaSection) {
-  var lines = SDPUtils.splitLines(mediaSection);
-  var mline = lines[0].split(' ');
-  return mline[0].substr(2);
-};
-
-SDPUtils.isRejected = function(mediaSection) {
-  return mediaSection.split(' ', 2)[1] === '0';
-};
-
-SDPUtils.parseMLine = function(mediaSection) {
-  var lines = SDPUtils.splitLines(mediaSection);
-  var mline = lines[0].split(' ');
-  return {
-    kind: mline[0].substr(2),
-    port: parseInt(mline[1], 10),
-    protocol: mline[2],
-    fmt: mline.slice(3).join(' ')
-  };
-};
-
-// Expose public methods.
-if (typeof module === 'object') {
-  module.exports = SDPUtils;
-}
-
-},{}],3:[function(require,module,exports){
-(function (global){
-/*
- *  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 adapterFactory = require('./adapter_factory.js');
-module.exports = adapterFactory({window: global.window});
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"./adapter_factory.js":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 utils = require('./utils');
-// Shimming starts here.
-module.exports = function(dependencies, opts) {
-  var window = dependencies && dependencies.window;
-
-  var options = {
-    shimChrome: true,
-    shimFirefox: true,
-    shimEdge: true,
-    shimSafari: true,
-  };
-
-  for (var key in opts) {
-    if (hasOwnProperty.call(opts, key)) {
-      options[key] = opts[key];
-    }
-  }
-
-  // Utils.
-  var logging = utils.log;
-  var browserDetails = utils.detectBrowser(window);
-
-  // 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;
-  var commonShim = require('./common_shim') || null;
-
-  // Export to the adapter global object visible in the browser.
-  var adapter = {
-    browserDetails: browserDetails,
-    commonShim: commonShim,
-    extractVersion: utils.extractVersion,
-    disableLog: utils.disableLog,
-    disableWarnings: utils.disableWarnings
-  };
-
-  // Shim browser if found.
-  switch (browserDetails.browser) {
-    case 'chrome':
-      if (!chromeShim || !chromeShim.shimPeerConnection ||
-          !options.shimChrome) {
-        logging('Chrome shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming chrome.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = chromeShim;
-      commonShim.shimCreateObjectURL(window);
-
-      chromeShim.shimGetUserMedia(window);
-      chromeShim.shimMediaStream(window);
-      chromeShim.shimSourceObject(window);
-      chromeShim.shimPeerConnection(window);
-      chromeShim.shimOnTrack(window);
-      chromeShim.shimAddTrackRemoveTrack(window);
-      chromeShim.shimGetSendersWithDtmf(window);
-
-      commonShim.shimRTCIceCandidate(window);
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    case 'firefox':
-      if (!firefoxShim || !firefoxShim.shimPeerConnection ||
-          !options.shimFirefox) {
-        logging('Firefox shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming firefox.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = firefoxShim;
-      commonShim.shimCreateObjectURL(window);
-
-      firefoxShim.shimGetUserMedia(window);
-      firefoxShim.shimSourceObject(window);
-      firefoxShim.shimPeerConnection(window);
-      firefoxShim.shimOnTrack(window);
-      firefoxShim.shimRemoveStream(window);
-
-      commonShim.shimRTCIceCandidate(window);
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    case 'edge':
-      if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) {
-        logging('MS edge shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming edge.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = edgeShim;
-      commonShim.shimCreateObjectURL(window);
-
-      edgeShim.shimGetUserMedia(window);
-      edgeShim.shimPeerConnection(window);
-      edgeShim.shimReplaceTrack(window);
-
-      // the edge shim implements the full RTCIceCandidate object.
-
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    case 'safari':
-      if (!safariShim || !options.shimSafari) {
-        logging('Safari shim is not included in this adapter release.');
-        return adapter;
-      }
-      logging('adapter.js shimming safari.');
-      // Export to the adapter global object visible in the browser.
-      adapter.browserShim = safariShim;
-      commonShim.shimCreateObjectURL(window);
-
-      safariShim.shimRTCIceServerUrls(window);
-      safariShim.shimCallbacksAPI(window);
-      safariShim.shimLocalStreamsAPI(window);
-      safariShim.shimRemoteStreamsAPI(window);
-      safariShim.shimTrackEventTransceiver(window);
-      safariShim.shimGetUserMedia(window);
-      safariShim.shimCreateOfferLegacy(window);
-
-      commonShim.shimRTCIceCandidate(window);
-      commonShim.shimMaxMessageSize(window);
-      commonShim.shimSendThrowTypeError(window);
-      break;
-    default:
-      logging('Unsupported browser!');
-      break;
-  }
-
-  return adapter;
-};
-
-},{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":10,"./safari/safari_shim":12,"./utils":13}],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 utils = require('../utils.js');
-var logging = utils.log;
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimMediaStream: function(window) {
-    window.MediaStream = window.MediaStream || window.webkitMediaStream;
-  },
-
-  shimOnTrack: function(window) {
-    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.addEventListener('track', this._ontrack = f);
-        }
-      });
-      var origSetRemoteDescription =
-          window.RTCPeerConnection.prototype.setRemoteDescription;
-      window.RTCPeerConnection.prototype.setRemoteDescription = function() {
-        var pc = this;
-        if (!pc._ontrackpoly) {
-          pc._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 receiver;
-              if (window.RTCPeerConnection.prototype.getReceivers) {
-                receiver = pc.getReceivers().find(function(r) {
-                  return r.track && r.track.id === te.track.id;
-                });
-              } else {
-                receiver = {track: te.track};
-              }
-
-              var event = new Event('track');
-              event.track = te.track;
-              event.receiver = receiver;
-              event.transceiver = {receiver: receiver};
-              event.streams = [e.stream];
-              pc.dispatchEvent(event);
-            });
-            e.stream.getTracks().forEach(function(track) {
-              var receiver;
-              if (window.RTCPeerConnection.prototype.getReceivers) {
-                receiver = pc.getReceivers().find(function(r) {
-                  return r.track && r.track.id === track.id;
-                });
-              } else {
-                receiver = {track: track};
-              }
-              var event = new Event('track');
-              event.track = track;
-              event.receiver = receiver;
-              event.transceiver = {receiver: receiver};
-              event.streams = [e.stream];
-              pc.dispatchEvent(event);
-            });
-          };
-          pc.addEventListener('addstream', pc._ontrackpoly);
-        }
-        return origSetRemoteDescription.apply(pc, arguments);
-      };
-    } else if (!('RTCRtpTransceiver' in window)) {
-      utils.wrapPeerConnectionEvent(window, 'track', function(e) {
-        if (!e.transceiver) {
-          e.transceiver = {receiver: e.receiver};
-        }
-        return e;
-      });
-    }
-  },
-
-  shimGetSendersWithDtmf: function(window) {
-    // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
-    if (typeof window === 'object' && window.RTCPeerConnection &&
-        !('getSenders' in window.RTCPeerConnection.prototype) &&
-        'createDTMFSender' in window.RTCPeerConnection.prototype) {
-      var shimSenderWithDtmf = function(pc, track) {
-        return {
-          track: track,
-          get dtmf() {
-            if (this._dtmf === undefined) {
-              if (track.kind === 'audio') {
-                this._dtmf = pc.createDTMFSender(track);
-              } else {
-                this._dtmf = null;
-              }
-            }
-            return this._dtmf;
-          },
-          _pc: pc
-        };
-      };
-
-      // augment addTrack when getSenders is not available.
-      if (!window.RTCPeerConnection.prototype.getSenders) {
-        window.RTCPeerConnection.prototype.getSenders = function() {
-          this._senders = this._senders || [];
-          return this._senders.slice(); // return a copy of the internal state.
-        };
-        var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
-        window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-          var pc = this;
-          var sender = origAddTrack.apply(pc, arguments);
-          if (!sender) {
-            sender = shimSenderWithDtmf(pc, track);
-            pc._senders.push(sender);
-          }
-          return sender;
-        };
-
-        var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
-        window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-          var pc = this;
-          origRemoveTrack.apply(pc, arguments);
-          var idx = pc._senders.indexOf(sender);
-          if (idx !== -1) {
-            pc._senders.splice(idx, 1);
-          }
-        };
-      }
-      var origAddStream = window.RTCPeerConnection.prototype.addStream;
-      window.RTCPeerConnection.prototype.addStream = function(stream) {
-        var pc = this;
-        pc._senders = pc._senders || [];
-        origAddStream.apply(pc, [stream]);
-        stream.getTracks().forEach(function(track) {
-          pc._senders.push(shimSenderWithDtmf(pc, track));
-        });
-      };
-
-      var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-      window.RTCPeerConnection.prototype.removeStream = function(stream) {
-        var pc = this;
-        pc._senders = pc._senders || [];
-        origRemoveStream.apply(pc, [stream]);
-
-        stream.getTracks().forEach(function(track) {
-          var sender = pc._senders.find(function(s) {
-            return s.track === track;
-          });
-          if (sender) {
-            pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender
-          }
-        });
-      };
-    } else if (typeof window === 'object' && window.RTCPeerConnection &&
-               'getSenders' in window.RTCPeerConnection.prototype &&
-               'createDTMFSender' in window.RTCPeerConnection.prototype &&
-               window.RTCRtpSender &&
-               !('dtmf' in window.RTCRtpSender.prototype)) {
-      var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
-      window.RTCPeerConnection.prototype.getSenders = function() {
-        var pc = this;
-        var senders = origGetSenders.apply(pc, []);
-        senders.forEach(function(sender) {
-          sender._pc = pc;
-        });
-        return senders;
-      };
-
-      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
-        get: function() {
-          if (this._dtmf === undefined) {
-            if (this.track.kind === 'audio') {
-              this._dtmf = this._pc.createDTMFSender(this.track);
-            } else {
-              this._dtmf = null;
-            }
-          }
-          return this._dtmf;
-        }
-      });
-    }
-  },
-
-  shimSourceObject: function(window) {
-    var URL = window && window.URL;
-
-    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 undefined;
-            }
-            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);
-            });
-          }
-        });
-      }
-    }
-  },
-
-  shimAddTrackRemoveTrackWithNative: function(window) {
-    // shim addTrack/removeTrack with native variants in order to make
-    // the interactions with legacy getLocalStreams behave as in other browsers.
-    // Keeps a mapping stream.id => [stream, rtpsenders...]
-    window.RTCPeerConnection.prototype.getLocalStreams = function() {
-      var pc = this;
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-      return Object.keys(this._shimmedLocalStreams).map(function(streamId) {
-        return pc._shimmedLocalStreams[streamId][0];
-      });
-    };
-
-    var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
-    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-      if (!stream) {
-        return origAddTrack.apply(this, arguments);
-      }
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-
-      var sender = origAddTrack.apply(this, arguments);
-      if (!this._shimmedLocalStreams[stream.id]) {
-        this._shimmedLocalStreams[stream.id] = [stream, sender];
-      } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) {
-        this._shimmedLocalStreams[stream.id].push(sender);
-      }
-      return sender;
-    };
-
-    var origAddStream = window.RTCPeerConnection.prototype.addStream;
-    window.RTCPeerConnection.prototype.addStream = function(stream) {
-      var pc = this;
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-
-      stream.getTracks().forEach(function(track) {
-        var alreadyExists = pc.getSenders().find(function(s) {
-          return s.track === track;
-        });
-        if (alreadyExists) {
-          throw new DOMException('Track already exists.',
-              'InvalidAccessError');
-        }
-      });
-      var existingSenders = pc.getSenders();
-      origAddStream.apply(this, arguments);
-      var newSenders = pc.getSenders().filter(function(newSender) {
-        return existingSenders.indexOf(newSender) === -1;
-      });
-      this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders);
-    };
-
-    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-      delete this._shimmedLocalStreams[stream.id];
-      return origRemoveStream.apply(this, arguments);
-    };
-
-    var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
-    window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-      var pc = this;
-      this._shimmedLocalStreams = this._shimmedLocalStreams || {};
-      if (sender) {
-        Object.keys(this._shimmedLocalStreams).forEach(function(streamId) {
-          var idx = pc._shimmedLocalStreams[streamId].indexOf(sender);
-          if (idx !== -1) {
-            pc._shimmedLocalStreams[streamId].splice(idx, 1);
-          }
-          if (pc._shimmedLocalStreams[streamId].length === 1) {
-            delete pc._shimmedLocalStreams[streamId];
-          }
-        });
-      }
-      return origRemoveTrack.apply(this, arguments);
-    };
-  },
-
-  shimAddTrackRemoveTrack: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-    // shim addTrack and removeTrack.
-    if (window.RTCPeerConnection.prototype.addTrack &&
-        browserDetails.version >= 65) {
-      return this.shimAddTrackRemoveTrackWithNative(window);
-    }
-
-    // also shim pc.getLocalStreams when addTrack is shimmed
-    // to return the original streams.
-    var origGetLocalStreams = window.RTCPeerConnection.prototype
-        .getLocalStreams;
-    window.RTCPeerConnection.prototype.getLocalStreams = function() {
-      var pc = this;
-      var nativeStreams = origGetLocalStreams.apply(this);
-      pc._reverseStreams = pc._reverseStreams || {};
-      return nativeStreams.map(function(stream) {
-        return pc._reverseStreams[stream.id];
-      });
-    };
-
-    var origAddStream = window.RTCPeerConnection.prototype.addStream;
-    window.RTCPeerConnection.prototype.addStream = function(stream) {
-      var pc = this;
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-
-      stream.getTracks().forEach(function(track) {
-        var alreadyExists = pc.getSenders().find(function(s) {
-          return s.track === track;
-        });
-        if (alreadyExists) {
-          throw new DOMException('Track already exists.',
-              'InvalidAccessError');
-        }
-      });
-      // Add identity mapping for consistency with addTrack.
-      // Unless this is being used with a stream from addTrack.
-      if (!pc._reverseStreams[stream.id]) {
-        var newStream = new window.MediaStream(stream.getTracks());
-        pc._streams[stream.id] = newStream;
-        pc._reverseStreams[newStream.id] = stream;
-        stream = newStream;
-      }
-      origAddStream.apply(pc, [stream]);
-    };
-
-    var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      var pc = this;
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-
-      origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]);
-      delete pc._reverseStreams[(pc._streams[stream.id] ?
-          pc._streams[stream.id].id : stream.id)];
-      delete pc._streams[stream.id];
-    };
-
-    window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-      var pc = this;
-      if (pc.signalingState === 'closed') {
-        throw new DOMException(
-          'The RTCPeerConnection\'s signalingState is \'closed\'.',
-          'InvalidStateError');
-      }
-      var streams = [].slice.call(arguments, 1);
-      if (streams.length !== 1 ||
-          !streams[0].getTracks().find(function(t) {
-            return t === track;
-          })) {
-        // this is not fully correct but all we can manage without
-        // [[associated MediaStreams]] internal slot.
-        throw new DOMException(
-          'The adapter.js addTrack polyfill only supports a single ' +
-          ' stream which is associated with the specified track.',
-          'NotSupportedError');
-      }
-
-      var alreadyExists = pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-      if (alreadyExists) {
-        throw new DOMException('Track already exists.',
-            'InvalidAccessError');
-      }
-
-      pc._streams = pc._streams || {};
-      pc._reverseStreams = pc._reverseStreams || {};
-      var oldStream = pc._streams[stream.id];
-      if (oldStream) {
-        // this is using odd Chrome behaviour, use with caution:
-        // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
-        // Note: we rely on the high-level addTrack/dtmf shim to
-        // create the sender with a dtmf sender.
-        oldStream.addTrack(track);
-
-        // Trigger ONN async.
-        Promise.resolve().then(function() {
-          pc.dispatchEvent(new Event('negotiationneeded'));
-        });
-      } else {
-        var newStream = new window.MediaStream([track]);
-        pc._streams[stream.id] = newStream;
-        pc._reverseStreams[newStream.id] = stream;
-        pc.addStream(newStream);
-      }
-      return pc.getSenders().find(function(s) {
-        return s.track === track;
-      });
-    };
-
-    // replace the internal stream id with the external one and
-    // vice versa.
-    function replaceInternalStreamId(pc, description) {
-      var sdp = description.sdp;
-      Object.keys(pc._reverseStreams || []).forEach(function(internalId) {
-        var externalStream = pc._reverseStreams[internalId];
-        var internalStream = pc._streams[externalStream.id];
-        sdp = sdp.replace(new RegExp(internalStream.id, 'g'),
-            externalStream.id);
-      });
-      return new RTCSessionDescription({
-        type: description.type,
-        sdp: sdp
-      });
-    }
-    function replaceExternalStreamId(pc, description) {
-      var sdp = description.sdp;
-      Object.keys(pc._reverseStreams || []).forEach(function(internalId) {
-        var externalStream = pc._reverseStreams[internalId];
-        var internalStream = pc._streams[externalStream.id];
-        sdp = sdp.replace(new RegExp(externalStream.id, 'g'),
-            internalStream.id);
-      });
-      return new RTCSessionDescription({
-        type: description.type,
-        sdp: sdp
-      });
-    }
-    ['createOffer', 'createAnswer'].forEach(function(method) {
-      var nativeMethod = window.RTCPeerConnection.prototype[method];
-      window.RTCPeerConnection.prototype[method] = function() {
-        var pc = this;
-        var args = arguments;
-        var isLegacyCall = arguments.length &&
-            typeof arguments[0] === 'function';
-        if (isLegacyCall) {
-          return nativeMethod.apply(pc, [
-            function(description) {
-              var desc = replaceInternalStreamId(pc, description);
-              args[0].apply(null, [desc]);
-            },
-            function(err) {
-              if (args[1]) {
-                args[1].apply(null, err);
-              }
-            }, arguments[2]
-          ]);
-        }
-        return nativeMethod.apply(pc, arguments)
-        .then(function(description) {
-          return replaceInternalStreamId(pc, description);
-        });
-      };
-    });
-
-    var origSetLocalDescription =
-        window.RTCPeerConnection.prototype.setLocalDescription;
-    window.RTCPeerConnection.prototype.setLocalDescription = function() {
-      var pc = this;
-      if (!arguments.length || !arguments[0].type) {
-        return origSetLocalDescription.apply(pc, arguments);
-      }
-      arguments[0] = replaceExternalStreamId(pc, arguments[0]);
-      return origSetLocalDescription.apply(pc, arguments);
-    };
-
-    // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier
-
-    var origLocalDescription = Object.getOwnPropertyDescriptor(
-        window.RTCPeerConnection.prototype, 'localDescription');
-    Object.defineProperty(window.RTCPeerConnection.prototype,
-        'localDescription', {
-          get: function() {
-            var pc = this;
-            var description = origLocalDescription.get.apply(this);
-            if (description.type === '') {
-              return description;
-            }
-            return replaceInternalStreamId(pc, description);
-          }
-        });
-
-    window.RTCPeerConnection.prototype.removeTrack = function(sender) {
-      var pc = this;
-      if (pc.signalingState === 'closed') {
-        throw new DOMException(
-          'The RTCPeerConnection\'s signalingState is \'closed\'.',
-          'InvalidStateError');
-      }
-      // We can not yet check for sender instanceof RTCRtpSender
-      // since we shim RTPSender. So we check if sender._pc is set.
-      if (!sender._pc) {
-        throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' +
-            'does not implement interface RTCRtpSender.', 'TypeError');
-      }
-      var isLocal = sender._pc === pc;
-      if (!isLocal) {
-        throw new DOMException('Sender was not created by this connection.',
-            'InvalidAccessError');
-      }
-
-      // Search for the native stream the senders track belongs to.
-      pc._streams = pc._streams || {};
-      var stream;
-      Object.keys(pc._streams).forEach(function(streamid) {
-        var hasTrack = pc._streams[streamid].getTracks().find(function(track) {
-          return sender.track === track;
-        });
-        if (hasTrack) {
-          stream = pc._streams[streamid];
-        }
-      });
-
-      if (stream) {
-        if (stream.getTracks().length === 1) {
-          // if this is the last track of the stream, remove the stream. This
-          // takes care of any shimmed _senders.
-          pc.removeStream(pc._reverseStreams[stream.id]);
-        } else {
-          // relying on the same odd chrome behaviour as above.
-          stream.removeTrack(sender.track);
-        }
-        pc.dispatchEvent(new Event('negotiationneeded'));
-      }
-    };
-  },
-
-  shimPeerConnection: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    // The RTCPeerConnection object.
-    if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) {
-      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-        // Translate iceTransportPolicy to iceTransports,
-        // see https://code.google.com/p/webrtc/issues/detail?id=4869
-        // this was fixed in M56 along with unprefixing RTCPeerConnection.
-        logging('PeerConnection');
-        if (pcConfig && pcConfig.iceTransportPolicy) {
-          pcConfig.iceTransports = pcConfig.iceTransportPolicy;
-        }
-
-        return new window.webkitRTCPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype =
-          window.webkitRTCPeerConnection.prototype;
-      // wrap static methods. Currently just generateCertificate.
-      if (window.webkitRTCPeerConnection.generateCertificate) {
-        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-          get: function() {
-            return window.webkitRTCPeerConnection.generateCertificate;
-          }
-        });
-      }
-    } else {
-      // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
-      var OrigPeerConnection = window.RTCPeerConnection;
-      window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-        if (pcConfig && pcConfig.iceServers) {
-          var newIceServers = [];
-          for (var i = 0; i < pcConfig.iceServers.length; i++) {
-            var server = pcConfig.iceServers[i];
-            if (!server.hasOwnProperty('urls') &&
-                server.hasOwnProperty('url')) {
-              utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
-              server = JSON.parse(JSON.stringify(server));
-              server.urls = server.url;
-              newIceServers.push(server);
-            } else {
-              newIceServers.push(pcConfig.iceServers[i]);
-            }
-          }
-          pcConfig.iceServers = newIceServers;
-        }
-        return new OrigPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
-      // wrap static methods. Currently just generateCertificate.
-      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-        get: function() {
-          return OrigPeerConnection.generateCertificate;
-        }
-      });
-    }
-
-    var origGetStats = window.RTCPeerConnection.prototype.getStats;
-    window.RTCPeerConnection.prototype.getStats = function(selector,
-        successCallback, errorCallback) {
-      var pc = 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.apply(this, arguments);
-      }
-
-      // When spec-style getStats is supported, return those when called with
-      // either no arguments or the selector argument is null.
-      if (origGetStats.length === 0 && (arguments.length === 0 ||
-          typeof arguments[0] !== 'function')) {
-        return origGetStats.apply(this, []);
-      }
-
-      var fixChromeStats_ = function(response) {
-        var standardReport = {};
-        var reports = response.result();
-        reports.forEach(function(report) {
-          var standardStats = {
-            id: report.id,
-            timestamp: report.timestamp,
-            type: {
-              localcandidate: 'local-candidate',
-              remotecandidate: 'remote-candidate'
-            }[report.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) {
-        return new Map(Object.keys(stats).map(function(key) {
-          return [key, stats[key]];
-        }));
-      };
-
-      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) {
-        origGetStats.apply(pc, [
-          function(response) {
-            resolve(makeMapStats(fixChromeStats_(response)));
-          }, reject]);
-      }).then(successCallback, errorCallback);
-    };
-
-    // add promise support -- natively available in Chrome 51
-    if (browserDetails.version < 51) {
-      ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-          .forEach(function(method) {
-            var nativeMethod = window.RTCPeerConnection.prototype[method];
-            window.RTCPeerConnection.prototype[method] = function() {
-              var args = arguments;
-              var pc = this;
-              var promise = new Promise(function(resolve, reject) {
-                nativeMethod.apply(pc, [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]);
-                }
-              });
-            };
-          });
-    }
-
-    // promise support for createOffer and createAnswer. Available (without
-    // bugs) since M52: crbug/619289
-    if (browserDetails.version < 52) {
-      ['createOffer', 'createAnswer'].forEach(function(method) {
-        var nativeMethod = window.RTCPeerConnection.prototype[method];
-        window.RTCPeerConnection.prototype[method] = function() {
-          var pc = 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(pc, [resolve, reject, opts]);
-            });
-          }
-          return nativeMethod.apply(this, arguments);
-        };
-      });
-    }
-
-    // shim implicit creation of RTCSessionDescription/RTCIceCandidate
-    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-        .forEach(function(method) {
-          var nativeMethod = window.RTCPeerConnection.prototype[method];
-          window.RTCPeerConnection.prototype[method] = function() {
-            arguments[0] = new ((method === 'addIceCandidate') ?
-                window.RTCIceCandidate :
-                window.RTCSessionDescription)(arguments[0]);
-            return nativeMethod.apply(this, arguments);
-          };
-        });
-
-    // support for addIceCandidate(null or undefined)
-    var nativeAddIceCandidate =
-        window.RTCPeerConnection.prototype.addIceCandidate;
-    window.RTCPeerConnection.prototype.addIceCandidate = function() {
-      if (!arguments[0]) {
-        if (arguments[1]) {
-          arguments[1].apply(null);
-        }
-        return Promise.resolve();
-      }
-      return nativeAddIceCandidate.apply(this, arguments);
-    };
-  }
-};
-
-},{"../utils.js":13,"./getusermedia":6}],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';
-var utils = require('../utils.js');
-var logging = utils.log;
-
-// Expose public methods.
-module.exports = function(window) {
-  var browserDetails = utils.detectBrowser(window);
-  var navigator = window && window.navigator;
-
-  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) {
-    if (browserDetails.version >= 61) {
-      return func(constraints);
-    }
-    constraints = JSON.parse(JSON.stringify(constraints));
-    if (constraints && typeof constraints.audio === 'object') {
-      var remap = function(obj, a, b) {
-        if (a in obj && !(b in obj)) {
-          obj[b] = obj[a];
-          delete obj[a];
-        }
-      };
-      constraints = JSON.parse(JSON.stringify(constraints));
-      remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
-      remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
-      constraints.audio = constraintsToChrome_(constraints.audio);
-    }
-    if (constraints && typeof constraints.video === 'object') {
-      // Shim facingMode for mobile & surface pro.
-      var face = constraints.video.facingMode;
-      face = face && ((typeof face === 'object') ? face : {ideal: face});
-      var getSupportedFacingModeLies = browserDetails.version < 66;
-
-      if ((face && (face.exact === 'user' || face.exact === 'environment' ||
-                    face.ideal === 'user' || face.ideal === 'environment')) &&
-          !(navigator.mediaDevices.getSupportedConstraints &&
-            navigator.mediaDevices.getSupportedConstraints().facingMode &&
-            !getSupportedFacingModeLies)) {
-        delete constraints.video.facingMode;
-        var matches;
-        if (face.exact === 'environment' || face.ideal === 'environment') {
-          matches = ['back', 'rear'];
-        } else if (face.exact === 'user' || face.ideal === 'user') {
-          matches = ['front'];
-        }
-        if (matches) {
-          // Look for matches in label, or use last cam for back (typical).
-          return navigator.mediaDevices.enumerateDevices()
-          .then(function(devices) {
-            devices = devices.filter(function(d) {
-              return d.kind === 'videoinput';
-            });
-            var dev = devices.find(function(d) {
-              return matches.some(function(match) {
-                return d.label.toLowerCase().indexOf(match) !== -1;
-              });
-            });
-            if (!dev && devices.length && matches.indexOf('back') !== -1) {
-              dev = devices[devices.length - 1]; // more likely the back cam
-            }
-            if (dev) {
-              constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :
-                                                        {ideal: dev.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',
-        InvalidStateError: 'NotReadableError',
-        DevicesNotFoundError: 'NotFoundError',
-        ConstraintNotSatisfiedError: 'OverconstrainedError',
-        TrackStartError: 'NotReadableError',
-        MediaDeviceFailedDueToShutdown: 'NotReadableError',
-        MediaDeviceKillSwitchOn: 'NotReadableError'
-      }[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) {
-        if (onError) {
-          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 window.MediaStreamTrack.getSources(function(devices) {
-            resolve(devices.map(function(device) {
-              return {label: device.label,
-                kind: kinds[device.kind],
-                deviceId: device.id,
-                groupId: ''};
-            }));
-          });
-        });
-      },
-      getSupportedConstraints: function() {
-        return {
-          deviceId: true, echoCancellation: true, facingMode: true,
-          frameRate: true, height: true, width: true
-        };
-      }
-    };
-  }
-
-  // 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).then(function(stream) {
-          if (c.audio && !stream.getAudioTracks().length ||
-              c.video && !stream.getVideoTracks().length) {
-            stream.getTracks().forEach(function(track) {
-              track.stop();
-            });
-            throw new DOMException('', 'NotFoundError');
-          }
-          return stream;
-        }, 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":13}],7:[function(require,module,exports){
-/*
- *  Copyright (c) 2017 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 utils = require('./utils');
-
-module.exports = {
-  shimRTCIceCandidate: function(window) {
-    // foundation is arbitrarily chosen as an indicator for full support for
-    // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
-    if (window.RTCIceCandidate && 'foundation' in
-        window.RTCIceCandidate.prototype) {
-      return;
-    }
-
-    var NativeRTCIceCandidate = window.RTCIceCandidate;
-    window.RTCIceCandidate = function(args) {
-      // Remove the a= which shouldn't be part of the candidate string.
-      if (typeof args === 'object' && args.candidate &&
-          args.candidate.indexOf('a=') === 0) {
-        args = JSON.parse(JSON.stringify(args));
-        args.candidate = args.candidate.substr(2);
-      }
-
-      // Augment the native candidate with the parsed fields.
-      var nativeCandidate = new NativeRTCIceCandidate(args);
-      var parsedCandidate = SDPUtils.parseCandidate(args.candidate);
-      var augmentedCandidate = Object.assign(nativeCandidate,
-          parsedCandidate);
-
-      // Add a serializer that does not serialize the extra attributes.
-      augmentedCandidate.toJSON = function() {
-        return {
-          candidate: augmentedCandidate.candidate,
-          sdpMid: augmentedCandidate.sdpMid,
-          sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
-          usernameFragment: augmentedCandidate.usernameFragment,
-        };
-      };
-      return augmentedCandidate;
-    };
-
-    // Hook up the augmented candidate in onicecandidate and
-    // addEventListener('icecandidate', ...)
-    utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) {
-      if (e.candidate) {
-        Object.defineProperty(e, 'candidate', {
-          value: new window.RTCIceCandidate(e.candidate),
-          writable: 'false'
-        });
-      }
-      return e;
-    });
-  },
-
-  // shimCreateObjectURL must be called before shimSourceObject to avoid loop.
-
-  shimCreateObjectURL: function(window) {
-    var URL = window && window.URL;
-
-    if (!(typeof window === 'object' && window.HTMLMediaElement &&
-          'srcObject' in window.HTMLMediaElement.prototype &&
-        URL.createObjectURL && URL.revokeObjectURL)) {
-      // Only shim CreateObjectURL using srcObject if srcObject exists.
-      return undefined;
-    }
-
-    var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
-    var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
-    var streams = new Map(), newId = 0;
-
-    URL.createObjectURL = function(stream) {
-      if ('getTracks' in stream) {
-        var url = 'polyblob:' + (++newId);
-        streams.set(url, stream);
-        utils.deprecated('URL.createObjectURL(stream)',
-            'elem.srcObject = stream');
-        return url;
-      }
-      return nativeCreateObjectURL(stream);
-    };
-    URL.revokeObjectURL = function(url) {
-      nativeRevokeObjectURL(url);
-      streams.delete(url);
-    };
-
-    var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
-                                              'src');
-    Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
-      get: function() {
-        return dsc.get.apply(this);
-      },
-      set: function(url) {
-        this.srcObject = streams.get(url) || null;
-        return dsc.set.apply(this, [url]);
-      }
-    });
-
-    var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute;
-    window.HTMLMediaElement.prototype.setAttribute = function() {
-      if (arguments.length === 2 &&
-          ('' + arguments[0]).toLowerCase() === 'src') {
-        this.srcObject = streams.get(arguments[1]) || null;
-      }
-      return nativeSetAttribute.apply(this, arguments);
-    };
-  },
-
-  shimMaxMessageSize: function(window) {
-    if (window.RTCSctpTransport || !window.RTCPeerConnection) {
-      return;
-    }
-    var browserDetails = utils.detectBrowser(window);
-
-    if (!('sctp' in window.RTCPeerConnection.prototype)) {
-      Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
-        get: function() {
-          return typeof this._sctp === 'undefined' ? null : this._sctp;
-        }
-      });
-    }
-
-    var sctpInDescription = function(description) {
-      var sections = SDPUtils.splitSections(description.sdp);
-      sections.shift();
-      return sections.some(function(mediaSection) {
-        var mLine = SDPUtils.parseMLine(mediaSection);
-        return mLine && mLine.kind === 'application'
-            && mLine.protocol.indexOf('SCTP') !== -1;
-      });
-    };
-
-    var getRemoteFirefoxVersion = function(description) {
-      // TODO: Is there a better solution for detecting Firefox?
-      var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);
-      if (match === null || match.length < 2) {
-        return -1;
-      }
-      var version = parseInt(match[1], 10);
-      // Test for NaN (yes, this is ugly)
-      return version !== version ? -1 : version;
-    };
-
-    var getCanSendMaxMessageSize = function(remoteIsFirefox) {
-      // Every implementation we know can send at least 64 KiB.
-      // Note: Although Chrome is technically able to send up to 256 KiB, the
-      //       data does not reach the other peer reliably.
-      //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
-      var canSendMaxMessageSize = 65536;
-      if (browserDetails.browser === 'firefox') {
-        if (browserDetails.version < 57) {
-          if (remoteIsFirefox === -1) {
-            // FF < 57 will send in 16 KiB chunks using the deprecated PPID
-            // fragmentation.
-            canSendMaxMessageSize = 16384;
-          } else {
-            // However, other FF (and RAWRTC) can reassemble PPID-fragmented
-            // messages. Thus, supporting ~2 GiB when sending.
-            canSendMaxMessageSize = 2147483637;
-          }
-        } else {
-          // Currently, all FF >= 57 will reset the remote maximum message size
-          // to the default value when a data channel is created at a later
-          // stage. :(
-          // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
-          canSendMaxMessageSize =
-            browserDetails.version === 57 ? 65535 : 65536;
-        }
-      }
-      return canSendMaxMessageSize;
-    };
-
-    var getMaxMessageSize = function(description, remoteIsFirefox) {
-      // Note: 65536 bytes is the default value from the SDP spec. Also,
-      //       every implementation we know supports receiving 65536 bytes.
-      var maxMessageSize = 65536;
-
-      // FF 57 has a slightly incorrect default remote max message size, so
-      // we need to adjust it here to avoid a failure when sending.
-      // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
-      if (browserDetails.browser === 'firefox'
-           && browserDetails.version === 57) {
-        maxMessageSize = 65535;
-      }
-
-      var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:');
-      if (match.length > 0) {
-        maxMessageSize = parseInt(match[0].substr(19), 10);
-      } else if (browserDetails.browser === 'firefox' &&
-                  remoteIsFirefox !== -1) {
-        // If the maximum message size is not present in the remote SDP and
-        // both local and remote are Firefox, the remote peer can receive
-        // ~2 GiB.
-        maxMessageSize = 2147483637;
-      }
-      return maxMessageSize;
-    };
-
-    var origSetRemoteDescription =
-        window.RTCPeerConnection.prototype.setRemoteDescription;
-    window.RTCPeerConnection.prototype.setRemoteDescription = function() {
-      var pc = this;
-      pc._sctp = null;
-
-      if (sctpInDescription(arguments[0])) {
-        // Check if the remote is FF.
-        var isFirefox = getRemoteFirefoxVersion(arguments[0]);
-
-        // Get the maximum message size the local peer is capable of sending
-        var canSendMMS = getCanSendMaxMessageSize(isFirefox);
-
-        // Get the maximum message size of the remote peer.
-        var remoteMMS = getMaxMessageSize(arguments[0], isFirefox);
-
-        // Determine final maximum message size
-        var maxMessageSize;
-        if (canSendMMS === 0 && remoteMMS === 0) {
-          maxMessageSize = Number.POSITIVE_INFINITY;
-        } else if (canSendMMS === 0 || remoteMMS === 0) {
-          maxMessageSize = Math.max(canSendMMS, remoteMMS);
-        } else {
-          maxMessageSize = Math.min(canSendMMS, remoteMMS);
-        }
-
-        // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
-        // attribute.
-        var sctp = {};
-        Object.defineProperty(sctp, 'maxMessageSize', {
-          get: function() {
-            return maxMessageSize;
-          }
-        });
-        pc._sctp = sctp;
-      }
-
-      return origSetRemoteDescription.apply(pc, arguments);
-    };
-  },
-
-  shimSendThrowTypeError: function(window) {
-    // Note: Although Firefox >= 57 has a native implementation, the maximum
-    //       message size can be reset for all data channels at a later stage.
-    //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
-
-    var origCreateDataChannel =
-      window.RTCPeerConnection.prototype.createDataChannel;
-    window.RTCPeerConnection.prototype.createDataChannel = function() {
-      var pc = this;
-      var dataChannel = origCreateDataChannel.apply(pc, arguments);
-      var origDataChannelSend = dataChannel.send;
-
-      // Patch 'send' method
-      dataChannel.send = function() {
-        var dc = this;
-        var data = arguments[0];
-        var length = data.length || data.size || data.byteLength;
-        if (length > pc.sctp.maxMessageSize) {
-          throw new DOMException('Message too large (can send a maximum of ' +
-            pc.sctp.maxMessageSize + ' bytes)', 'TypeError');
-        }
-        return origDataChannelSend.apply(dc, arguments);
-      };
-
-      return dataChannel;
-    };
-  }
-};
-
-},{"./utils":13,"sdp":2}],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 utils = require('../utils');
-var shimRTCPeerConnection = require('rtcpeerconnection-shim');
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimPeerConnection: function(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    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;
-        };
-      }
-      // this adds an additional event listener to MediaStrackTrack that signals
-      // when a tracks enabled property was changed. Workaround for a bug in
-      // addStream, see below. No longer required in 15025+
-      if (browserDetails.version < 15025) {
-        var origMSTEnabled = Object.getOwnPropertyDescriptor(
-            window.MediaStreamTrack.prototype, 'enabled');
-        Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {
-          set: function(value) {
-            origMSTEnabled.set.call(this, value);
-            var ev = new Event('enabled');
-            ev.enabled = value;
-            this.dispatchEvent(ev);
-          }
-        });
-      }
-    }
-
-    // ORTC defines the DTMF sender a bit different.
-    // https://github.com/w3c/ortc/issues/714
-    if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {
-      Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
-        get: function() {
-          if (this._dtmf === undefined) {
-            if (this.track.kind === 'audio') {
-              this._dtmf = new window.RTCDtmfSender(this);
-            } else if (this.track.kind === 'video') {
-              this._dtmf = null;
-            }
-          }
-          return this._dtmf;
-        }
-      });
-    }
-
-    window.RTCPeerConnection =
-        shimRTCPeerConnection(window, browserDetails.version);
-  },
-  shimReplaceTrack: function(window) {
-    // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
-    if (window.RTCRtpSender &&
-        !('replaceTrack' in window.RTCRtpSender.prototype)) {
-      window.RTCRtpSender.prototype.replaceTrack =
-          window.RTCRtpSender.prototype.setTrack;
-    }
-  }
-};
-
-},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],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.
- */
- /* eslint-env node */
-'use strict';
-
-// Expose public methods.
-module.exports = function(window) {
-  var navigator = window && window.navigator;
-
-  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));
-    });
-  };
-};
-
-},{}],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 utils = require('../utils');
-
-module.exports = {
-  shimGetUserMedia: require('./getusermedia'),
-  shimOnTrack: function(window) {
-    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.transceiver = {receiver: event.receiver};
-              event.streams = [e.stream];
-              this.dispatchEvent(event);
-            }.bind(this));
-          }.bind(this));
-        }
-      });
-    }
-    if (typeof window === 'object' && window.RTCTrackEvent &&
-        ('receiver' in window.RTCTrackEvent.prototype) &&
-        !('transceiver' in window.RTCTrackEvent.prototype)) {
-      Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
-        get: function() {
-          return {receiver: this.receiver};
-        }
-      });
-    }
-  },
-
-  shimSourceObject: function(window) {
-    // 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(window) {
-    var browserDetails = utils.detectBrowser(window);
-
-    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 window.mozRTCPeerConnection(pcConfig, pcConstraints);
-      };
-      window.RTCPeerConnection.prototype =
-          window.mozRTCPeerConnection.prototype;
-
-      // wrap static methods. Currently just generateCertificate.
-      if (window.mozRTCPeerConnection.generateCertificate) {
-        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-          get: function() {
-            return window.mozRTCPeerConnection.generateCertificate;
-          }
-        });
-      }
-
-      window.RTCSessionDescription = window.mozRTCSessionDescription;
-      window.RTCIceCandidate = window.mozRTCIceCandidate;
-    }
-
-    // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
-    ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
-        .forEach(function(method) {
-          var nativeMethod = window.RTCPeerConnection.prototype[method];
-          window.RTCPeerConnection.prototype[method] = function() {
-            arguments[0] = new ((method === 'addIceCandidate') ?
-                window.RTCIceCandidate :
-                window.RTCSessionDescription)(arguments[0]);
-            return nativeMethod.apply(this, arguments);
-          };
-        });
-
-    // support for addIceCandidate(null or undefined)
-    var nativeAddIceCandidate =
-        window.RTCPeerConnection.prototype.addIceCandidate;
-    window.RTCPeerConnection.prototype.addIceCandidate = function() {
-      if (!arguments[0]) {
-        if (arguments[1]) {
-          arguments[1].apply(null);
-        }
-        return Promise.resolve();
-      }
-      return 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 modernStatsTypes = {
-      inboundrtp: 'inbound-rtp',
-      outboundrtp: 'outbound-rtp',
-      candidatepair: 'candidate-pair',
-      localcandidate: 'local-candidate',
-      remotecandidate: 'remote-candidate'
-    };
-
-    var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
-    window.RTCPeerConnection.prototype.getStats = function(
-      selector,
-      onSucc,
-      onErr
-    ) {
-      return nativeGetStats.apply(this, [selector || null])
-        .then(function(stats) {
-          if (browserDetails.version < 48) {
-            stats = makeMapStats(stats);
-          }
-          if (browserDetails.version < 53 && !onSucc) {
-            // Shim only promise getStats with spec-hyphens in type names
-            // Leave callback version alone; misc old uses of forEach before Map
-            try {
-              stats.forEach(function(stat) {
-                stat.type = modernStatsTypes[stat.type] || stat.type;
-              });
-            } catch (e) {
-              if (e.name !== 'TypeError') {
-                throw e;
-              }
-              // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
-              stats.forEach(function(stat, i) {
-                stats.set(i, Object.assign({}, stat, {
-                  type: modernStatsTypes[stat.type] || stat.type
-                }));
-              });
-            }
-          }
-          return stats;
-        })
-        .then(onSucc, onErr);
-    };
-  },
-
-  shimRemoveStream: function(window) {
-    if (!window.RTCPeerConnection ||
-        'removeStream' in window.RTCPeerConnection.prototype) {
-      return;
-    }
-    window.RTCPeerConnection.prototype.removeStream = function(stream) {
-      var pc = this;
-      utils.deprecated('removeStream', 'removeTrack');
-      this.getSenders().forEach(function(sender) {
-        if (sender.track && stream.getTracks().indexOf(sender.track) !== -1) {
-          pc.removeTrack(sender);
-        }
-      });
-    };
-  }
-};
-
-},{"../utils":13,"./getusermedia":11}],11:[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 utils = require('../utils');
-var logging = utils.log;
-
-// Expose public methods.
-module.exports = function(window) {
-  var browserDetails = utils.detectBrowser(window);
-  var navigator = window && window.navigator;
-  var MediaStreamTrack = window && window.MediaStreamTrack;
-
-  var shimError_ = function(e) {
-    return {
-      name: {
-        InternalError: 'NotReadableError',
-        NotSupportedError: 'TypeError',
-        PermissionDeniedError: 'NotAllowedError',
-        SecurityError: '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).then(function(stream) {
-        // Work around https://bugzil.la/802326
-        if (c.audio && !stream.getAudioTracks().length ||
-            c.video && !stream.getVideoTracks().length) {
-          stream.getTracks().forEach(function(track) {
-            track.stop();
-          });
-          throw new DOMException('The object can not be found here.',
-                                 'NotFoundError');
-        }
-        return stream;
-      }, function(e) {
-        return Promise.reject(shimError_(e));
-      });
-    };
-  }
-  if (!(browserDetails.version > 55 &&
-      'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {
-    var remap = function(obj, a, b) {
-      if (a in obj && !(b in obj)) {
-        obj[b] = obj[a];
-        delete obj[a];
-      }
-    };
-
-    var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.
-        bind(navigator.mediaDevices);
-    navigator.mediaDevices.getUserMedia = function(c) {
-      if (typeof c === 'object' && typeof c.audio === 'object') {
-        c = JSON.parse(JSON.stringify(c));
-        remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
-        remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
-      }
-      return nativeGetUserMedia(c);
-    };
-
-    if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
-      var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
-      MediaStreamTrack.prototype.getSettings = function() {
-        var obj = nativeGetSettings.apply(this, arguments);
-        remap(obj, 'mozAutoGainControl', 'autoGainControl');
-        remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
-        return obj;
-      };
-    }
-
-    if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {
-      var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
-      MediaStreamTrack.prototype.applyConstraints = function(c) {
-        if (this.kind === 'audio' && typeof c === 'object') {
-          c = JSON.parse(JSON.stringify(c));
-          remap(c, 'autoGainControl', 'mozAutoGainControl');
-          remap(c, 'noiseSuppression', 'mozNoiseSuppression');
-        }
-        return nativeApplyConstraints.apply(this, [c]);
-      };
-    }
-  }
-  navigator.getUserMedia = function(constraints, onSuccess, onError) {
-    if (browserDetails.version < 44) {
-      return getUserMedia_(constraints, onSuccess, onError);
-    }
-    // Replace Firefox 44+'s deprecation warning with unprefixed version.
-    utils.deprecated('navigator.getUserMedia',
-        'navigator.mediaDevices.getUserMedia');
-    navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
-  };
-};
-
-},{"../utils":13}],12:[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 utils = require('../utils');
-
-module.exports = {
-  shimLocalStreamsAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getLocalStreams = function() {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        return this._localStreams;
-      };
-    }
-    if (!('getStreamById' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getStreamById = function(id) {
-        var result = null;
-        if (this._localStreams) {
-          this._localStreams.forEach(function(stream) {
-            if (stream.id === id) {
-              result = stream;
-            }
-          });
-        }
-        if (this._remoteStreams) {
-          this._remoteStreams.forEach(function(stream) {
-            if (stream.id === id) {
-              result = stream;
-            }
-          });
-        }
-        return result;
-      };
-    }
-    if (!('addStream' in window.RTCPeerConnection.prototype)) {
-      var _addTrack = window.RTCPeerConnection.prototype.addTrack;
-      window.RTCPeerConnection.prototype.addStream = function(stream) {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        if (this._localStreams.indexOf(stream) === -1) {
-          this._localStreams.push(stream);
-        }
-        var pc = this;
-        stream.getTracks().forEach(function(track) {
-          _addTrack.call(pc, track, stream);
-        });
-      };
-
-      window.RTCPeerConnection.prototype.addTrack = function(track, stream) {
-        if (stream) {
-          if (!this._localStreams) {
-            this._localStreams = [stream];
-          } else if (this._localStreams.indexOf(stream) === -1) {
-            this._localStreams.push(stream);
-          }
-        }
-        return _addTrack.call(this, track, stream);
-      };
-    }
-    if (!('removeStream' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.removeStream = function(stream) {
-        if (!this._localStreams) {
-          this._localStreams = [];
-        }
-        var index = this._localStreams.indexOf(stream);
-        if (index === -1) {
-          return;
-        }
-        this._localStreams.splice(index, 1);
-        var pc = this;
-        var tracks = stream.getTracks();
-        this.getSenders().forEach(function(sender) {
-          if (tracks.indexOf(sender.track) !== -1) {
-            pc.removeTrack(sender);
-          }
-        });
-      };
-    }
-  },
-  shimRemoteStreamsAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
-      window.RTCPeerConnection.prototype.getRemoteStreams = function() {
-        return this._remoteStreams ? this._remoteStreams : [];
-      };
-    }
-    if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
-      Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
-        get: function() {
-          return this._onaddstream;
-        },
-        set: function(f) {
-          var pc = this;
-          if (this._onaddstream) {
-            this.removeEventListener('addstream', this._onaddstream);
-            this.removeEventListener('track', this._onaddstreampoly);
-          }
-          this.addEventListener('addstream', this._onaddstream = f);
-          this.addEventListener('track', this._onaddstreampoly = function(e) {
-            e.streams.forEach(function(stream) {
-              if (!pc._remoteStreams) {
-                pc._remoteStreams = [];
-              }
-              if (pc._remoteStreams.indexOf(stream) >= 0) {
-                return;
-              }
-              pc._remoteStreams.push(stream);
-              var event = new Event('addstream');
-              event.stream = stream;
-              pc.dispatchEvent(event);
-            });
-          });
-        }
-      });
-    }
-  },
-  shimCallbacksAPI: function(window) {
-    if (typeof window !== 'object' || !window.RTCPeerConnection) {
-      return;
-    }
-    var prototype = window.RTCPeerConnection.prototype;
-    var createOffer = prototype.createOffer;
-    var createAnswer = prototype.createAnswer;
-    var setLocalDescription = prototype.setLocalDescription;
-    var setRemoteDescription = prototype.setRemoteDescription;
-    var addIceCandidate = prototype.addIceCandidate;
-
-    prototype.createOffer = function(successCallback, failureCallback) {
-      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
-      var promise = createOffer.apply(this, [options]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-
-    prototype.createAnswer = function(successCallback, failureCallback) {
-      var options = (arguments.length >= 2) ? arguments[2] : arguments[0];
-      var promise = createAnswer.apply(this, [options]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-
-    var withCallback = function(description, successCallback, failureCallback) {
-      var promise = setLocalDescription.apply(this, [description]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.setLocalDescription = withCallback;
-
-    withCallback = function(description, successCallback, failureCallback) {
-      var promise = setRemoteDescription.apply(this, [description]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.setRemoteDescription = withCallback;
-
-    withCallback = function(candidate, successCallback, failureCallback) {
-      var promise = addIceCandidate.apply(this, [candidate]);
-      if (!failureCallback) {
-        return promise;
-      }
-      promise.then(successCallback, failureCallback);
-      return Promise.resolve();
-    };
-    prototype.addIceCandidate = withCallback;
-  },
-  shimGetUserMedia: function(window) {
-    var navigator = window && window.navigator;
-
-    if (!navigator.getUserMedia) {
-      if (navigator.webkitGetUserMedia) {
-        navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
-      } else if (navigator.mediaDevices &&
-          navigator.mediaDevices.getUserMedia) {
-        navigator.getUserMedia = function(constraints, cb, errcb) {
-          navigator.mediaDevices.getUserMedia(constraints)
-          .then(cb, errcb);
-        }.bind(navigator);
-      }
-    }
-  },
-  shimRTCIceServerUrls: function(window) {
-    // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
-    var OrigPeerConnection = window.RTCPeerConnection;
-    window.RTCPeerConnection = function(pcConfig, pcConstraints) {
-      if (pcConfig && pcConfig.iceServers) {
-        var newIceServers = [];
-        for (var i = 0; i < pcConfig.iceServers.length; i++) {
-          var server = pcConfig.iceServers[i];
-          if (!server.hasOwnProperty('urls') &&
-              server.hasOwnProperty('url')) {
-            utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
-            server = JSON.parse(JSON.stringify(server));
-            server.urls = server.url;
-            delete server.url;
-            newIceServers.push(server);
-          } else {
-            newIceServers.push(pcConfig.iceServers[i]);
-          }
-        }
-        pcConfig.iceServers = newIceServers;
-      }
-      return new OrigPeerConnection(pcConfig, pcConstraints);
-    };
-    window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
-    // wrap static methods. Currently just generateCertificate.
-    if ('generateCertificate' in window.RTCPeerConnection) {
-      Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
-        get: function() {
-          return OrigPeerConnection.generateCertificate;
-        }
-      });
-    }
-  },
-  shimTrackEventTransceiver: function(window) {
-    // Add event.transceiver member over deprecated event.receiver
-    if (typeof window === 'object' && window.RTCPeerConnection &&
-        ('receiver' in window.RTCTrackEvent.prototype) &&
-        // can't check 'transceiver' in window.RTCTrackEvent.prototype, as it is
-        // defined for some reason even when window.RTCTransceiver is not.
-        !window.RTCTransceiver) {
-      Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
-        get: function() {
-          return {receiver: this.receiver};
-        }
-      });
-    }
-  },
-
-  shimCreateOfferLegacy: function(window) {
-    var origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
-    window.RTCPeerConnection.prototype.createOffer = function(offerOptions) {
-      var pc = this;
-      if (offerOptions) {
-        var audioTransceiver = pc.getTransceivers().find(function(transceiver) {
-          return transceiver.sender.track &&
-              transceiver.sender.track.kind === 'audio';
-        });
-        if (offerOptions.offerToReceiveAudio === false && audioTransceiver) {
-          if (audioTransceiver.direction === 'sendrecv') {
-            if (audioTransceiver.setDirection) {
-              audioTransceiver.setDirection('sendonly');
-            } else {
-              audioTransceiver.direction = 'sendonly';
-            }
-          } else if (audioTransceiver.direction === 'recvonly') {
-            if (audioTransceiver.setDirection) {
-              audioTransceiver.setDirection('inactive');
-            } else {
-              audioTransceiver.direction = 'inactive';
-            }
-          }
-        } else if (offerOptions.offerToReceiveAudio === true &&
-            !audioTransceiver) {
-          pc.addTransceiver('audio');
-        }
-
-        var videoTransceiver = pc.getTransceivers().find(function(transceiver) {
-          return transceiver.sender.track &&
-              transceiver.sender.track.kind === 'video';
-        });
-        if (offerOptions.offerToReceiveVideo === false && videoTransceiver) {
-          if (videoTransceiver.direction === 'sendrecv') {
-            videoTransceiver.setDirection('sendonly');
-          } else if (videoTransceiver.direction === 'recvonly') {
-            videoTransceiver.setDirection('inactive');
-          }
-        } else if (offerOptions.offerToReceiveVideo === true &&
-            !videoTransceiver) {
-          pc.addTransceiver('video');
-        }
-      }
-      return origCreateOffer.apply(pc, arguments);
-    };
-  }
-};
-
-},{"../utils":13}],13:[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;
-var deprecationWarnings_ = true;
-
-/**
- * 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.
- */
-function extractVersion(uastring, expr, pos) {
-  var match = uastring.match(expr);
-  return match && match.length >= pos && parseInt(match[pos], 10);
-}
-
-// Wraps the peerconnection event eventNameToWrap in a function
-// which returns the modified event object.
-function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
-  if (!window.RTCPeerConnection) {
-    return;
-  }
-  var proto = window.RTCPeerConnection.prototype;
-  var nativeAddEventListener = proto.addEventListener;
-  proto.addEventListener = function(nativeEventName, cb) {
-    if (nativeEventName !== eventNameToWrap) {
-      return nativeAddEventListener.apply(this, arguments);
-    }
-    var wrappedCallback = function(e) {
-      cb(wrapper(e));
-    };
-    this._eventMap = this._eventMap || {};
-    this._eventMap[cb] = wrappedCallback;
-    return nativeAddEventListener.apply(this, [nativeEventName,
-      wrappedCallback]);
-  };
-
-  var nativeRemoveEventListener = proto.removeEventListener;
-  proto.removeEventListener = function(nativeEventName, cb) {
-    if (nativeEventName !== eventNameToWrap || !this._eventMap
-        || !this._eventMap[cb]) {
-      return nativeRemoveEventListener.apply(this, arguments);
-    }
-    var unwrappedCb = this._eventMap[cb];
-    delete this._eventMap[cb];
-    return nativeRemoveEventListener.apply(this, [nativeEventName,
-      unwrappedCb]);
-  };
-
-  Object.defineProperty(proto, 'on' + eventNameToWrap, {
-    get: function() {
-      return this['_on' + eventNameToWrap];
-    },
-    set: function(cb) {
-      if (this['_on' + eventNameToWrap]) {
-        this.removeEventListener(eventNameToWrap,
-            this['_on' + eventNameToWrap]);
-        delete this['_on' + eventNameToWrap];
-      }
-      if (cb) {
-        this.addEventListener(eventNameToWrap,
-            this['_on' + eventNameToWrap] = cb);
-      }
-    }
-  });
-}
-
-// Utility methods.
-module.exports = {
-  extractVersion: extractVersion,
-  wrapPeerConnectionEvent: wrapPeerConnectionEvent,
-  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';
-  },
-
-  /**
-   * Disable or enable deprecation warnings
-   * @param {!boolean} bool set to true to disable warnings.
-   */
-  disableWarnings: function(bool) {
-    if (typeof bool !== 'boolean') {
-      return new Error('Argument type: ' + typeof bool +
-          '. Please use a boolean.');
-    }
-    deprecationWarnings_ = !bool;
-    return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
-  },
-
-  log: function() {
-    if (typeof window === 'object') {
-      if (logDisabled_) {
-        return;
-      }
-      if (typeof console !== 'undefined' && typeof console.log === 'function') {
-        console.log.apply(console, arguments);
-      }
-    }
-  },
-
-  /**
-   * Shows a deprecation warning suggesting the modern and spec-compatible API.
-   */
-  deprecated: function(oldMethod, newMethod) {
-    if (!deprecationWarnings_) {
-      return;
-    }
-    console.warn(oldMethod + ' is deprecated, please use ' + newMethod +
-        ' instead.');
-  },
-
-  /**
-   * Browser detector.
-   *
-   * @return {object} result containing browser and version
-   *     properties.
-   */
-  detectBrowser: function(window) {
-    var navigator = window && window.navigator;
-
-    // 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;
-    }
-
-    if (navigator.mozGetUserMedia) { // Firefox.
-      result.browser = 'firefox';
-      result.version = extractVersion(navigator.userAgent,
-          /Firefox\/(\d+)\./, 1);
-    } else if (navigator.webkitGetUserMedia) {
-      // Chrome, Chromium, Webview, Opera.
-      // Version matches Chrome/WebRTC version.
-      result.browser = 'chrome';
-      result.version = extractVersion(navigator.userAgent,
-          /Chrom(e|ium)\/(\d+)\./, 2);
-    } else if (navigator.mediaDevices &&
-        navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge.
-      result.browser = 'edge';
-      result.version = extractVersion(navigator.userAgent,
-          /Edge\/(\d+).(\d+)$/, 2);
-    } else if (navigator.mediaDevices &&
-        navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari.
-      result.browser = 'safari';
-      result.version = extractVersion(navigator.userAgent,
-          /AppleWebKit\/(\d+)\./, 1);
-    } else { // Default fallthrough: not supported.
-      result.browser = 'Not a supported browser.';
-      return result;
-    }
-
-    return result;
-  }
-};
-
-},{}]},{},[3])(3)
-});
\ No newline at end of file
+(function($){function findLine(sdpLines,prefix,substr){return findLineInRange(sdpLines,0,-1,prefix,substr);}
+function findLineInRange(sdpLines,startLine,endLine,prefix,substr){var realEndLine=(endLine!=-1)?endLine:sdpLines.length;for(var i=startLine;i<realEndLine;++i){if(sdpLines[i].indexOf(prefix)===0){if(!substr||sdpLines[i].toLowerCase().indexOf(substr.toLowerCase())!==-1){return i;}}}
+return null;}
+function getCodecPayloadType(sdpLine){var pattern=new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');var result=sdpLine.match(pattern);return(result&&result.length==2)?result[1]:null;}
+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(){}},useStream:null,},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(){}
+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 onRemoteStreamSuccess(self,stream){console.log("Remote Stream Success");doCallback(self,"onRemoteStream",stream);}
+function onICE(self,candidate){self.mediaData.candidate=candidate;self.mediaData.candidateList.push(self.mediaData.candidate);doCallback(self,"onICE");}
+function doCallback(self,func,arg){if(func in self.options.callbacks){self.options.callbacks[func](self,arg);}}
+function onICEComplete(self,candidate){console.log("ICE Complete");doCallback(self,"onICEComplete");}
+function onChannelError(self,e){console.error("Channel Error",e);doCallback(self,"onError",e);}
+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(typeof element.srcObject!=='undefined'){element.srcObject=stream;}else{console.error('Error attaching stream to element.');}}
+function onRemoteStream(self,stream){if(self.options.useVideo){self.options.useVideo.style.display='block';var iOS=['iPad','iPhone','iPod'].indexOf(navigator.platform)>=0;if(iOS){self.options.useVideo.setAttribute("playsinline",true);}}
+var element=self.options.useAudio;console.log("REMOTE STREAM",stream,element);FSRTCattachMediaStream(element,stream);self.remoteStream=stream;onRemoteStreamSuccess(self,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';self.options.useVideo['src']='';}
+if(self.localStream&&!self.options.useStream){if(typeof self.localStream.stop=='function'){self.localStream.stop();}else{if(self.localStream.active){var tracks=self.localStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}
+self.localStream=null;}
+if(self.options.localVideo){deactivateLocalVideo(self.options.localVideo);}
+if(self.options.localVideoStream&&!self.options.useStream){if(typeof self.options.localVideoStream.stop=='function'){self.options.localVideoStream.stop();}else{if(self.options.localVideoStream.active){var tracks=self.options.localVideoStream.getTracks();console.log(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;if(!self.localStream){return false;}
+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;}
+self.audioEnabled=audioTracks[i].enabled;}
+return!self.audioEnabled;}
+$.FSRTC.prototype.getVideoMute=function(){var self=this;return self.videoEnabled;}
+$.FSRTC.prototype.setVideoMute=function(what){var self=this;if(!self.localStream){return false;}
+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=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},turnServer:self.options.turnServer});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(self.options.useVideo&&self.options.localVideo&&!self.options.useStream){getUserMedia({constraints:{audio:false,video:{deviceId:params.useCamera},},localVideo:self.options.localVideo,useCameraLabel:self.options.useCameraLabel,useMicLabel:self.options.useMicLabel,onsuccess:function(e){self.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!",e);}});}
+if(self.options.useStream){if(self.options.useVideo){self.options.localVideoStream=self.options.useStream;if(self.options.localVideo){activateLocalVideo(self.options.localVideo,self.options.useStream);}}
+onSuccess(self.options.useStream);}
+else{getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,useCameraLabel:self.options.useCameraLabel,useMicLabel:self.options.useMicLabel,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={};if(obj.options.audioParams){audio=obj.options.audioParams;}
+if(obj.options.useMic!=="any"){audio.deviceId=assignMediaIdToConstraint(obj.options.useMic);}}
+if(obj.options.useVideo&&obj.options.localVideo&&!obj.options.useStream){getUserMedia({constraints:{audio:false,video:{deviceId:obj.options.useCamera},},localVideo:obj.options.localVideo,useCameraLabel:obj.options.useCameraLabel,useMicLabel:obj.options.useMicLabel,onsuccess:function(e){obj.options.localVideoStream=e;console.log("local video ready");},onerror:function(e){console.error("local video error!",e);}});}
+var video={};var bestFrameRate=obj.options.videoParams.vertoBestFrameRate;var minFrameRate=obj.options.videoParams.minFrameRate||15;delete obj.options.videoParams.vertoBestFrameRate;if(obj.options.screenShare){if(!obj.options.useCamera&&!!navigator.mozGetUserMedia){var dowin=window.confirm("Do you want to share an application window?  If not you can share an entire screen.");video={width:{min:obj.options.videoParams.minWidth,max:obj.options.videoParams.maxWidth},height:{min:obj.options.videoParams.minHeight,max:obj.options.videoParams.maxHeight},mediaSource:dowin?"window":"screen"}}else{var opt=[];if(obj.options.useCamera){opt.push({sourceId:obj.options.useCamera});}
+if(bestFrameRate){opt.push({minFrameRate:bestFrameRate});opt.push({maxFrameRate:bestFrameRate});}
+video={mandatory:obj.options.videoParams,optional:opt};}}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=assignMediaIdToConstraint(obj.options.useCamera,video);}
+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){self.constraints.offerToReceiveVideo=false;self.constraints.offerToReceiveAudio=false;self.constraints.offerToSendAudio=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,turnServer:self.options.turnServer});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(self.options.useStream){if(self.options.useVideo){self.options.localVideoStream=self.options.useStream;if(self.options.localVideo){activateLocalVideo(self.options.localVideo,self.options.useStream);}}
+onSuccess(self.options.useStream);}
+else if(mediaParams.audio||mediaParams.video){getUserMedia({constraints:{audio:mediaParams.audio,video:mediaParams.video},video:mediaParams.useVideo,onsuccess:onSuccess,onerror:onError,useCameraLabel:self.options.useCameraLabel,useMicLabel:self.options.useMicLabel,});}else{onSuccess(null);}};function FSRTCPeerConnection(options){var gathering=false,done=false;var config={};var default_ice=[{urls:['stun:stun.l.google.com:19302']}];if(self.options.turnServer){default_ice.push(self.options.turnServer)}
+if(options.iceServers){if(typeof(options.iceServers)==="boolean"){config.iceServers=default_ice;}else{config.iceServers=options.iceServers;}}
+config.bundlePolicy="max-compat";config.sdpSemantics="plan-b";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){options.attachStream.getAudioTracks().forEach(function(track){peer.addTrack(track,options.attachStream)});options.attachStream.getVideoTracks().forEach(function(track){peer.addTrack(track,options.attachStream)});}
+if(options.attachStreams&&options.attachStreams.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.oninactive=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)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);};}
+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 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 activateLocalVideo(el,stream){el.srcObject=stream;el.style.display='block';}
+function deactivateLocalVideo(el){el.srcObject=null;el.style.display='none';}
+function assureConstraintByLabel(constraint,fallbackLabel){if(fallbackLabel===undefined&&constraint===undefined){return Promise.resolve(constraint);}
+if(typeof(assureMediaInputId)!=='function'){console.warn('Tried to use constraint fallbacks but did not found vendor function `assureMediaInputId` on window scope. Did you forget to import `vendor/media-device-id.js` before Verto?');return Promise.resolve(constraint);}
+if(typeof(constraint)==='object'&&!constraint.deviceId){return Promise.resolve(constraint);}
+if(constraint.deviceId){if(typeof(constraint.deviceId)==='string'){return new Promise(function(resolve){assureMediaInputId(fallbackLabel,constraint.deviceId).then(function(id){resolve(Object.assign({},constraint,{deviceId:id}));}).catch(function(){resolve(constraint);});});}
+if(typeof(constraint.deviceId)==='object'&&typeof(constraint.deviceId.exact)==='string'){return new Promise(function(resolve){assureMediaInputId(fallbackLabel,constraint.deviceId.exact).then(function(id){resolve(assignMediaIdToConstraint(id,constraint));}).catch(function(){resolve(constraint);});});}}
+return Promise.resolve(constraint);}
+function trustyGetUserMedia(options,constraints){navigator.mediaDevices.getUserMedia(constraints).then(function(stream){if(options.localVideo){activateLocalVideo(options.localVideo,stream);}
+if(options.onsuccess){options.onsuccess(stream);}}).catch(options.onerror||function(e){console.error(e);});}
+function assignMediaIdToConstraint(mediaId,rest){return Object.assign({},rest||{},{deviceId:{exact:mediaId}});}
+function getUserMedia(options){var constraints=options.constraints||{audio:true,video:video_constraints,};Promise.all([assureConstraintByLabel(constraints.audio,options.useMicLabel),assureConstraintByLabel(constraints.video,options.useCameraLabel),]).then(function(assurances){trustyGetUserMedia(options,{audio:assurances[0],video:assurances[1]});}).catch(function(error){console.error('Unexpected error on media id assurance attempts:',error,'Options:',options);});}
+$.FSRTC.resSupported=function(w,h){for(var i in $.FSRTC.validRes){if($.FSRTC.validRes[i][0]==w&&$.FSRTC.validRes[i][1]==h){return true;}}
+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=[[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;}
+w=resList[resI][0];h=resList[resI][1];resI++;var video={width:{exact:w},height:{exact:h}};if(cam!=="any"){video=assignMediaIdToConstraint(cam,video);}
+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.warn(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);}
+$.FSRTC.checkPerms=function(runtime,check_audio,check_video){getUserMedia({constraints:{audio:check_audio,video:check_video,},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info("media perm init complete");if(runtime){setTimeout(runtime,100,true);}},onerror:function(e){if(check_video&&check_audio){console.error("error, retesting with audio params only");return $.FSRTC.checkPerms(runtime,check_audio,false);}
+console.error("media perm init error");if(runtime){runtime(false)}}});}})(jQuery);(function($){$.JsonRpcClient=function(options){var self=this;this.options=$.extend({ajaxUrl:null,socketUrl:null,onmessage:null,login:null,passwd:null,sessid:null,loginParams:null,userVariables:null,getSocket:function(onmessage_cb){return self._getSocket(onmessage_cb);}},options);self.ws_cnt=0;this.wsOnMessage=function(event){self._wsOnMessage(event);};};$.JsonRpcClient.prototype._ws_socket=null;$.JsonRpcClient.prototype._ws_callbacks={};$.JsonRpcClient.prototype._current_id=1;$.JsonRpcClient.prototype.speedTest=function(bytes,cb){var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this.speedCB=cb;this.speedBytes=bytes;socket.send("#SPU "+bytes);var loops=bytes/1024;var rem=bytes%1024;var i;var data=new Array(1024).join(".");for(i=0;i<loops;i++){socket.send("#SPB "+data);}
+if(rem){socket.send("#SPB "+data);}
+socket.send("#SPE");}};$.JsonRpcClient.prototype.call=function(method,params,success_cb,error_cb){if(!params){params={};}
+if(this.options.sessid){params.sessid=this.options.sessid;}
+var request={jsonrpc:'2.0',method:method,params:params,id:this._current_id++};if(!success_cb){success_cb=function(e){console.log("Success: ",e);};}
+if(!error_cb){error_cb=function(e){console.log("Error: ",e);};}
+var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request,success_cb,error_cb);return;}
+if(this.options.ajaxUrl===null){throw"$.JsonRpcClient.call used with no websocket and no http endpoint.";}
+$.ajax({type:'POST',url:this.options.ajaxUrl,data:$.toJSON(request),dataType:'json',cache:false,success:function(data){if('error'in data)error_cb(data.error,this);success_cb(data.result,this);},error:function(jqXHR,textStatus,errorThrown){try{var response=$.parseJSON(jqXHR.responseText);if('console'in window)console.log(response);error_cb(response.error,this);}catch(err){error_cb({error:jqXHR.responseText},this);}}});};$.JsonRpcClient.prototype.notify=function(method,params){if(this.options.sessid){params.sessid=this.options.sessid;}
+var request={jsonrpc:'2.0',method:method,params:params};var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request);return;}
+if(this.options.ajaxUrl===null){throw"$.JsonRpcClient.notify used with no websocket and no http endpoint.";}
+$.ajax({type:'POST',url:this.options.ajaxUrl,data:$.toJSON(request),dataType:'json',cache:false});};$.JsonRpcClient.prototype.batch=function(callback,all_done_cb,error_cb){var batch=new $.JsonRpcClient._batchObject(this,all_done_cb,error_cb);callback(batch);batch._execute();};$.JsonRpcClient.prototype.socketReady=function(){if(this._ws_socket===null||this._ws_socket.readyState>1){return false;}
+return true;};$.JsonRpcClient.prototype.closeSocket=function(){var self=this;if(self.socketReady()){self._ws_socket.onclose=function(w){console.log("Closing Socket");};self._ws_socket.close();}};$.JsonRpcClient.prototype.loginData=function(params){var self=this;self.options.login=params.login;self.options.passwd=params.passwd;self.options.loginParams=params.loginParams;self.options.userVariables=params.userVariables;};$.JsonRpcClient.prototype.connectSocket=function(onmessage_cb){var self=this;if(self.to){clearTimeout(self.to);}
+if(!self.socketReady()){self.authing=false;if(self._ws_socket){delete self._ws_socket;}
+self._ws_socket=new WebSocket(self.options.socketUrl);if(self._ws_socket){self._ws_socket.onmessage=onmessage_cb;self._ws_socket.onclose=function(w){if(!self.ws_sleep){self.ws_sleep=1000;}
+if(self.options.onWSClose){self.options.onWSClose(self);}
+if(self.ws_cnt>10&&self.options.wsFallbackURL){self.options.socketUrl=self.options.wsFallbackURL;}
+console.error("Websocket Lost "+self.ws_cnt+" sleep: "+self.ws_sleep+"msec");self.to=setTimeout(function(){console.log("Attempting Reconnection....");self.connectSocket(onmessage_cb);},self.ws_sleep);self.ws_cnt++;if(self.ws_sleep<3000&&(self.ws_cnt%10)===0){self.ws_sleep+=1000;}};self._ws_socket.onopen=function(){if(self.to){clearTimeout(self.to);}
+self.ws_sleep=1000;self.ws_cnt=0;if(self.options.onWSConnect){self.options.onWSConnect(self);}
+var req;while((req=$.JsonRpcClient.q.pop())){self._ws_socket.send(req);}};}}
+return self._ws_socket?true:false;};$.JsonRpcClient.prototype.stopRetrying=function(){if(self.to)
+clearTimeout(self.to);}
+$.JsonRpcClient.prototype._getSocket=function(onmessage_cb){if(this.options.socketUrl===null||!("WebSocket"in window))return null;this.connectSocket(onmessage_cb);return this._ws_socket;};$.JsonRpcClient.q=[];$.JsonRpcClient.prototype._wsCall=function(socket,request,success_cb,error_cb){var request_json=$.toJSON(request);if(socket.readyState<1){self=this;$.JsonRpcClient.q.push(request_json);}else{socket.send(request_json);}
+if('id'in request&&typeof success_cb!=='undefined'){this._ws_callbacks[request.id]={request:request_json,request_obj:request,success_cb:success_cb,error_cb:error_cb};}};$.JsonRpcClient.prototype._wsOnMessage=function(event){var response;if(event.data[0]=="#"&&event.data[1]=="S"&&event.data[2]=="P"){if(event.data[3]=="U"){this.up_dur=parseInt(event.data.substring(4));}else if(this.speedCB&&event.data[3]=="D"){this.down_dur=parseInt(event.data.substring(4));var up_kps=(((this.speedBytes*8)/(this.up_dur/1000))/1024).toFixed(0);var down_kps=(((this.speedBytes*8)/(this.down_dur/1000))/1024).toFixed(0);console.info("Speed Test: Up: "+up_kps+" Down: "+down_kps);var cb=this.speedCB;this.speedCB=null;cb(event,{upDur:this.up_dur,downDur:this.down_dur,upKPS:up_kps,downKPS:down_kps});}
+return;}
+try{response=$.parseJSON(event.data);if(typeof response==='object'&&'jsonrpc'in response&&response.jsonrpc==='2.0'){if('result'in response&&this._ws_callbacks[response.id]){var success_cb=this._ws_callbacks[response.id].success_cb;delete this._ws_callbacks[response.id];success_cb(response.result,this);return;}else if('error'in response&&this._ws_callbacks[response.id]){var error_cb=this._ws_callbacks[response.id].error_cb;var orig_req=this._ws_callbacks[response.id].request;if(!self.authing&&response.error.code==-32000&&self.options.login&&self.options.passwd){self.authing=true;this.call("login",{login:self.options.login,passwd:self.options.passwd,loginParams:self.options.loginParams,userVariables:self.options.userVariables},this._ws_callbacks[response.id].request_obj.method=="login"?function(e){self.authing=false;console.log("logged in");delete self._ws_callbacks[response.id];if(self.options.onWSLogin){self.options.onWSLogin(true,self);}}:function(e){self.authing=false;console.log("logged in, resending request id: "+response.id);var socket=self.options.getSocket(self.wsOnMessage);if(socket!==null){socket.send(orig_req);}
+if(self.options.onWSLogin){self.options.onWSLogin(true,self);}},function(e){console.log("error logging in, request id:",response.id);delete self._ws_callbacks[response.id];error_cb(response.error,this);if(self.options.onWSLogin){self.options.onWSLogin(false,self);}});return;}
+delete this._ws_callbacks[response.id];error_cb(response.error,this);return;}}}catch(err){console.log("ERROR: "+err);return;}
+if(typeof this.options.onmessage==='function'){event.eventData=response;if(!event.eventData){event.eventData={};}
+var reply=this.options.onmessage(event);if(reply&&typeof reply==="object"&&event.eventData.id){var msg={jsonrpc:"2.0",id:event.eventData.id,result:reply};var socket=self.options.getSocket(self.wsOnMessage);if(socket!==null){socket.send($.toJSON(msg));}}}};$.JsonRpcClient._batchObject=function(jsonrpcclient,all_done_cb,error_cb){this._requests=[];this.jsonrpcclient=jsonrpcclient;this.all_done_cb=all_done_cb;this.error_cb=typeof error_cb==='function'?error_cb:function(){};};$.JsonRpcClient._batchObject.prototype.call=function(method,params,success_cb,error_cb){if(!params){params={};}
+if(this.options.sessid){params.sessid=this.options.sessid;}
+if(!success_cb){success_cb=function(e){console.log("Success: ",e);};}
+if(!error_cb){error_cb=function(e){console.log("Error: ",e);};}
+this._requests.push({request:{jsonrpc:'2.0',method:method,params:params,id:this.jsonrpcclient._current_id++},success_cb:success_cb,error_cb:error_cb});};$.JsonRpcClient._batchObject.prototype.notify=function(method,params){if(this.options.sessid){params.sessid=this.options.sessid;}
+this._requests.push({request:{jsonrpc:'2.0',method:method,params:params}});};$.JsonRpcClient._batchObject.prototype._execute=function(){var self=this;if(this._requests.length===0)return;var batch_request=[];var handlers={};var i=0;var call;var success_cb;var error_cb;var socket=self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);if(socket!==null){for(i=0;i<this._requests.length;i++){call=this._requests[i];success_cb=('success_cb'in call)?call.success_cb:undefined;error_cb=('error_cb'in call)?call.error_cb:undefined;self.jsonrpcclient._wsCall(socket,call.request,success_cb,error_cb);}
+if(typeof all_done_cb==='function')all_done_cb(result);return;}
+for(i=0;i<this._requests.length;i++){call=this._requests[i];batch_request.push(call.request);if('id'in call.request){handlers[call.request.id]={success_cb:call.success_cb,error_cb:call.error_cb};}}
+success_cb=function(data){self._batchCb(data,handlers,self.all_done_cb);};if(self.jsonrpcclient.options.ajaxUrl===null){throw"$.JsonRpcClient.batch used with no websocket and no http endpoint.";}
+$.ajax({url:self.jsonrpcclient.options.ajaxUrl,data:$.toJSON(batch_request),dataType:'json',cache:false,type:'POST',error:function(jqXHR,textStatus,errorThrown){self.error_cb(jqXHR,textStatus,errorThrown);},success:success_cb});};$.JsonRpcClient._batchObject.prototype._batchCb=function(result,handlers,all_done_cb){for(var i=0;i<result.length;i++){var response=result[i];if('error'in response){if(response.id===null||!(response.id in handlers)){if('console'in window)console.log(response);}else{handlers[response.id].error_cb(response.error,this);}}else{if(!(response.id in handlers)&&'console'in window){console.log(response);}else{handlers[response.id].success_cb(response.result,this);}}}
+if(typeof all_done_cb==='function')all_done_cb(result);};})(jQuery);(function($){var sources=[];var generateGUID=(typeof(window.crypto)!=='undefined'&&typeof(window.crypto.getRandomValues)!=='undefined')?function(){var buf=new Uint16Array(8);window.crypto.getRandomValues(buf);var S4=function(num){var ret=num.toString(16);while(ret.length<4){ret="0"+ret;}
+return ret;};return(S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-"+S4(buf[3])+"-"+S4(buf[4])+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7]));}:function(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){var r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);return v.toString(16);});};$.verto=function(options,callbacks){var verto=this;$.verto.saved.push(verto);verto.options=$.extend({login:null,passwd:null,socketUrl:null,tag:null,localTag:null,videoParams:{},audioParams:{},loginParams:{},deviceParams:{onResCheck:null},userVariables:{},iceServers:false,ringSleep:6000,sessid:null,useStream:null},options);if(verto.options.deviceParams.useCamera){$.FSRTC.getValidRes(verto.options.deviceParams.useCamera,verto.options.deviceParams.onResCheck);}
+if(!verto.options.deviceParams.useMic){verto.options.deviceParams.useMic="any";}
+if(!verto.options.deviceParams.useSpeak){verto.options.deviceParams.useSpeak="any";}
+if(verto.options.sessid){verto.sessid=verto.options.sessid;}else{verto.sessid=localStorage.getItem("verto_session_uuid")||generateGUID();localStorage.setItem("verto_session_uuid",verto.sessid);}
+verto.dialogs={};verto.callbacks=callbacks||{};verto.eventSUBS={};verto.rpcClient=new $.JsonRpcClient({login:verto.options.login,passwd:verto.options.passwd,socketUrl:verto.options.socketUrl,wsFallbackURL:verto.options.wsFallbackURL,turnServer:verto.options.turnServer,loginParams:verto.options.loginParams,userVariables:verto.options.userVariables,sessid:verto.sessid,onmessage:function(e){return verto.handleMessage(e.eventData);},onWSConnect:function(o){o.call('login',{});},onWSLogin:function(success){if(verto.callbacks.onWSLogin){verto.callbacks.onWSLogin(verto,success);}},onWSClose:function(success){if(verto.callbacks.onWSClose){verto.callbacks.onWSClose(verto,success);}
+verto.purge();}});var tag=verto.options.tag;if(typeof(tag)==="function"){tag=tag();}
+if(verto.options.ringFile&&verto.options.tag){verto.ringer=$("#"+tag);}
+verto.rpcClient.call('login',{});};$.verto.prototype.deviceParams=function(obj){var verto=this;for(var i in obj){verto.options.deviceParams[i]=obj[i];}
+if(obj.useCamera){$.FSRTC.getValidRes(verto.options.deviceParams.useCamera,obj?obj.onResCheck:undefined);}};$.verto.prototype.videoParams=function(obj){var verto=this;for(var i in obj){verto.options.videoParams[i]=obj[i];}};$.verto.prototype.iceServers=function(obj){var verto=this;verto.options.iceServers=obj;};$.verto.prototype.loginData=function(params){var verto=this;verto.options.login=params.login;verto.options.passwd=params.passwd;verto.rpcClient.loginData(params);};$.verto.prototype.logout=function(msg){var verto=this;verto.rpcClient.closeSocket();if(verto.callbacks.onWSClose){verto.callbacks.onWSClose(verto,false);}
+verto.purge();};$.verto.prototype.login=function(msg){var verto=this;verto.logout();verto.rpcClient.call('login',{});};$.verto.prototype.message=function(msg){var verto=this;var err=0;if(!msg.to){console.error("Missing To");err++;}
+if(!msg.body){console.error("Missing Body");err++;}
+if(err){return false;}
+verto.sendMethod("verto.info",{msg:msg});return true;};$.verto.prototype.processReply=function(method,success,e){var verto=this;var i;switch(method){case"verto.subscribe":for(i in e.unauthorizedChannels){drop_bad(verto,e.unauthorizedChannels[i]);}
+for(i in e.subscribedChannels){mark_ready(verto,e.subscribedChannels[i]);}
+break;case"verto.unsubscribe":break;}};$.verto.prototype.sendMethod=function(method,params){var verto=this;verto.rpcClient.call(method,params,function(e){verto.processReply(method,true,e);},function(e){verto.processReply(method,false,e);});};function do_sub(verto,channel,obj){}
+function drop_bad(verto,channel){console.error("drop unauthorized channel: "+channel);delete verto.eventSUBS[channel];}
+function mark_ready(verto,channel){for(var j in verto.eventSUBS[channel]){verto.eventSUBS[channel][j].ready=true;console.log("subscribed to channel: "+channel);if(verto.eventSUBS[channel][j].readyHandler){verto.eventSUBS[channel][j].readyHandler(verto,channel);}}}
+var SERNO=1;function do_subscribe(verto,channel,subChannels,sparams){var params=sparams||{};var local=params.local;var obj={eventChannel:channel,userData:params.userData,handler:params.handler,ready:false,readyHandler:params.readyHandler,serno:SERNO++};var isnew=false;if(!verto.eventSUBS[channel]){verto.eventSUBS[channel]=[];subChannels.push(channel);isnew=true;}
+verto.eventSUBS[channel].push(obj);if(local){obj.ready=true;obj.local=true;}
+if(!isnew&&verto.eventSUBS[channel][0].ready){obj.ready=true;if(obj.readyHandler){obj.readyHandler(verto,channel);}}
+return{serno:obj.serno,eventChannel:channel};}
+$.verto.prototype.subscribe=function(channel,sparams){var verto=this;var r=[];var subChannels=[];var params=sparams||{};if(typeof(channel)==="string"){r.push(do_subscribe(verto,channel,subChannels,params));}else{for(var i in channel){r.push(do_subscribe(verto,channel,subChannels,params));}}
+if(subChannels.length){verto.sendMethod("verto.subscribe",{eventChannel:subChannels.length==1?subChannels[0]:subChannels,subParams:params.subParams});}
+return r;};$.verto.prototype.unsubscribe=function(handle){var verto=this;var i;if(!handle){for(i in verto.eventSUBS){if(verto.eventSUBS[i]){verto.unsubscribe(verto.eventSUBS[i]);}}}else{var unsubChannels={};var sendChannels=[];var channel;if(typeof(handle)=="string"){delete verto.eventSUBS[handle];unsubChannels[handle]++;}else{for(i in handle){if(typeof(handle[i])=="string"){channel=handle[i];delete verto.eventSUBS[channel];unsubChannels[channel]++;}else{var repl=[];channel=handle[i].eventChannel;for(var j in verto.eventSUBS[channel]){if(verto.eventSUBS[channel][j].serno==handle[i].serno){}else{repl.push(verto.eventSUBS[channel][j]);}}
+verto.eventSUBS[channel]=repl;if(verto.eventSUBS[channel].length===0){delete verto.eventSUBS[channel];unsubChannels[channel]++;}}}}
+for(var u in unsubChannels){console.log("Sending Unsubscribe for: ",u);sendChannels.push(u);}
+if(sendChannels.length){verto.sendMethod("verto.unsubscribe",{eventChannel:sendChannels.length==1?sendChannels[0]:sendChannels});}}};$.verto.prototype.broadcast=function(channel,params){var verto=this;var msg={eventChannel:channel,data:{}};for(var i in params){msg.data[i]=params[i];}
+verto.sendMethod("verto.broadcast",msg);};$.verto.prototype.purge=function(callID){var verto=this;var x=0;var i;for(i in verto.dialogs){if(!x){console.log("purging dialogs");}
+x++;verto.dialogs[i].setState($.verto.enum.state.purge);}
+for(i in verto.eventSUBS){if(verto.eventSUBS[i]){console.log("purging subscription: "+i);delete verto.eventSUBS[i];}}};$.verto.prototype.hangup=function(callID){var verto=this;if(callID){var dialog=verto.dialogs[callID];if(dialog){dialog.hangup();}}else{for(var i in verto.dialogs){verto.dialogs[i].hangup();}}};$.verto.prototype.newCall=function(args,callbacks){var verto=this;if(!verto.rpcClient.socketReady()){console.error("Not Connected...");return;}
+if(args["useCamera"]){verto.options.deviceParams["useCamera"]=args["useCamera"];verto.options.deviceParams["useCameraLabel"]=args["useCameraLabel"];}
+var dialog=new $.verto.dialog($.verto.enum.direction.outbound,this,args);if(callbacks){dialog.callbacks=callbacks;}
+dialog.invite();return dialog;};$.verto.prototype.handleMessage=function(data){var verto=this;if(!(data&&data.method)){console.error("Invalid Data",data);return;}
+if(data.params.callID){var dialog=verto.dialogs[data.params.callID];if(data.method==="verto.attach"&&dialog){delete dialog.verto.dialogs[dialog.callID];dialog.rtc.stop();dialog=null;}
+if(dialog){switch(data.method){case'verto.bye':dialog.hangup(data.params);break;case'verto.answer':dialog.handleAnswer(data.params);break;case'verto.media':dialog.handleMedia(data.params);break;case'verto.display':dialog.handleDisplay(data.params);break;case'verto.info':dialog.handleInfo(data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",dialog,data.method);break;}}else{switch(data.method){case'verto.attach':data.params.attach=true;if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.useVideo=true;}
+if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
+dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);dialog.setState($.verto.enum.state.recovering);break;case'verto.invite':if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.wantVideo=true;}
+if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
+dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED");break;}}
+return{method:data.method};}else{switch(data.method){case'verto.punt':verto.purge();verto.logout();break;case'verto.event':var list=null;var key=null;if(data.params){key=data.params.eventChannel;}
+if(key){list=verto.eventSUBS[key];if(!list){list=verto.eventSUBS[key.split(".")[0]];}}
+if(!list&&key&&key===verto.sessid){if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.pvtEvent,data.params);}}else if(!list&&key&&verto.dialogs[key]){verto.dialogs[key].sendMessage($.verto.enum.message.pvtEvent,data.params);}else if(!list){if(!key){key="UNDEFINED";}
+console.error("UNSUBBED or invalid EVENT "+key+" IGNORED");}else{for(var i in list){var sub=list[i];if(!sub||!sub.ready){console.error("invalid EVENT for "+key+" IGNORED");}else if(sub.handler){sub.handler(verto,data.params,sub.userData);}else if(verto.callbacks.onEvent){verto.callbacks.onEvent(verto,data.params,sub.userData);}else{console.log("EVENT:",data.params);}}}
+break;case"verto.info":if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.info,data.params.msg);}
+console.debug("MESSAGE from: "+data.params.msg.from,data.params.msg.body);break;case'verto.clientReady':if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.clientReady,data.params);}
+console.debug("CLIENT READY",data.params);break;default:console.error("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",data.method);break;}}};var del_array=function(array,name){var r=[];var len=array.length;for(var i=0;i<len;i++){if(array[i]!=name){r.push(array[i]);}}
+return r;};var hashArray=function(){var vha=this;var hash={};var array=[];vha.reorder=function(a){array=a;var h=hash;hash={};var len=array.length;for(var i=0;i<len;i++){var key=array[i];if(h[key]){hash[key]=h[key];delete h[key];}}
+h=undefined;};vha.clear=function(){hash=undefined;array=undefined;hash={};array=[];};vha.add=function(name,val,insertAt){var redraw=false;if(!hash[name]){if(insertAt===undefined||insertAt<0||insertAt>=array.length){array.push(name);}else{var x=0;var n=[];var len=array.length;for(var i=0;i<len;i++){if(x++==insertAt){n.push(name);}
+n.push(array[i]);}
+array=undefined;array=n;n=undefined;redraw=true;}}
+hash[name]=val;return redraw;};vha.del=function(name){var r=false;if(hash[name]){array=del_array(array,name);delete hash[name];r=true;}else{console.error("can't del nonexistant key "+name);}
+return r;};vha.get=function(name){return hash[name];};vha.order=function(){return array;};vha.hash=function(){return hash;};vha.indexOf=function(name){var len=array.length;for(var i=0;i<len;i++){if(array[i]==name){return i;}}};vha.arrayLen=function(){return array.length;};vha.asArray=function(){var r=[];var len=array.length;for(var i=0;i<len;i++){var key=array[i];r.push(hash[key]);}
+return r;};vha.each=function(cb){var len=array.length;for(var i=0;i<len;i++){cb(array[i],hash[array[i]]);}};vha.dump=function(html){var str="";vha.each(function(name,val){str+="name: "+name+" val: "+JSON.stringify(val)+(html?"<br>":"\n");});return str;};};$.verto.liveArray=function(verto,context,name,config){var la=this;var lastSerno=0;var binding=null;var user_obj=config.userObj;var local=false;hashArray.call(la);la._add=la.add;la._del=la.del;la._reorder=la.reorder;la._clear=la.clear;la.context=context;la.name=name;la.user_obj=user_obj;la.verto=verto;la.broadcast=function(channel,obj){verto.broadcast(channel,obj);};la.errs=0;la.clear=function(){la._clear();lastSerno=0;if(la.onChange){la.onChange(la,{action:"clear"});}};la.checkSerno=function(serno){if(serno<0){return true;}
+if(lastSerno>0&&serno!=(lastSerno+1)){if(la.onErr){la.onErr(la,{lastSerno:lastSerno,serno:serno});}
+la.errs++;console.debug(la.errs);if(la.errs<3){la.bootstrap(la.user_obj);}
+return false;}else{lastSerno=serno;return true;}};la.reorder=function(serno,a){if(la.checkSerno(serno)){la._reorder(a);if(la.onChange){la.onChange(la,{serno:serno,action:"reorder"});}}};la.init=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){if(la.onChange){la.onChange(la,{serno:serno,action:"init",index:index,key:key,data:val});}}};la.bootObj=function(serno,val){if(la.checkSerno(serno)){for(var i in val){la._add(val[i][0],val[i][1]);}
+if(la.onChange){la.onChange(la,{serno:serno,action:"bootObj",data:val,redraw:true});}}};la.add=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){var redraw=la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"add",index:index,key:key,data:val,redraw:redraw});}}};la.modify=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"modify",key:key,data:val,index:index});}}};la.del=function(serno,key,index){if(key===null||key===undefined){key=serno;}
+if(la.checkSerno(serno)){if(index===null||index<0||index===undefined){index=la.indexOf(key);}
+var ok=la._del(key);if(ok&&la.onChange){la.onChange(la,{serno:serno,action:"del",key:key,index:index});}}};var eventHandler=function(v,e,la){var packet=e.data;if(packet.name!=la.name){return;}
+switch(packet.action){case"init":la.init(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"bootObj":la.bootObj(packet.wireSerno,packet.data);break;case"add":la.add(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"modify":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.modify(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);}
+break;case"del":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.del(packet.wireSerno,packet.hashKey,packet.arrIndex);}
+break;case"clear":la.clear();break;case"reorder":la.reorder(packet.wireSerno,packet.order);break;default:if(la.checkSerno(packet.wireSerno)){if(la.onChange){la.onChange(la,{serno:packet.wireSerno,action:packet.action,data:packet.data});}}
+break;}};if(la.context){binding=la.verto.subscribe(la.context,{handler:eventHandler,userData:la,subParams:config.subParams});}
+la.destroy=function(){la._clear();la.verto.unsubscribe(binding);};la.sendCommand=function(cmd,obj){var self=la;self.broadcast(self.context,{liveArray:{command:cmd,context:self.context,name:self.name,obj:obj}});};la.bootstrap=function(obj){var self=la;la.sendCommand("bootstrap",obj);};la.changepage=function(obj){var self=la;self.clear();self.broadcast(self.context,{liveArray:{command:"changepage",context:la.context,name:la.name,obj:obj}});};la.heartbeat=function(obj){var self=la;var callback=function(){self.heartbeat.call(self,obj);};self.broadcast(self.context,{liveArray:{command:"heartbeat",context:self.context,name:self.name,obj:obj}});self.hb_pid=setTimeout(callback,30000);};la.bootstrap(la.user_obj);};$.verto.liveTable=function(verto,context,name,jq,config){var dt;var la=new $.verto.liveArray(verto,context,name,{subParams:config.subParams});var lt=this;lt.liveArray=la;lt.dataTable=dt;lt.verto=verto;lt.destroy=function(){if(dt){dt.fnDestroy();}
+if(la){la.destroy();}
+dt=null;la=null;};la.onErr=function(obj,args){console.error("Error: ",obj,args);};function genRow(data){if(typeof(data[4])==="string"&&data[4].indexOf("{")>-1){var tmp=$.parseJSON(data[4]);data[4]=tmp.oldStatus;data[5]=null;}
+return data;}
+function genArray(obj){var data=obj.asArray();for(var i in data){data[i]=genRow(data[i]);}
+return data;}
+la.onChange=function(obj,args){var index=0;var iserr=0;if(!dt){if(!config.aoColumns){if(args.action!="init"){return;}
+config.aoColumns=[];for(var i in args.data){config.aoColumns.push({"sTitle":args.data[i]});}}
+dt=jq.dataTable(config);}
+if(dt&&(args.action=="del"||args.action=="modify")){index=args.index;if(index===undefined&&args.key){index=la.indexOf(args.key);}
+if(index===undefined){console.error("INVALID PACKET Missing INDEX\n",args);return;}}
+if(config.onChange){config.onChange(obj,args);}
+try{switch(args.action){case"bootObj":if(!args.data){console.error("missing data");return;}
+dt.fnClearTable();dt.fnAddData(genArray(obj));dt.fnAdjustColumnSizing();break;case"add":if(!args.data){console.error("missing data");return;}
+if(args.redraw>-1){dt.fnClearTable();dt.fnAddData(genArray(obj));}else{dt.fnAddData(genRow(args.data));}
+dt.fnAdjustColumnSizing();break;case"modify":if(!args.data){return;}
+dt.fnUpdate(genRow(args.data),index);dt.fnAdjustColumnSizing();break;case"del":dt.fnDeleteRow(index);dt.fnAdjustColumnSizing();break;case"clear":dt.fnClearTable();break;case"reorder":dt.fnClearTable();dt.fnAddData(genArray(obj));break;case"hide":jq.hide();break;case"show":jq.show();break;}}catch(err){console.error("ERROR: "+err);iserr++;}
+if(iserr){obj.errs++;if(obj.errs<3){obj.bootstrap(obj.user_obj);}}else{obj.errs=0;}};la.onChange(la,{action:"init"});};var CONFMAN_SERNO=1;$.verto.conf=function(verto,params){var conf=this;conf.params=$.extend({dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);conf.verto=verto;conf.serno=CONFMAN_SERNO++;createMainModeratorMethods();verto.subscribe(conf.params.laData.modChannel,{handler:function(v,e){if(conf.params.onBroadcast){conf.params.onBroadcast(verto,conf,e.data);}}});verto.subscribe(conf.params.laData.infoChannel,{handler:function(v,e){if(typeof(conf.params.infoCallback)==="function"){conf.params.infoCallback(v,e);}}});verto.subscribe(conf.params.laData.chatChannel,{handler:function(v,e){if(typeof(conf.params.chatCallback)==="function"){conf.params.chatCallback(v,e);}}});};$.verto.conf.prototype.modCommand=function(cmd,id,value){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.conf.prototype.destroy=function(){var conf=this;conf.destroyed=true;conf.params.onBroadcast(conf.verto,conf,'destroy');if(conf.params.laData.modChannel){conf.verto.unsubscribe(conf.params.laData.modChannel);}
+if(conf.params.laData.chatChannel){conf.verto.unsubscribe(conf.params.laData.chatChannel);}
+if(conf.params.laData.infoChannel){conf.verto.unsubscribe(conf.params.laData.infoChannel);}};function createMainModeratorMethods(){$.verto.conf.prototype.listVideoLayouts=function(){this.modCommand("list-videoLayouts",null,null);};$.verto.conf.prototype.play=function(file){this.modCommand("play",null,file);};$.verto.conf.prototype.stop=function(){this.modCommand("stop",null,"all");};$.verto.conf.prototype.deaf=function(memberID){this.modCommand("deaf",parseInt(memberID));};$.verto.conf.prototype.undeaf=function(memberID){this.modCommand("undeaf",parseInt(memberID));};$.verto.conf.prototype.record=function(file){this.modCommand("recording",null,["start",file]);};$.verto.conf.prototype.stopRecord=function(){this.modCommand("recording",null,["stop","all"]);};$.verto.conf.prototype.snapshot=function(file){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-write-png",null,file);};$.verto.conf.prototype.setVideoLayout=function(layout,canvasID){if(!this.params.hasVid){throw'Conference has no video';}
+if(canvasID){this.modCommand("vid-layout",null,[layout,canvasID]);}else{this.modCommand("vid-layout",null,layout);}};$.verto.conf.prototype.kick=function(memberID){this.modCommand("kick",parseInt(memberID));};$.verto.conf.prototype.muteMic=function(memberID){this.modCommand("tmute",parseInt(memberID));};$.verto.conf.prototype.muteVideo=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("tvmute",parseInt(memberID));};$.verto.conf.prototype.presenter=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-res-id",parseInt(memberID),"presenter");};$.verto.conf.prototype.videoFloor=function(memberID){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-floor",parseInt(memberID),"force");};$.verto.conf.prototype.banner=function(memberID,text){if(!this.params.hasVid){throw'Conference has no video';}
+this.modCommand("vid-banner",parseInt(memberID),escape(text));};$.verto.conf.prototype.volumeDown=function(memberID){this.modCommand("volume_out",parseInt(memberID),"down");};$.verto.conf.prototype.volumeUp=function(memberID){this.modCommand("volume_out",parseInt(memberID),"up");};$.verto.conf.prototype.gainDown=function(memberID){this.modCommand("volume_in",parseInt(memberID),"down");};$.verto.conf.prototype.gainUp=function(memberID){this.modCommand("volume_in",parseInt(memberID),"up");};$.verto.conf.prototype.transfer=function(memberID,exten){this.modCommand("transfer",parseInt(memberID),exten);};$.verto.conf.prototype.sendChat=function(message,type){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};}
+$.verto.modfuncs={};$.verto.confMan=function(verto,params){var confMan=this;confMan.params=$.extend({tableID:null,statusID:null,mainModID:null,dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);confMan.verto=verto;confMan.serno=CONFMAN_SERNO++;confMan.canvasCount=confMan.params.laData.canvasCount;function genMainMod(jq){var play_id="play_"+confMan.serno;var stop_id="stop_"+confMan.serno;var recording_id="recording_"+confMan.serno;var snapshot_id="snapshot_"+confMan.serno;var rec_stop_id="recording_stop"+confMan.serno;var div_id="confman_"+confMan.serno;var html="<div id='"+div_id+"'><br>"+"<button class='ctlbtn' id='"+play_id+"'>Play</button>"+"<button class='ctlbtn' id='"+stop_id+"'>Stop</button>"+"<button class='ctlbtn' id='"+recording_id+"'>Record</button>"+"<button class='ctlbtn' id='"+rec_stop_id+"'>Record Stop</button>"+
+(confMan.params.hasVid?"<button class='ctlbtn' id='"+snapshot_id+"'>PNG Snapshot</button>":"")+"<br><br></div>";jq.html(html);$.verto.modfuncs.change_video_layout=function(id,canvas_id){var val=$("#"+id+" option:selected").text();if(val!=="none"){confMan.modCommand("vid-layout",null,[val,canvas_id]);}};if(confMan.params.hasVid){for(var j=0;j<confMan.canvasCount;j++){var vlayout_id="confman_vid_layout_"+j+"_"+confMan.serno;var vlselect_id="confman_vl_select_"+j+"_"+confMan.serno;var vlhtml="<div id='"+vlayout_id+"'><br>"+"<b>Video Layout Canvas "+(j+1)+"</b> <select onChange='$.verto.modfuncs.change_video_layout(\""+vlayout_id+"\", \""+(j+1)+"\")' id='"+vlselect_id+"'></select> "+"<br><br></div>";jq.append(vlhtml);}
+$("#"+snapshot_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("vid-write-png",null,file);}});}
+$("#"+play_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("play",null,file);}});$("#"+stop_id).click(function(){confMan.modCommand("stop",null,"all");});$("#"+recording_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("recording",null,["start",file]);}});$("#"+rec_stop_id).click(function(){confMan.modCommand("recording",null,["stop","all"]);});}
+function genControls(jq,rowid){var x=parseInt(rowid);var kick_id="kick_"+x;var canvas_in_next_id="canvas_in_next_"+x;var canvas_in_prev_id="canvas_in_prev_"+x;var canvas_out_next_id="canvas_out_next_"+x;var canvas_out_prev_id="canvas_out_prev_"+x;var canvas_in_set_id="canvas_in_set_"+x;var canvas_out_set_id="canvas_out_set_"+x;var layer_set_id="layer_set_"+x;var layer_next_id="layer_next_"+x;var layer_prev_id="layer_prev_"+x;var tmute_id="tmute_"+x;var tvmute_id="tvmute_"+x;var vbanner_id="vbanner_"+x;var tvpresenter_id="tvpresenter_"+x;var tvfloor_id="tvfloor_"+x;var box_id="box_"+x;var gainup_id="gain_in_up"+x;var gaindn_id="gain_in_dn"+x;var volup_id="vol_in_up"+x;var voldn_id="vol_in_dn"+x;var transfer_id="transfer"+x;var html="<div id='"+box_id+"'>";html+="<b>General Controls</b><hr noshade>";html+="<button class='ctlbtn' id='"+kick_id+"'>Kick</button>"+"<button class='ctlbtn' id='"+tmute_id+"'>Mute</button>"+"<button class='ctlbtn' id='"+gainup_id+"'>Gain -</button>"+"<button class='ctlbtn' id='"+gaindn_id+"'>Gain +</button>"+"<button class='ctlbtn' id='"+voldn_id+"'>Vol -</button>"+"<button class='ctlbtn' id='"+volup_id+"'>Vol +</button>"+"<button class='ctlbtn' id='"+transfer_id+"'>Transfer</button>";if(confMan.params.hasVid){html+="<br><br><b>Video Controls</b><hr noshade>";html+="<button class='ctlbtn' id='"+tvmute_id+"'>VMute</button>"+"<button class='ctlbtn' id='"+tvpresenter_id+"'>Presenter</button>"+"<button class='ctlbtn' id='"+tvfloor_id+"'>Vid Floor</button>"+"<button class='ctlbtn' id='"+vbanner_id+"'>Banner</button>";if(confMan.canvasCount>1){html+="<br><br><b>Canvas Controls</b><hr noshade>"+"<button class='ctlbtn' id='"+canvas_in_set_id+"'>Set Input Canvas</button>"+"<button class='ctlbtn' id='"+canvas_in_prev_id+"'>Prev Input Canvas</button>"+"<button class='ctlbtn' id='"+canvas_in_next_id+"'>Next Input Canvas</button>"+"<br>"+"<button class='ctlbtn' id='"+canvas_out_set_id+"'>Set Watching Canvas</button>"+"<button class='ctlbtn' id='"+canvas_out_prev_id+"'>Prev Watching Canvas</button>"+"<button class='ctlbtn' id='"+canvas_out_next_id+"'>Next Watching Canvas</button>";}
+html+="<br>"+"<button class='ctlbtn' id='"+layer_set_id+"'>Set Layer</button>"+"<button class='ctlbtn' id='"+layer_prev_id+"'>Prev Layer</button>"+"<button class='ctlbtn' id='"+layer_next_id+"'>Next Layer</button>"+"</div>";}
+jq.html(html);if(!jq.data("mouse")){$("#"+box_id).hide();}
+jq.mouseover(function(e){jq.data({"mouse":true});$("#"+box_id).show();});jq.mouseout(function(e){jq.data({"mouse":false});$("#"+box_id).hide();});$("#"+transfer_id).click(function(){var xten=prompt("Enter Extension");if(xten){confMan.modCommand("transfer",x,xten);}});$("#"+kick_id).click(function(){confMan.modCommand("kick",x);});$("#"+layer_set_id).click(function(){var cid=prompt("Please enter layer ID","");if(cid){confMan.modCommand("vid-layer",x,cid);}});$("#"+layer_next_id).click(function(){confMan.modCommand("vid-layer",x,"next");});$("#"+layer_prev_id).click(function(){confMan.modCommand("vid-layer",x,"prev");});$("#"+canvas_in_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-canvas",x,cid);}});$("#"+canvas_out_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-watching-canvas",x,cid);}});$("#"+canvas_in_next_id).click(function(){confMan.modCommand("vid-canvas",x,"next");});$("#"+canvas_in_prev_id).click(function(){confMan.modCommand("vid-canvas",x,"prev");});$("#"+canvas_out_next_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"next");});$("#"+canvas_out_prev_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"prev");});$("#"+tmute_id).click(function(){confMan.modCommand("tmute",x);});if(confMan.params.hasVid){$("#"+tvmute_id).click(function(){confMan.modCommand("tvmute",x);});$("#"+tvpresenter_id).click(function(){confMan.modCommand("vid-res-id",x,"presenter");});$("#"+tvfloor_id).click(function(){confMan.modCommand("vid-floor",x,"force");});$("#"+vbanner_id).click(function(){var text=prompt("Please enter text","");if(text){confMan.modCommand("vid-banner",x,escape(text));}});}
+$("#"+gainup_id).click(function(){confMan.modCommand("volume_in",x,"up");});$("#"+gaindn_id).click(function(){confMan.modCommand("volume_in",x,"down");});$("#"+volup_id).click(function(){confMan.modCommand("volume_out",x,"up");});$("#"+voldn_id).click(function(){confMan.modCommand("volume_out",x,"down");});return html;}
+var atitle="";var awidth=0;verto.subscribe(confMan.params.laData.infoChannel,{handler:function(v,e){if(typeof(confMan.params.infoCallback)==="function"){confMan.params.infoCallback(v,e);}}});verto.subscribe(confMan.params.laData.chatChannel,{handler:function(v,e){if(typeof(confMan.params.chatCallback)==="function"){confMan.params.chatCallback(v,e);}}});if(confMan.params.laData.role==="moderator"){atitle="Action";awidth=600;if(confMan.params.mainModID){genMainMod($(confMan.params.mainModID));$(confMan.params.displayID).html("Moderator Controls Ready<br><br>");}else{$(confMan.params.mainModID).html("");}
+verto.subscribe(confMan.params.laData.modChannel,{handler:function(v,e){if(confMan.params.onBroadcast){confMan.params.onBroadcast(verto,confMan,e.data);}
+if(e.data["conf-command"]==="list-videoLayouts"){for(var j=0;j<confMan.canvasCount;j++){var vlselect_id="#confman_vl_select_"+j+"_"+confMan.serno;var vlayout_id="#confman_vid_layout_"+j+"_"+confMan.serno;var x=0;var options;$(vlselect_id).selectmenu({});$(vlselect_id).selectmenu("enable");$(vlselect_id).empty();$(vlselect_id).append(new Option("Choose a Layout","none"));if(e.data.responseData){var rdata=[];for(var i in e.data.responseData){rdata.push(e.data.responseData[i].name);}
+options=rdata.sort(function(a,b){var ga=a.substring(0,6)=="group:"?true:false;var gb=b.substring(0,6)=="group:"?true:false;if((ga||gb)&&ga!=gb){return ga?-1:1;}
+return((a==b)?0:((a>b)?1:-1));});for(var i in options){$(vlselect_id).append(new Option(options[i],options[i]));x++;}}
+if(x){$(vlselect_id).selectmenu('refresh',true);}else{$(vlayout_id).hide();}}}else{if(!confMan.destroyed&&confMan.params.displayID){$(confMan.params.displayID).html(e.data.response+"<br><br>");if(confMan.lastTimeout){clearTimeout(confMan.lastTimeout);confMan.lastTimeout=0;}
+confMan.lastTimeout=setTimeout(function(){$(confMan.params.displayID).html(confMan.destroyed?"":"Moderator Controls Ready<br><br>");},4000);}}}});if(confMan.params.hasVid){confMan.modCommand("list-videoLayouts",null,null);}}
+var row_callback=null;if(confMan.params.laData.role==="moderator"){row_callback=function(nRow,aData,iDisplayIndex,iDisplayIndexFull){if(!aData[5]){var $row=$('td:eq(5)',nRow);genControls($row,aData);if(confMan.params.onLaRow){confMan.params.onLaRow(verto,confMan,$row,aData);}}};}
+confMan.lt=new $.verto.liveTable(verto,confMan.params.laData.laChannel,confMan.params.laData.laName,$(confMan.params.tableID),{subParams:{callID:confMan.params.dialog?confMan.params.dialog.callID:null},"onChange":function(obj,args){$(confMan.params.statusID).text("Conference Members: "+" ("+obj.arrayLen()+" Total)");if(confMan.params.onLaChange){confMan.params.onLaChange(verto,confMan,$.verto.enum.confEvent.laChange,obj,args);}},"aaData":[],"aoColumns":[{"sTitle":"ID","sWidth":"50"},{"sTitle":"Number","sWidth":"250"},{"sTitle":"Name","sWidth":"250"},{"sTitle":"Codec","sWidth":"100"},{"sTitle":"Status","sWidth":confMan.params.hasVid?"200px":"150px"},{"sTitle":atitle,"sWidth":awidth,}],"bAutoWidth":true,"bDestroy":true,"bSort":false,"bInfo":false,"bFilter":false,"bLengthChange":false,"bPaginate":false,"iDisplayLength":1400,"oLanguage":{"sEmptyTable":"The Conference is Empty....."},"fnRowCallback":row_callback});};$.verto.confMan.prototype.modCommand=function(cmd,id,value){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.confMan.prototype.sendChat=function(message,type){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};$.verto.confMan.prototype.destroy=function(){var confMan=this;confMan.destroyed=true;if(confMan.lt){confMan.lt.destroy();}
+if(confMan.params.laData.chatChannel){confMan.verto.unsubscribe(confMan.params.laData.chatChannel);}
+if(confMan.params.laData.modChannel){confMan.verto.unsubscribe(confMan.params.laData.modChannel);}
+if(confMan.params.mainModID){$(confMan.params.mainModID).html("");}};$.verto.dialog=function(direction,verto,params){var dialog=this;dialog.params=$.extend({useVideo:verto.options.useVideo,useStereo:verto.options.useStereo,screenShare:false,useCamera:false,useMic:verto.options.deviceParams.useMic,useMicLabel:verto.options.deviceParams.useMicLabel,useSpeak:verto.options.deviceParams.useSpeak,tag:verto.options.tag,localTag:verto.options.localTag,login:verto.options.login,videoParams:verto.options.videoParams,useStream:verto.options.useStream,},params);if(!dialog.params.screenShare){dialog.params.useCamera=verto.options.deviceParams.useCamera;dialog.params.useCameraLabel=verto.options.deviceParams.useCameraLabel;}
+dialog.verto=verto;dialog.direction=direction;dialog.lastState=null;dialog.state=dialog.lastState=$.verto.enum.state.new;dialog.callbacks=verto.callbacks;dialog.answered=false;dialog.attach=params.attach||false;dialog.screenShare=params.screenShare||false;dialog.useCamera=dialog.params.useCamera;dialog.useCameraLabel=dialog.params.useCameraLabel;dialog.useMic=dialog.params.useMic;dialog.useMicLabel=dialog.params.useMicLabel;dialog.useSpeak=dialog.params.useSpeak;if(dialog.params.callID){dialog.callID=dialog.params.callID;}else{dialog.callID=dialog.params.callID=generateGUID();}
+if(typeof(dialog.params.tag)==="function"){dialog.params.tag=dialog.params.tag();}
+if(dialog.params.tag){dialog.audioStream=document.getElementById(dialog.params.tag);if(dialog.params.useVideo){dialog.videoStream=dialog.audioStream;}}
+if(dialog.params.localTag){dialog.localVideo=document.getElementById(dialog.params.localTag);}
+dialog.verto.dialogs[dialog.callID]=dialog;var RTCcallbacks={};if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.params.display_direction==="outbound"){dialog.params.remote_caller_id_name=dialog.params.caller_id_name;dialog.params.remote_caller_id_number=dialog.params.caller_id_number;}else{dialog.params.remote_caller_id_name=dialog.params.callee_id_name;dialog.params.remote_caller_id_number=dialog.params.callee_id_number;}
+if(!dialog.params.remote_caller_id_name){dialog.params.remote_caller_id_name="Nobody";}
+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){if(dialog.callbacks.permissionCallback&&typeof dialog.callbacks.permissionCallback.onGranted==='function'){dialog.callbacks.permissionCallback.onGranted(stream);}
+else if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onGranted==='function'){dialog.verto.options.permissionCallback.onGranted(stream);}
+console.log("stream started");};RTCcallbacks.onRemoteStream=function(rtc,stream){if(typeof dialog.callbacks.onRemoteStream==='function'){dialog.callbacks.onRemoteStream(stream,dialog);}
+console.log("remote stream started");};RTCcallbacks.onError=function(e){if(dialog.callbacks.permissionCallback&&typeof dialog.callbacks.permissionCallback.onDenied==='function'){dialog.callbacks.permissionCallback.onDenied();}
+else 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,useCameraLabel:dialog.useCameraLabel,useMic:dialog.useMic,useMicLabel:dialog.useMicLabel,useSpeak:dialog.useSpeak,turnServer:verto.options.turnServer,useStream:dialog.params.useStream});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((obj.noDialogParams&&i!="callID")){continue;}
+obj.dialogParams[i]=dialog.params[i];}
+delete obj.noDialogParams;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;}
+function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}}
+return id;}
+$.verto.dialog.prototype.setAudioPlaybackDevice=function(sinkId,callback,arg){var dialog=this;var element=dialog.audioStream;if(typeof element.sinkId!=='undefined'){var devname=find_name(sinkId);console.info("Dialog: "+dialog.callID+" Setting speaker:",element,devname);element.setSinkId(sinkId).then(function(){console.log("Dialog: "+dialog.callID+' Success, audio output device attached: '+sinkId);if(callback){callback(true,devname,arg);}}).catch(function(error){var errorMessage=error;if(error.name==='SecurityError'){errorMessage="Dialog: "+dialog.callID+' You need to use HTTPS for selecting audio output '+'device: '+error;}
+if(callback){callback(false,null,arg);}
+console.error(errorMessage);});}else{console.warn("Dialog: "+dialog.callID+' Browser does not support output device selection.');if(callback){callback(false,null,arg);}}}
+$.verto.dialog.prototype.setState=function(state){var dialog=this;if(dialog.state==$.verto.enum.state.ringing){dialog.stopRinging();}
+if(dialog.state==state||!checkStateChange(dialog.state,state)){console.error("Dialog "+dialog.callID+": INVALID state change from "+dialog.state.name+" to "+state.name);dialog.hangup();return false;}
+console.log("Dialog "+dialog.callID+": state change from "+dialog.state.name+" to "+state.name);dialog.lastState=dialog.state;dialog.state=state;if(dialog.callbacks.onDialogState){dialog.callbacks.onDialogState(this);}
+switch(dialog.state){case $.verto.enum.state.early:case $.verto.enum.state.active:var speaker=dialog.useSpeak;console.info("Using Speaker: ",speaker);if(speaker&&speaker!=="any"&&speaker!=="none"){setTimeout(function(){dialog.setAudioPlaybackDevice(speaker);},500);}
+break;case $.verto.enum.state.trying:setTimeout(function(){if(dialog.state==$.verto.enum.state.trying){dialog.setState($.verto.enum.state.hangup);}},30000);break;case $.verto.enum.state.purge:dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.hangup:if(dialog.lastState.val>$.verto.enum.state.requesting.val&&dialog.lastState.val<$.verto.enum.state.hangup.val){dialog.sendMethod("verto.bye",{});}
+dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.destroy:if(typeof(dialog.verto.options.tag)==="function"){$('#'+dialog.params.tag).remove();}
+delete dialog.verto.dialogs[dialog.callID];if(dialog.params.screenShare){dialog.rtc.stopPeer();}else{dialog.rtc.stop();}
+break;}
+return true;};$.verto.dialog.prototype.processReply=function(method,success,e){var dialog=this;switch(method){case"verto.answer":case"verto.attach":if(success){dialog.setState($.verto.enum.state.active);}else{dialog.hangup();}
+break;case"verto.invite":if(success){dialog.setState($.verto.enum.state.trying);}else{dialog.setState($.verto.enum.state.destroy);}
+break;case"verto.bye":dialog.hangup();break;case"verto.modify":if(e.holdState){if(e.holdState=="held"){if(dialog.state!=$.verto.enum.state.held){dialog.setState($.verto.enum.state.held);}}else if(e.holdState=="active"){if(dialog.state!=$.verto.enum.state.active){dialog.setState($.verto.enum.state.active);}}}
+if(success){}
+break;default:break;}};$.verto.dialog.prototype.hangup=function(params){var dialog=this;if(params){if(params.causeCode){dialog.causeCode=params.causeCode;}
+if(params.cause){dialog.cause=params.cause;}}
+if(!dialog.cause&&!dialog.causeCode){dialog.cause="NORMAL_CLEARING";}
+if(dialog.state.val>=$.verto.enum.state.new.val&&dialog.state.val<$.verto.enum.state.hangup.val){dialog.setState($.verto.enum.state.hangup);}else if(dialog.state.val<$.verto.enum.state.destroy){dialog.setState($.verto.enum.state.destroy);}};$.verto.dialog.prototype.stopRinging=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.stop();}};$.verto.dialog.prototype.indicateRing=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.attr("src",dialog.verto.options.ringFile)[0].play();setTimeout(function(){dialog.stopRinging();if(dialog.state==$.verto.enum.state.ringing){dialog.indicateRing();}},dialog.verto.options.ringSleep);}};$.verto.dialog.prototype.ring=function(){var dialog=this;dialog.setState($.verto.enum.state.ringing);dialog.indicateRing();};$.verto.dialog.prototype.useVideo=function(on){var dialog=this;dialog.params.useVideo=on;if(on){dialog.videoStream=dialog.audioStream;}else{dialog.videoStream=null;}
+dialog.rtc.useVideo(dialog.videoStream,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.verto.dialog.prototype.useStereo=function(on){var dialog=this;dialog.params.useStereo=on;dialog.rtc.useStereo(on);};$.verto.dialog.prototype.dtmf=function(digits){var dialog=this;if(digits){dialog.sendMethod("verto.info",{dtmf:digits});}};$.verto.dialog.prototype.rtt=function(obj){var dialog=this;var pobj={};if(!obj){return false;}
+pobj.code=obj.code;pobj.chars=obj.chars;if(pobj.chars||pobj.code){dialog.sendMethod("verto.info",{txt:obj,noDialogParams:true});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.replace=function(replaceCallID,params){var dialog=this;if(replaceCallID){dialog.sendMethod("verto.modify",{action:"replace",replaceCallID:replaceCallID,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;if(!msg.to){console.error("Missing To");err++;}
+if(!msg.body){console.error("Missing Body");err++;}
+if(err){return false;}
+dialog.sendMethod("verto.info",{msg:msg});return true;};$.verto.dialog.prototype.answer=function(params){var dialog=this;if(!dialog.answered){if(!params){params={};}
+params.sdp=dialog.params.sdp;if(params){if(params.useVideo){dialog.useVideo(true);}
+dialog.params.callee_id_name=params.callee_id_name;dialog.params.callee_id_number=params.callee_id_number;if(params.useCamera){dialog.useCamera=params.useCamera;dialog.useCameraLabel=params.useCameraLabel;}
+if(params.useMic){dialog.useMic=params.useMic;dialog.useMic=params.useMicLabel;}
+if(params.useSpeak){dialog.useSpeak=params.useSpeak;}}
+dialog.rtc.createAnswer(params);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(params){var dialog=this;dialog.gotAnswer=true;if(dialog.state.val>=$.verto.enum.state.active.val){return;}
+if(dialog.state.val>=$.verto.enum.state.early.val){dialog.setState($.verto.enum.state.active);}else{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" &lt;":" <")+dialog.params.remote_caller_id_number+(enc?"&gt;":">");return party;};$.verto.dialog.prototype.sendMessage=function(msg,params){var dialog=this;if(dialog.callbacks.onMessage){dialog.callbacks.onMessage(dialog.verto,dialog,msg,params);}};$.verto.dialog.prototype.handleInfo=function(params){var dialog=this;dialog.sendMessage($.verto.enum.message.info,params);};$.verto.dialog.prototype.handleDisplay=function(params){var dialog=this;if(params.display_name){dialog.params.remote_caller_id_name=params.display_name;}
+if(params.display_number){dialog.params.remote_caller_id_number=params.display_number;}
+dialog.sendMessage($.verto.enum.message.display,{});};$.verto.dialog.prototype.handleMedia=function(params){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;}
+dialog.gotEarly=true;dialog.rtc.answer(params.sdp,function(){console.log("Dialog "+dialog.callID+"Establishing early media");dialog.setState($.verto.enum.state.early);if(dialog.gotAnswer){console.log("Dialog "+dialog.callID+"Answering Channel");dialog.setState($.verto.enum.state.active);}},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"EARLY SDP",params.sdp);};$.verto.ENUM=function(s){var i=0,o={};s.split(" ").map(function(x){o[x]={name:x,val:i++};});return Object.freeze(o);};$.verto.enum={};$.verto.enum.states=Object.freeze({new:{requesting:1,recovering:1,ringing:1,destroy:1,answering:1,hangup:1},requesting:{trying:1,hangup:1,active:1},recovering:{answering:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{answering:1,requesting:1,hangup:1,held:1},held:{hangup:1,active:1},early:{hangup:1,active:1},hangup:{destroy:1},destroy:{},purge:{destroy:1}});$.verto.enum.state=$.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent clientReady");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$.verto.unloadJobs=[];var unloadEventName='beforeunload';var iOS=['iPad','iPhone','iPod'].indexOf(navigator.platform)>=0;if(iOS){unloadEventName='pagehide';}
+$(window).bind(unloadEventName,function(){for(var f in $.verto.unloadJobs){$.verto.unloadJobs[f]();}
+if($.verto.haltClosure)
+return $.verto.haltClosure();for(var i in $.verto.saved){var verto=$.verto.saved[i];if(verto){verto.purge();verto.logout();}}
+return $.verto.warnOnUnload;});$.verto.videoDevices=[];$.verto.audioInDevices=[];$.verto.audioOutDevices=[];var checkDevices=function(runtime){console.info("enumerating devices");var aud_in=[],aud_out=[],vid=[];var has_video=0,has_audio=0;var Xstream;function gotDevices(deviceInfos){for(var i=0;i!==deviceInfos.length;++i){var deviceInfo=deviceInfos[i];var text="";console.log(deviceInfo);console.log(deviceInfo.kind+": "+deviceInfo.label+" id = "+deviceInfo.deviceId);if(deviceInfo.kind==='audioinput'){text=deviceInfo.label||'microphone '+(aud_in.length+1);aud_in.push({id:deviceInfo.deviceId,kind:"audio_in",label:text});}else if(deviceInfo.kind==='audiooutput'){text=deviceInfo.label||'speaker '+(aud_out.length+1);aud_out.push({id:deviceInfo.deviceId,kind:"audio_out",label:text});}else if(deviceInfo.kind==='videoinput'){text=deviceInfo.label||'camera '+(vid.length+1);vid.push({id:deviceInfo.deviceId,kind:"video",label:text});}else{console.log('Some other kind of source/device: ',deviceInfo);}}
+$.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);if(Xstream){Xstream.getTracks().forEach(function(track){track.stop();});}
+if(runtime){runtime(true);}}
+function handleError(error){console.log('device enumeration error: ',error);if(runtime)runtime(false);}
+function checkTypes(devs){for(var i=0;i!==devs.length;++i){if(devs[i].kind==='audioinput'){has_audio++;}else if(devs[i].kind==='videoinput'){has_video++;}}
+navigator.getUserMedia({audio:(has_audio>0?true:false),video:(has_video>0?true:false)},function(stream){Xstream=stream;navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);},function(err){console.log("The following error occurred: "+err.name);});}
+navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);};$.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);(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(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){'use strict';var _adapter_factory=require('./adapter_factory.js');var adapter=(0,_adapter_factory.adapterFactory)({window:window});module.exports=adapter;},{"./adapter_factory.js":2}],2:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.adapterFactory=adapterFactory;var _utils=require('./utils');var utils=_interopRequireWildcard(_utils);var _chrome_shim=require('./chrome/chrome_shim');var chromeShim=_interopRequireWildcard(_chrome_shim);var _edge_shim=require('./edge/edge_shim');var edgeShim=_interopRequireWildcard(_edge_shim);var _firefox_shim=require('./firefox/firefox_shim');var firefoxShim=_interopRequireWildcard(_firefox_shim);var _safari_shim=require('./safari/safari_shim');var safariShim=_interopRequireWildcard(_safari_shim);var _common_shim=require('./common_shim');var commonShim=_interopRequireWildcard(_common_shim);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function adapterFactory(){var _ref=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{},window=_ref.window;var options=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{shimChrome:true,shimFirefox:true,shimEdge:true,shimSafari:true};var logging=utils.log;var browserDetails=utils.detectBrowser(window);var adapter={browserDetails:browserDetails,commonShim:commonShim,extractVersion:utils.extractVersion,disableLog:utils.disableLog,disableWarnings:utils.disableWarnings};switch(browserDetails.browser){case'chrome':if(!chromeShim||!chromeShim.shimPeerConnection||!options.shimChrome){logging('Chrome shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming chrome.');adapter.browserShim=chromeShim;chromeShim.shimGetUserMedia(window);chromeShim.shimMediaStream(window);chromeShim.shimPeerConnection(window);chromeShim.shimOnTrack(window);chromeShim.shimAddTrackRemoveTrack(window);chromeShim.shimGetSendersWithDtmf(window);chromeShim.shimSenderReceiverGetStats(window);chromeShim.fixNegotiationNeeded(window);commonShim.shimRTCIceCandidate(window);commonShim.shimConnectionState(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);commonShim.removeAllowExtmapMixed(window);break;case'firefox':if(!firefoxShim||!firefoxShim.shimPeerConnection||!options.shimFirefox){logging('Firefox shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming firefox.');adapter.browserShim=firefoxShim;firefoxShim.shimGetUserMedia(window);firefoxShim.shimPeerConnection(window);firefoxShim.shimOnTrack(window);firefoxShim.shimRemoveStream(window);firefoxShim.shimSenderGetStats(window);firefoxShim.shimReceiverGetStats(window);firefoxShim.shimRTCDataChannel(window);commonShim.shimRTCIceCandidate(window);commonShim.shimConnectionState(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);break;case'edge':if(!edgeShim||!edgeShim.shimPeerConnection||!options.shimEdge){logging('MS edge shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming edge.');adapter.browserShim=edgeShim;edgeShim.shimGetUserMedia(window);edgeShim.shimGetDisplayMedia(window);edgeShim.shimPeerConnection(window);edgeShim.shimReplaceTrack(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);break;case'safari':if(!safariShim||!options.shimSafari){logging('Safari shim is not included in this adapter release.');return adapter;}
+logging('adapter.js shimming safari.');adapter.browserShim=safariShim;safariShim.shimRTCIceServerUrls(window);safariShim.shimCreateOfferLegacy(window);safariShim.shimCallbacksAPI(window);safariShim.shimLocalStreamsAPI(window);safariShim.shimRemoteStreamsAPI(window);safariShim.shimTrackEventTransceiver(window);safariShim.shimGetUserMedia(window);commonShim.shimRTCIceCandidate(window);commonShim.shimMaxMessageSize(window);commonShim.shimSendThrowTypeError(window);commonShim.removeAllowExtmapMixed(window);break;default:logging('Unsupported browser!');break;}
+return adapter;}},{"./chrome/chrome_shim":3,"./common_shim":6,"./edge/edge_shim":7,"./firefox/firefox_shim":11,"./safari/safari_shim":14,"./utils":15}],3:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=exports.shimGetUserMedia=undefined;var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};var _getusermedia=require('./getusermedia');Object.defineProperty(exports,'shimGetUserMedia',{enumerable:true,get:function get(){return _getusermedia.shimGetUserMedia;}});var _getdisplaymedia=require('./getdisplaymedia');Object.defineProperty(exports,'shimGetDisplayMedia',{enumerable:true,get:function get(){return _getdisplaymedia.shimGetDisplayMedia;}});exports.shimMediaStream=shimMediaStream;exports.shimOnTrack=shimOnTrack;exports.shimGetSendersWithDtmf=shimGetSendersWithDtmf;exports.shimSenderReceiverGetStats=shimSenderReceiverGetStats;exports.shimAddTrackRemoveTrackWithNative=shimAddTrackRemoveTrackWithNative;exports.shimAddTrackRemoveTrack=shimAddTrackRemoveTrack;exports.shimPeerConnection=shimPeerConnection;exports.fixNegotiationNeeded=fixNegotiationNeeded;var _utils=require('../utils.js');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function walkStats(stats,base,resultSet){if(!base||resultSet.has(base.id)){return;}
+resultSet.set(base.id,base);Object.keys(base).forEach(function(name){if(name.endsWith('Id')){walkStats(stats,stats.get(base[name]),resultSet);}else if(name.endsWith('Ids')){base[name].forEach(function(id){walkStats(stats,stats.get(id),resultSet);});}});}
+function filterStats(result,track,outbound){var streamStatsType=outbound?'outbound-rtp':'inbound-rtp';var filteredResult=new Map();if(track===null){return filteredResult;}
+var trackStats=[];result.forEach(function(value){if(value.type==='track'&&value.trackIdentifier===track.id){trackStats.push(value);}});trackStats.forEach(function(trackStat){result.forEach(function(stats){if(stats.type===streamStatsType&&stats.trackId===trackStat.id){walkStats(result,stats,filteredResult);}});});return filteredResult;}
+function shimMediaStream(window){window.MediaStream=window.MediaStream||window.webkitMediaStream;}
+function shimOnTrack(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&!('ontrack'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function get(){return this._ontrack;},set:function set(f){if(this._ontrack){this.removeEventListener('track',this._ontrack);}
+this.addEventListener('track',this._ontrack=f);},enumerable:true,configurable:true});var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){var _this=this;if(!this._ontrackpoly){this._ontrackpoly=function(e){e.stream.addEventListener('addtrack',function(te){var receiver=void 0;if(window.RTCPeerConnection.prototype.getReceivers){receiver=_this.getReceivers().find(function(r){return r.track&&r.track.id===te.track.id;});}else{receiver={track:te.track};}
+var event=new Event('track');event.track=te.track;event.receiver=receiver;event.transceiver={receiver:receiver};event.streams=[e.stream];_this.dispatchEvent(event);});e.stream.getTracks().forEach(function(track){var receiver=void 0;if(window.RTCPeerConnection.prototype.getReceivers){receiver=_this.getReceivers().find(function(r){return r.track&&r.track.id===track.id;});}else{receiver={track:track};}
+var event=new Event('track');event.track=track;event.receiver=receiver;event.transceiver={receiver:receiver};event.streams=[e.stream];_this.dispatchEvent(event);});};this.addEventListener('addstream',this._ontrackpoly);}
+return origSetRemoteDescription.apply(this,arguments);};}else{utils.wrapPeerConnectionEvent(window,'track',function(e){if(!e.transceiver){Object.defineProperty(e,'transceiver',{value:{receiver:e.receiver}});}
+return e;});}}
+function shimGetSendersWithDtmf(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&!('getSenders'in window.RTCPeerConnection.prototype)&&'createDTMFSender'in window.RTCPeerConnection.prototype){var shimSenderWithDtmf=function shimSenderWithDtmf(pc,track){return{track:track,get dtmf(){if(this._dtmf===undefined){if(track.kind==='audio'){this._dtmf=pc.createDTMFSender(track);}else{this._dtmf=null;}}
+return this._dtmf;},_pc:pc};};if(!window.RTCPeerConnection.prototype.getSenders){window.RTCPeerConnection.prototype.getSenders=function(){this._senders=this._senders||[];return this._senders.slice();};var origAddTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addTrack=function(track,stream){var sender=origAddTrack.apply(this,arguments);if(!sender){sender=shimSenderWithDtmf(this,track);this._senders.push(sender);}
+return sender;};var origRemoveTrack=window.RTCPeerConnection.prototype.removeTrack;window.RTCPeerConnection.prototype.removeTrack=function(sender){origRemoveTrack.apply(this,arguments);var idx=this._senders.indexOf(sender);if(idx!==-1){this._senders.splice(idx,1);}};}
+var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var _this2=this;this._senders=this._senders||[];origAddStream.apply(this,[stream]);stream.getTracks().forEach(function(track){_this2._senders.push(shimSenderWithDtmf(_this2,track));});};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){var _this3=this;this._senders=this._senders||[];origRemoveStream.apply(this,[stream]);stream.getTracks().forEach(function(track){var sender=_this3._senders.find(function(s){return s.track===track;});if(sender){_this3._senders.splice(_this3._senders.indexOf(sender),1);}});};}else if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&'getSenders'in window.RTCPeerConnection.prototype&&'createDTMFSender'in window.RTCPeerConnection.prototype&&window.RTCRtpSender&&!('dtmf'in window.RTCRtpSender.prototype)){var origGetSenders=window.RTCPeerConnection.prototype.getSenders;window.RTCPeerConnection.prototype.getSenders=function(){var _this4=this;var senders=origGetSenders.apply(this,[]);senders.forEach(function(sender){return sender._pc=_this4;});return senders;};Object.defineProperty(window.RTCRtpSender.prototype,'dtmf',{get:function get(){if(this._dtmf===undefined){if(this.track.kind==='audio'){this._dtmf=this._pc.createDTMFSender(this.track);}else{this._dtmf=null;}}
+return this._dtmf;}});}}
+function shimSenderReceiverGetStats(window){if(!((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&window.RTCRtpSender&&window.RTCRtpReceiver)){return;}
+if(!('getStats'in window.RTCRtpSender.prototype)){var origGetSenders=window.RTCPeerConnection.prototype.getSenders;if(origGetSenders){window.RTCPeerConnection.prototype.getSenders=function(){var _this5=this;var senders=origGetSenders.apply(this,[]);senders.forEach(function(sender){return sender._pc=_this5;});return senders;};}
+var origAddTrack=window.RTCPeerConnection.prototype.addTrack;if(origAddTrack){window.RTCPeerConnection.prototype.addTrack=function(){var sender=origAddTrack.apply(this,arguments);sender._pc=this;return sender;};}
+window.RTCRtpSender.prototype.getStats=function(){var sender=this;return this._pc.getStats().then(function(result){return(filterStats(result,sender.track,true));});};}
+if(!('getStats'in window.RTCRtpReceiver.prototype)){var origGetReceivers=window.RTCPeerConnection.prototype.getReceivers;if(origGetReceivers){window.RTCPeerConnection.prototype.getReceivers=function(){var _this6=this;var receivers=origGetReceivers.apply(this,[]);receivers.forEach(function(receiver){return receiver._pc=_this6;});return receivers;};}
+utils.wrapPeerConnectionEvent(window,'track',function(e){e.receiver._pc=e.srcElement;return e;});window.RTCRtpReceiver.prototype.getStats=function(){var receiver=this;return this._pc.getStats().then(function(result){return filterStats(result,receiver.track,false);});};}
+if(!('getStats'in window.RTCRtpSender.prototype&&'getStats'in window.RTCRtpReceiver.prototype)){return;}
+var origGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(){if(arguments.length>0&&arguments[0]instanceof window.MediaStreamTrack){var track=arguments[0];var sender=void 0;var receiver=void 0;var err=void 0;this.getSenders().forEach(function(s){if(s.track===track){if(sender){err=true;}else{sender=s;}}});this.getReceivers().forEach(function(r){if(r.track===track){if(receiver){err=true;}else{receiver=r;}}
+return r.track===track;});if(err||sender&&receiver){return Promise.reject(new DOMException('There are more than one sender or receiver for the track.','InvalidAccessError'));}else if(sender){return sender.getStats();}else if(receiver){return receiver.getStats();}
+return Promise.reject(new DOMException('There is no sender or receiver for the track.','InvalidAccessError'));}
+return origGetStats.apply(this,arguments);};}
+function shimAddTrackRemoveTrackWithNative(window){window.RTCPeerConnection.prototype.getLocalStreams=function(){var _this7=this;this._shimmedLocalStreams=this._shimmedLocalStreams||{};return Object.keys(this._shimmedLocalStreams).map(function(streamId){return _this7._shimmedLocalStreams[streamId][0];});};var origAddTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(!stream){return origAddTrack.apply(this,arguments);}
+this._shimmedLocalStreams=this._shimmedLocalStreams||{};var sender=origAddTrack.apply(this,arguments);if(!this._shimmedLocalStreams[stream.id]){this._shimmedLocalStreams[stream.id]=[stream,sender];}else if(this._shimmedLocalStreams[stream.id].indexOf(sender)===-1){this._shimmedLocalStreams[stream.id].push(sender);}
+return sender;};var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var _this8=this;this._shimmedLocalStreams=this._shimmedLocalStreams||{};stream.getTracks().forEach(function(track){var alreadyExists=_this8.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}});var existingSenders=this.getSenders();origAddStream.apply(this,arguments);var newSenders=this.getSenders().filter(function(newSender){return existingSenders.indexOf(newSender)===-1;});this._shimmedLocalStreams[stream.id]=[stream].concat(newSenders);};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){this._shimmedLocalStreams=this._shimmedLocalStreams||{};delete this._shimmedLocalStreams[stream.id];return origRemoveStream.apply(this,arguments);};var origRemoveTrack=window.RTCPeerConnection.prototype.removeTrack;window.RTCPeerConnection.prototype.removeTrack=function(sender){var _this9=this;this._shimmedLocalStreams=this._shimmedLocalStreams||{};if(sender){Object.keys(this._shimmedLocalStreams).forEach(function(streamId){var idx=_this9._shimmedLocalStreams[streamId].indexOf(sender);if(idx!==-1){_this9._shimmedLocalStreams[streamId].splice(idx,1);}
+if(_this9._shimmedLocalStreams[streamId].length===1){delete _this9._shimmedLocalStreams[streamId];}});}
+return origRemoveTrack.apply(this,arguments);};}
+function shimAddTrackRemoveTrack(window){if(!window.RTCPeerConnection){return;}
+var browserDetails=utils.detectBrowser(window);if(window.RTCPeerConnection.prototype.addTrack&&browserDetails.version>=65){return shimAddTrackRemoveTrackWithNative(window);}
+var origGetLocalStreams=window.RTCPeerConnection.prototype.getLocalStreams;window.RTCPeerConnection.prototype.getLocalStreams=function(){var _this10=this;var nativeStreams=origGetLocalStreams.apply(this);this._reverseStreams=this._reverseStreams||{};return nativeStreams.map(function(stream){return _this10._reverseStreams[stream.id];});};var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var _this11=this;this._streams=this._streams||{};this._reverseStreams=this._reverseStreams||{};stream.getTracks().forEach(function(track){var alreadyExists=_this11.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}});if(!this._reverseStreams[stream.id]){var newStream=new window.MediaStream(stream.getTracks());this._streams[stream.id]=newStream;this._reverseStreams[newStream.id]=stream;stream=newStream;}
+origAddStream.apply(this,[stream]);};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){this._streams=this._streams||{};this._reverseStreams=this._reverseStreams||{};origRemoveStream.apply(this,[this._streams[stream.id]||stream]);delete this._reverseStreams[this._streams[stream.id]?this._streams[stream.id].id:stream.id];delete this._streams[stream.id];};window.RTCPeerConnection.prototype.addTrack=function(track,stream){var _this12=this;if(this.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');}
+var streams=[].slice.call(arguments,1);if(streams.length!==1||!streams[0].getTracks().find(function(t){return t===track;})){throw new DOMException('The adapter.js addTrack polyfill only supports a single '+' stream which is associated with the specified track.','NotSupportedError');}
+var alreadyExists=this.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}
+this._streams=this._streams||{};this._reverseStreams=this._reverseStreams||{};var oldStream=this._streams[stream.id];if(oldStream){oldStream.addTrack(track);Promise.resolve().then(function(){_this12.dispatchEvent(new Event('negotiationneeded'));});}else{var newStream=new window.MediaStream([track]);this._streams[stream.id]=newStream;this._reverseStreams[newStream.id]=stream;this.addStream(newStream);}
+return this.getSenders().find(function(s){return s.track===track;});};function replaceInternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(internalStream.id,'g'),externalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});}
+function replaceExternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(externalStream.id,'g'),internalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});}
+['createOffer','createAnswer'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var _this13=this;var args=arguments;var isLegacyCall=arguments.length&&typeof arguments[0]==='function';if(isLegacyCall){return nativeMethod.apply(this,[function(description){var desc=replaceInternalStreamId(_this13,description);args[0].apply(null,[desc]);},function(err){if(args[1]){args[1].apply(null,err);}},arguments[2]]);}
+return nativeMethod.apply(this,arguments).then(function(description){return replaceInternalStreamId(_this13,description);});};});var origSetLocalDescription=window.RTCPeerConnection.prototype.setLocalDescription;window.RTCPeerConnection.prototype.setLocalDescription=function(){if(!arguments.length||!arguments[0].type){return origSetLocalDescription.apply(this,arguments);}
+arguments[0]=replaceExternalStreamId(this,arguments[0]);return origSetLocalDescription.apply(this,arguments);};var origLocalDescription=Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype,'localDescription');Object.defineProperty(window.RTCPeerConnection.prototype,'localDescription',{get:function get(){var description=origLocalDescription.get.apply(this);if(description.type===''){return description;}
+return replaceInternalStreamId(this,description);}});window.RTCPeerConnection.prototype.removeTrack=function(sender){var _this14=this;if(this.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');}
+if(!sender._pc){throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.','TypeError');}
+var isLocal=sender._pc===this;if(!isLocal){throw new DOMException('Sender was not created by this connection.','InvalidAccessError');}
+this._streams=this._streams||{};var stream=void 0;Object.keys(this._streams).forEach(function(streamid){var hasTrack=_this14._streams[streamid].getTracks().find(function(track){return sender.track===track;});if(hasTrack){stream=_this14._streams[streamid];}});if(stream){if(stream.getTracks().length===1){this.removeStream(this._reverseStreams[stream.id]);}else{stream.removeTrack(sender.track);}
+this.dispatchEvent(new Event('negotiationneeded'));}};}
+function shimPeerConnection(window){if(!window.RTCPeerConnection&&window.webkitRTCPeerConnection){window.RTCPeerConnection=window.webkitRTCPeerConnection;}
+if(!window.RTCPeerConnection){return;}
+var origGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(selector,successCallback,errorCallback){var _this15=this;var args=arguments;if(arguments.length>0&&typeof selector==='function'){return origGetStats.apply(this,arguments);}
+if(origGetStats.length===0&&(arguments.length===0||typeof arguments[0]!=='function')){return origGetStats.apply(this,[]);}
+var fixChromeStats_=function fixChromeStats_(response){var standardReport={};var reports=response.result();reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:{localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[report.type]||report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name);});standardReport[standardStats.id]=standardStats;});return standardReport;};var makeMapStats=function makeMapStats(stats){return new Map(Object.keys(stats).map(function(key){return[key,stats[key]];}));};if(arguments.length>=2){var successCallbackWrapper_=function successCallbackWrapper_(response){args[1](makeMapStats(fixChromeStats_(response)));};return origGetStats.apply(this,[successCallbackWrapper_,arguments[0]]);}
+return new Promise(function(resolve,reject){origGetStats.apply(_this15,[function(response){resolve(makeMapStats(fixChromeStats_(response)));},reject]);}).then(successCallback,errorCallback);};['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new(method==='addIceCandidate'?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);}
+return Promise.resolve();}
+return nativeAddIceCandidate.apply(this,arguments);};}
+function fixNegotiationNeeded(window){utils.wrapPeerConnectionEvent(window,'negotiationneeded',function(e){var pc=e.target;if(pc.signalingState!=='stable'){return;}
+return e;});}},{"../utils.js":15,"./getdisplaymedia":4,"./getusermedia":5}],4:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=shimGetDisplayMedia;function shimGetDisplayMedia(window,getSourceId){if(window.navigator.mediaDevices&&'getDisplayMedia'in window.navigator.mediaDevices){return;}
+if(!window.navigator.mediaDevices){return;}
+if(typeof getSourceId!=='function'){console.error('shimGetDisplayMedia: getSourceId argument is not '+'a function');return;}
+window.navigator.mediaDevices.getDisplayMedia=function(constraints){return getSourceId(constraints).then(function(sourceId){var widthSpecified=constraints.video&&constraints.video.width;var heightSpecified=constraints.video&&constraints.video.height;var frameRateSpecified=constraints.video&&constraints.video.frameRate;constraints.video={mandatory:{chromeMediaSource:'desktop',chromeMediaSourceId:sourceId,maxFrameRate:frameRateSpecified||3}};if(widthSpecified){constraints.video.mandatory.maxWidth=widthSpecified;}
+if(heightSpecified){constraints.video.mandatory.maxHeight=heightSpecified;}
+return window.navigator.mediaDevices.getUserMedia(constraints);});};}},{}],5:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimGetUserMedia=shimGetUserMedia;var _utils=require('../utils.js');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+var logging=utils.log;function shimGetUserMedia(window){var navigator=window&&window.navigator;if(!navigator.mediaDevices){return;}
+var browserDetails=utils.detectBrowser(window);var constraintsToChrome_=function constraintsToChrome_(c){if((typeof c==='undefined'?'undefined':_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 oldname_(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 shimConstraints_(constraints,func){if(browserDetails.version>=61){return func(constraints);}
+constraints=JSON.parse(JSON.stringify(constraints));if(constraints&&_typeof(constraints.audio)==='object'){var remap=function remap(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};constraints=JSON.parse(JSON.stringify(constraints));remap(constraints.audio,'autoGainControl','googAutoGainControl');remap(constraints.audio,'noiseSuppression','googNoiseSuppression');constraints.audio=constraintsToChrome_(constraints.audio);}
+if(constraints&&_typeof(constraints.video)==='object'){var face=constraints.video.facingMode;face=face&&((typeof face==='undefined'?'undefined':_typeof(face))==='object'?face:{ideal:face});var getSupportedFacingModeLies=browserDetails.version<66;if(face&&(face.exact==='user'||face.exact==='environment'||face.ideal==='user'||face.ideal==='environment')&&!(navigator.mediaDevices.getSupportedConstraints&&navigator.mediaDevices.getSupportedConstraints().facingMode&&!getSupportedFacingModeLies)){delete constraints.video.facingMode;var matches=void 0;if(face.exact==='environment'||face.ideal==='environment'){matches=['back','rear'];}else if(face.exact==='user'||face.ideal==='user'){matches=['front'];}
+if(matches){return navigator.mediaDevices.enumerateDevices().then(function(devices){devices=devices.filter(function(d){return d.kind==='videoinput';});var dev=devices.find(function(d){return matches.some(function(match){return d.label.toLowerCase().includes(match);});});if(!dev&&devices.length&&matches.includes('back')){dev=devices[devices.length-1];}
+if(dev){constraints.video.deviceId=face.exact?{exact:dev.deviceId}:{ideal:dev.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 shimError_(e){if(browserDetails.version>=64){return e;}
+return{name:{PermissionDeniedError:'NotAllowedError',PermissionDismissedError:'NotAllowedError',InvalidStateError:'NotAllowedError',DevicesNotFoundError:'NotFoundError',ConstraintNotSatisfiedError:'OverconstrainedError',TrackStartError:'NotReadableError',MediaDeviceFailedDueToShutdown:'NotAllowedError',MediaDeviceKillSwitchOn:'NotAllowedError',TabCaptureError:'AbortError',ScreenCaptureError:'AbortError',DeviceCaptureError:'AbortError'}[e.name]||e.name,message:e.message,constraint:e.constraint||e.constraintName,toString:function toString(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function getUserMedia_(constraints,onSuccess,onError){shimConstraints_(constraints,function(c){navigator.webkitGetUserMedia(c,onSuccess,function(e){if(onError){onError(shimError_(e));}});});};navigator.getUserMedia=getUserMedia_.bind(navigator);var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(cs){return shimConstraints_(cs,function(c){return origGetUserMedia(c).then(function(stream){if(c.audio&&!stream.getAudioTracks().length||c.video&&!stream.getVideoTracks().length){stream.getTracks().forEach(function(track){track.stop();});throw new DOMException('','NotFoundError');}
+return stream;},function(e){return Promise.reject(shimError_(e));});});};}},{"../utils.js":15}],6:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimRTCIceCandidate=shimRTCIceCandidate;exports.shimMaxMessageSize=shimMaxMessageSize;exports.shimSendThrowTypeError=shimSendThrowTypeError;exports.shimConnectionState=shimConnectionState;exports.removeAllowExtmapMixed=removeAllowExtmapMixed;var _sdp=require('sdp');var _sdp2=_interopRequireDefault(_sdp);var _utils=require('./utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}
+function shimRTCIceCandidate(window){if(!window.RTCIceCandidate||window.RTCIceCandidate&&'foundation'in window.RTCIceCandidate.prototype){return;}
+var NativeRTCIceCandidate=window.RTCIceCandidate;window.RTCIceCandidate=function(args){if((typeof args==='undefined'?'undefined':_typeof(args))==='object'&&args.candidate&&args.candidate.indexOf('a=')===0){args=JSON.parse(JSON.stringify(args));args.candidate=args.candidate.substr(2);}
+if(args.candidate&&args.candidate.length){var nativeCandidate=new NativeRTCIceCandidate(args);var parsedCandidate=_sdp2.default.parseCandidate(args.candidate);var augmentedCandidate=Object.assign(nativeCandidate,parsedCandidate);augmentedCandidate.toJSON=function(){return{candidate:augmentedCandidate.candidate,sdpMid:augmentedCandidate.sdpMid,sdpMLineIndex:augmentedCandidate.sdpMLineIndex,usernameFragment:augmentedCandidate.usernameFragment};};return augmentedCandidate;}
+return new NativeRTCIceCandidate(args);};window.RTCIceCandidate.prototype=NativeRTCIceCandidate.prototype;utils.wrapPeerConnectionEvent(window,'icecandidate',function(e){if(e.candidate){Object.defineProperty(e,'candidate',{value:new window.RTCIceCandidate(e.candidate),writable:'false'});}
+return e;});}
+function shimMaxMessageSize(window){if(window.RTCSctpTransport||!window.RTCPeerConnection){return;}
+var browserDetails=utils.detectBrowser(window);if(!('sctp'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'sctp',{get:function get(){return typeof this._sctp==='undefined'?null:this._sctp;}});}
+var sctpInDescription=function sctpInDescription(description){var sections=_sdp2.default.splitSections(description.sdp);sections.shift();return sections.some(function(mediaSection){var mLine=_sdp2.default.parseMLine(mediaSection);return mLine&&mLine.kind==='application'&&mLine.protocol.indexOf('SCTP')!==-1;});};var getRemoteFirefoxVersion=function getRemoteFirefoxVersion(description){var match=description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);if(match===null||match.length<2){return-1;}
+var version=parseInt(match[1],10);return version!==version?-1:version;};var getCanSendMaxMessageSize=function getCanSendMaxMessageSize(remoteIsFirefox){var canSendMaxMessageSize=65536;if(browserDetails.browser==='firefox'){if(browserDetails.version<57){if(remoteIsFirefox===-1){canSendMaxMessageSize=16384;}else{canSendMaxMessageSize=2147483637;}}else if(browserDetails.version<60){canSendMaxMessageSize=browserDetails.version===57?65535:65536;}else{canSendMaxMessageSize=2147483637;}}
+return canSendMaxMessageSize;};var getMaxMessageSize=function getMaxMessageSize(description,remoteIsFirefox){var maxMessageSize=65536;if(browserDetails.browser==='firefox'&&browserDetails.version===57){maxMessageSize=65535;}
+var match=_sdp2.default.matchPrefix(description.sdp,'a=max-message-size:');if(match.length>0){maxMessageSize=parseInt(match[0].substr(19),10);}else if(browserDetails.browser==='firefox'&&remoteIsFirefox!==-1){maxMessageSize=2147483637;}
+return maxMessageSize;};var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){this._sctp=null;if(sctpInDescription(arguments[0])){var isFirefox=getRemoteFirefoxVersion(arguments[0]);var canSendMMS=getCanSendMaxMessageSize(isFirefox);var remoteMMS=getMaxMessageSize(arguments[0],isFirefox);var maxMessageSize=void 0;if(canSendMMS===0&&remoteMMS===0){maxMessageSize=Number.POSITIVE_INFINITY;}else if(canSendMMS===0||remoteMMS===0){maxMessageSize=Math.max(canSendMMS,remoteMMS);}else{maxMessageSize=Math.min(canSendMMS,remoteMMS);}
+var sctp={};Object.defineProperty(sctp,'maxMessageSize',{get:function get(){return maxMessageSize;}});this._sctp=sctp;}
+return origSetRemoteDescription.apply(this,arguments);};}
+function shimSendThrowTypeError(window){if(!(window.RTCPeerConnection&&'createDataChannel'in window.RTCPeerConnection.prototype)){return;}
+function wrapDcSend(dc,pc){var origDataChannelSend=dc.send;dc.send=function(){var data=arguments[0];var length=data.length||data.size||data.byteLength;if(dc.readyState==='open'&&pc.sctp&&length>pc.sctp.maxMessageSize){throw new TypeError('Message too large (can send a maximum of '+pc.sctp.maxMessageSize+' bytes)');}
+return origDataChannelSend.apply(dc,arguments);};}
+var origCreateDataChannel=window.RTCPeerConnection.prototype.createDataChannel;window.RTCPeerConnection.prototype.createDataChannel=function(){var dataChannel=origCreateDataChannel.apply(this,arguments);wrapDcSend(dataChannel,this);return dataChannel;};utils.wrapPeerConnectionEvent(window,'datachannel',function(e){wrapDcSend(e.channel,e.target);return e;});}
+function shimConnectionState(window){if(!window.RTCPeerConnection||'connectionState'in window.RTCPeerConnection.prototype){return;}
+var proto=window.RTCPeerConnection.prototype;Object.defineProperty(proto,'connectionState',{get:function get(){return{completed:'connected',checking:'connecting'}[this.iceConnectionState]||this.iceConnectionState;},enumerable:true,configurable:true});Object.defineProperty(proto,'onconnectionstatechange',{get:function get(){return this._onconnectionstatechange||null;},set:function set(cb){if(this._onconnectionstatechange){this.removeEventListener('connectionstatechange',this._onconnectionstatechange);delete this._onconnectionstatechange;}
+if(cb){this.addEventListener('connectionstatechange',this._onconnectionstatechange=cb);}},enumerable:true,configurable:true});['setLocalDescription','setRemoteDescription'].forEach(function(method){var origMethod=proto[method];proto[method]=function(){if(!this._connectionstatechangepoly){this._connectionstatechangepoly=function(e){var pc=e.target;if(pc._lastConnectionState!==pc.connectionState){pc._lastConnectionState=pc.connectionState;var newEvent=new Event('connectionstatechange',e);pc.dispatchEvent(newEvent);}
+return e;};this.addEventListener('iceconnectionstatechange',this._connectionstatechangepoly);}
+return origMethod.apply(this,arguments);};});}
+function removeAllowExtmapMixed(window){if(!window.RTCPeerConnection){return;}
+var browserDetails=utils.detectBrowser(window);if(browserDetails.browser==='chrome'&&browserDetails.version>=71){return;}
+var nativeSRD=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(desc){if(desc&&desc.sdp&&desc.sdp.indexOf('\na=extmap-allow-mixed')!==-1){desc.sdp=desc.sdp.split('\n').filter(function(line){return line.trim()!=='a=extmap-allow-mixed';}).join('\n');}
+return nativeSRD.apply(this,arguments);};}},{"./utils":15,"sdp":17}],7:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=exports.shimGetUserMedia=undefined;var _getusermedia=require('./getusermedia');Object.defineProperty(exports,'shimGetUserMedia',{enumerable:true,get:function get(){return _getusermedia.shimGetUserMedia;}});var _getdisplaymedia=require('./getdisplaymedia');Object.defineProperty(exports,'shimGetDisplayMedia',{enumerable:true,get:function get(){return _getdisplaymedia.shimGetDisplayMedia;}});exports.shimPeerConnection=shimPeerConnection;exports.shimReplaceTrack=shimReplaceTrack;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);var _filtericeservers=require('./filtericeservers');var _rtcpeerconnectionShim=require('rtcpeerconnection-shim');var _rtcpeerconnectionShim2=_interopRequireDefault(_rtcpeerconnectionShim);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}
+function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimPeerConnection(window){var browserDetails=utils.detectBrowser(window);if(window.RTCIceGatherer){if(!window.RTCIceCandidate){window.RTCIceCandidate=function(args){return args;};}
+if(!window.RTCSessionDescription){window.RTCSessionDescription=function(args){return args;};}
+if(browserDetails.version<15025){var origMSTEnabled=Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype,'enabled');Object.defineProperty(window.MediaStreamTrack.prototype,'enabled',{set:function set(value){origMSTEnabled.set.call(this,value);var ev=new Event('enabled');ev.enabled=value;this.dispatchEvent(ev);}});}}
+if(window.RTCRtpSender&&!('dtmf'in window.RTCRtpSender.prototype)){Object.defineProperty(window.RTCRtpSender.prototype,'dtmf',{get:function get(){if(this._dtmf===undefined){if(this.track.kind==='audio'){this._dtmf=new window.RTCDtmfSender(this);}else if(this.track.kind==='video'){this._dtmf=null;}}
+return this._dtmf;}});}
+if(window.RTCDtmfSender&&!window.RTCDTMFSender){window.RTCDTMFSender=window.RTCDtmfSender;}
+var RTCPeerConnectionShim=(0,_rtcpeerconnectionShim2.default)(window,browserDetails.version);window.RTCPeerConnection=function(config){if(config&&config.iceServers){config.iceServers=(0,_filtericeservers.filterIceServers)(config.iceServers,browserDetails.version);utils.log('ICE servers after filtering:',config.iceServers);}
+return new RTCPeerConnectionShim(config);};window.RTCPeerConnection.prototype=RTCPeerConnectionShim.prototype;}
+function shimReplaceTrack(window){if(window.RTCRtpSender&&!('replaceTrack'in window.RTCRtpSender.prototype)){window.RTCRtpSender.prototype.replaceTrack=window.RTCRtpSender.prototype.setTrack;}}},{"../utils":15,"./filtericeservers":8,"./getdisplaymedia":9,"./getusermedia":10,"rtcpeerconnection-shim":16}],8:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.filterIceServers=filterIceServers;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function filterIceServers(iceServers,edgeVersion){var hasTurn=false;iceServers=JSON.parse(JSON.stringify(iceServers));return iceServers.filter(function(server){if(server&&(server.urls||server.url)){var urls=server.urls||server.url;if(server.url&&!server.urls){utils.deprecated('RTCIceServer.url','RTCIceServer.urls');}
+var isString=typeof urls==='string';if(isString){urls=[urls];}
+urls=urls.filter(function(url){if(url.indexOf('stun:')===0){return false;}
+var validTurn=url.startsWith('turn')&&!url.startsWith('turn:[')&&url.includes('transport=udp');if(validTurn&&!hasTurn){hasTurn=true;return true;}
+return validTurn&&!hasTurn;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;}});}},{"../utils":15}],9:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=shimGetDisplayMedia;function shimGetDisplayMedia(window){if(!('getDisplayMedia'in window.navigator)){return;}
+if(!window.navigator.mediaDevices){return;}
+if(window.navigator.mediaDevices&&'getDisplayMedia'in window.navigator.mediaDevices){return;}
+window.navigator.mediaDevices.getDisplayMedia=window.navigator.getDisplayMedia.bind(window.navigator.mediaDevices);}},{}],10:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetUserMedia=shimGetUserMedia;function shimGetUserMedia(window){var navigator=window&&window.navigator;var shimError_=function shimError_(e){return{name:{PermissionDeniedError:'NotAllowedError'}[e.name]||e.name,message:e.message,constraint:e.constraint,toString:function toString(){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));});};}},{}],11:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=exports.shimGetUserMedia=undefined;var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};var _getusermedia=require('./getusermedia');Object.defineProperty(exports,'shimGetUserMedia',{enumerable:true,get:function get(){return _getusermedia.shimGetUserMedia;}});var _getdisplaymedia=require('./getdisplaymedia');Object.defineProperty(exports,'shimGetDisplayMedia',{enumerable:true,get:function get(){return _getdisplaymedia.shimGetDisplayMedia;}});exports.shimOnTrack=shimOnTrack;exports.shimPeerConnection=shimPeerConnection;exports.shimSenderGetStats=shimSenderGetStats;exports.shimReceiverGetStats=shimReceiverGetStats;exports.shimRemoveStream=shimRemoveStream;exports.shimRTCDataChannel=shimRTCDataChannel;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimOnTrack(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCTrackEvent&&'receiver'in window.RTCTrackEvent.prototype&&!('transceiver'in window.RTCTrackEvent.prototype)){Object.defineProperty(window.RTCTrackEvent.prototype,'transceiver',{get:function get(){return{receiver:this.receiver};}});}}
+function shimPeerConnection(window){var browserDetails=utils.detectBrowser(window);if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!(window.RTCPeerConnection||window.mozRTCPeerConnection)){return;}
+if(!window.RTCPeerConnection&&window.mozRTCPeerConnection){window.RTCPeerConnection=window.mozRTCPeerConnection;}
+['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new(method==='addIceCandidate'?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);}
+return Promise.resolve();}
+return nativeAddIceCandidate.apply(this,arguments);};var modernStatsTypes={inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'};var nativeGetStats=window.RTCPeerConnection.prototype.getStats;window.RTCPeerConnection.prototype.getStats=function(selector,onSucc,onErr){return nativeGetStats.apply(this,[selector||null]).then(function(stats){if(browserDetails.version<53&&!onSucc){try{stats.forEach(function(stat){stat.type=modernStatsTypes[stat.type]||stat.type;});}catch(e){if(e.name!=='TypeError'){throw e;}
+stats.forEach(function(stat,i){stats.set(i,Object.assign({},stat,{type:modernStatsTypes[stat.type]||stat.type}));});}}
+return stats;}).then(onSucc,onErr);};}
+function shimSenderGetStats(window){if(!((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&window.RTCRtpSender)){return;}
+if(window.RTCRtpSender&&'getStats'in window.RTCRtpSender.prototype){return;}
+var origGetSenders=window.RTCPeerConnection.prototype.getSenders;if(origGetSenders){window.RTCPeerConnection.prototype.getSenders=function(){var _this=this;var senders=origGetSenders.apply(this,[]);senders.forEach(function(sender){return sender._pc=_this;});return senders;};}
+var origAddTrack=window.RTCPeerConnection.prototype.addTrack;if(origAddTrack){window.RTCPeerConnection.prototype.addTrack=function(){var sender=origAddTrack.apply(this,arguments);sender._pc=this;return sender;};}
+window.RTCRtpSender.prototype.getStats=function(){return this.track?this._pc.getStats(this.track):Promise.resolve(new Map());};}
+function shimReceiverGetStats(window){if(!((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&window.RTCRtpSender)){return;}
+if(window.RTCRtpSender&&'getStats'in window.RTCRtpReceiver.prototype){return;}
+var origGetReceivers=window.RTCPeerConnection.prototype.getReceivers;if(origGetReceivers){window.RTCPeerConnection.prototype.getReceivers=function(){var _this2=this;var receivers=origGetReceivers.apply(this,[]);receivers.forEach(function(receiver){return receiver._pc=_this2;});return receivers;};}
+utils.wrapPeerConnectionEvent(window,'track',function(e){e.receiver._pc=e.srcElement;return e;});window.RTCRtpReceiver.prototype.getStats=function(){return this._pc.getStats(this.track);};}
+function shimRemoveStream(window){if(!window.RTCPeerConnection||'removeStream'in window.RTCPeerConnection.prototype){return;}
+window.RTCPeerConnection.prototype.removeStream=function(stream){var _this3=this;utils.deprecated('removeStream','removeTrack');this.getSenders().forEach(function(sender){if(sender.track&&stream.getTracks().includes(sender.track)){_this3.removeTrack(sender);}});};}
+function shimRTCDataChannel(window){if(window.DataChannel&&!window.RTCDataChannel){window.RTCDataChannel=window.DataChannel;}}},{"../utils":15,"./getdisplaymedia":12,"./getusermedia":13}],12:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});exports.shimGetDisplayMedia=shimGetDisplayMedia;function shimGetDisplayMedia(window,preferredMediaSource){if(window.navigator.mediaDevices&&'getDisplayMedia'in window.navigator.mediaDevices){return;}
+if(!window.navigator.mediaDevices){return;}
+window.navigator.mediaDevices.getDisplayMedia=function(constraints){if(!(constraints&&constraints.video)){var err=new DOMException('getDisplayMedia without video '+'constraints is undefined');err.name='NotFoundError';err.code=8;return Promise.reject(err);}
+if(constraints.video===true){constraints.video={mediaSource:preferredMediaSource};}else{constraints.video.mediaSource=preferredMediaSource;}
+return window.navigator.mediaDevices.getUserMedia(constraints);};}},{}],13:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimGetUserMedia=shimGetUserMedia;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimGetUserMedia(window){var browserDetails=utils.detectBrowser(window);var navigator=window&&window.navigator;var MediaStreamTrack=window&&window.MediaStreamTrack;navigator.getUserMedia=function(constraints,onSuccess,onError){utils.deprecated('navigator.getUserMedia','navigator.mediaDevices.getUserMedia');navigator.mediaDevices.getUserMedia(constraints).then(onSuccess,onError);};if(!(browserDetails.version>55&&'autoGainControl'in navigator.mediaDevices.getSupportedConstraints())){var remap=function remap(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};var nativeGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){if((typeof c==='undefined'?'undefined':_typeof(c))==='object'&&_typeof(c.audio)==='object'){c=JSON.parse(JSON.stringify(c));remap(c.audio,'autoGainControl','mozAutoGainControl');remap(c.audio,'noiseSuppression','mozNoiseSuppression');}
+return nativeGetUserMedia(c);};if(MediaStreamTrack&&MediaStreamTrack.prototype.getSettings){var nativeGetSettings=MediaStreamTrack.prototype.getSettings;MediaStreamTrack.prototype.getSettings=function(){var obj=nativeGetSettings.apply(this,arguments);remap(obj,'mozAutoGainControl','autoGainControl');remap(obj,'mozNoiseSuppression','noiseSuppression');return obj;};}
+if(MediaStreamTrack&&MediaStreamTrack.prototype.applyConstraints){var nativeApplyConstraints=MediaStreamTrack.prototype.applyConstraints;MediaStreamTrack.prototype.applyConstraints=function(c){if(this.kind==='audio'&&(typeof c==='undefined'?'undefined':_typeof(c))==='object'){c=JSON.parse(JSON.stringify(c));remap(c,'autoGainControl','mozAutoGainControl');remap(c,'noiseSuppression','mozNoiseSuppression');}
+return nativeApplyConstraints.apply(this,[c]);};}}}},{"../utils":15}],14:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.shimLocalStreamsAPI=shimLocalStreamsAPI;exports.shimRemoteStreamsAPI=shimRemoteStreamsAPI;exports.shimCallbacksAPI=shimCallbacksAPI;exports.shimGetUserMedia=shimGetUserMedia;exports.shimConstraints=shimConstraints;exports.shimRTCIceServerUrls=shimRTCIceServerUrls;exports.shimTrackEventTransceiver=shimTrackEventTransceiver;exports.shimCreateOfferLegacy=shimCreateOfferLegacy;var _utils=require('../utils');var utils=_interopRequireWildcard(_utils);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key];}}newObj.default=obj;return newObj;}}
+function shimLocalStreamsAPI(window){if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!window.RTCPeerConnection){return;}
+if(!('getLocalStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getLocalStreams=function(){if(!this._localStreams){this._localStreams=[];}
+return this._localStreams;};}
+if(!('addStream'in window.RTCPeerConnection.prototype)){var _addTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addStream=function(stream){var _this=this;if(!this._localStreams){this._localStreams=[];}
+if(!this._localStreams.includes(stream)){this._localStreams.push(stream);}
+stream.getTracks().forEach(function(track){return _addTrack.call(_this,track,stream);});};window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(stream){if(!this._localStreams){this._localStreams=[stream];}else if(!this._localStreams.includes(stream)){this._localStreams.push(stream);}}
+return _addTrack.call(this,track,stream);};}
+if(!('removeStream'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.removeStream=function(stream){var _this2=this;if(!this._localStreams){this._localStreams=[];}
+var index=this._localStreams.indexOf(stream);if(index===-1){return;}
+this._localStreams.splice(index,1);var tracks=stream.getTracks();this.getSenders().forEach(function(sender){if(tracks.includes(sender.track)){_this2.removeTrack(sender);}});};}}
+function shimRemoteStreamsAPI(window){if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!window.RTCPeerConnection){return;}
+if(!('getRemoteStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[];};}
+if(!('onaddstream'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'onaddstream',{get:function get(){return this._onaddstream;},set:function set(f){var _this3=this;if(this._onaddstream){this.removeEventListener('addstream',this._onaddstream);this.removeEventListener('track',this._onaddstreampoly);}
+this.addEventListener('addstream',this._onaddstream=f);this.addEventListener('track',this._onaddstreampoly=function(e){e.streams.forEach(function(stream){if(!_this3._remoteStreams){_this3._remoteStreams=[];}
+if(_this3._remoteStreams.includes(stream)){return;}
+_this3._remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;_this3.dispatchEvent(event);});});}});var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){var pc=this;if(!this._onaddstreampoly){this.addEventListener('track',this._onaddstreampoly=function(e){e.streams.forEach(function(stream){if(!pc._remoteStreams){pc._remoteStreams=[];}
+if(pc._remoteStreams.indexOf(stream)>=0){return;}
+pc._remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;pc.dispatchEvent(event);});});}
+return origSetRemoteDescription.apply(pc,arguments);};}}
+function shimCallbacksAPI(window){if((typeof window==='undefined'?'undefined':_typeof(window))!=='object'||!window.RTCPeerConnection){return;}
+var prototype=window.RTCPeerConnection.prototype;var createOffer=prototype.createOffer;var createAnswer=prototype.createAnswer;var setLocalDescription=prototype.setLocalDescription;var setRemoteDescription=prototype.setRemoteDescription;var addIceCandidate=prototype.addIceCandidate;prototype.createOffer=function(successCallback,failureCallback){var options=arguments.length>=2?arguments[2]:arguments[0];var promise=createOffer.apply(this,[options]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.createAnswer=function(successCallback,failureCallback){var options=arguments.length>=2?arguments[2]:arguments[0];var promise=createAnswer.apply(this,[options]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};var withCallback=function withCallback(description,successCallback,failureCallback){var promise=setLocalDescription.apply(this,[description]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setLocalDescription=withCallback;withCallback=function withCallback(description,successCallback,failureCallback){var promise=setRemoteDescription.apply(this,[description]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setRemoteDescription=withCallback;withCallback=function withCallback(candidate,successCallback,failureCallback){var promise=addIceCandidate.apply(this,[candidate]);if(!failureCallback){return promise;}
+promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.addIceCandidate=withCallback;}
+function shimGetUserMedia(window){var navigator=window&&window.navigator;if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){var mediaDevices=navigator.mediaDevices;var _getUserMedia=mediaDevices.getUserMedia.bind(mediaDevices);navigator.mediaDevices.getUserMedia=function(constraints){return _getUserMedia(shimConstraints(constraints));};}
+if(!navigator.getUserMedia&&navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){navigator.getUserMedia=function(constraints,cb,errcb){navigator.mediaDevices.getUserMedia(constraints).then(cb,errcb);}.bind(navigator);}}
+function shimConstraints(constraints){if(constraints&&constraints.video!==undefined){return Object.assign({},constraints,{video:utils.compactObject(constraints.video)});}
+return constraints;}
+function shimRTCIceServerUrls(window){var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i<pcConfig.iceServers.length;i++){var server=pcConfig.iceServers[i];if(!server.hasOwnProperty('urls')&&server.hasOwnProperty('url')){utils.deprecated('RTCIceServer.url','RTCIceServer.urls');server=JSON.parse(JSON.stringify(server));server.urls=server.url;delete server.url;newIceServers.push(server);}else{newIceServers.push(pcConfig.iceServers[i]);}}
+pcConfig.iceServers=newIceServers;}
+return new OrigPeerConnection(pcConfig,pcConstraints);};window.RTCPeerConnection.prototype=OrigPeerConnection.prototype;if('generateCertificate'in window.RTCPeerConnection){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function get(){return OrigPeerConnection.generateCertificate;}});}}
+function shimTrackEventTransceiver(window){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'&&window.RTCPeerConnection&&'receiver'in window.RTCTrackEvent.prototype&&!window.RTCTransceiver){Object.defineProperty(window.RTCTrackEvent.prototype,'transceiver',{get:function get(){return{receiver:this.receiver};}});}}
+function shimCreateOfferLegacy(window){var origCreateOffer=window.RTCPeerConnection.prototype.createOffer;window.RTCPeerConnection.prototype.createOffer=function(offerOptions){if(offerOptions){if(typeof offerOptions.offerToReceiveAudio!=='undefined'){offerOptions.offerToReceiveAudio=!!offerOptions.offerToReceiveAudio;}
+var audioTransceiver=this.getTransceivers().find(function(transceiver){return transceiver.sender.track&&transceiver.sender.track.kind==='audio';});if(offerOptions.offerToReceiveAudio===false&&audioTransceiver){if(audioTransceiver.direction==='sendrecv'){if(audioTransceiver.setDirection){audioTransceiver.setDirection('sendonly');}else{audioTransceiver.direction='sendonly';}}else if(audioTransceiver.direction==='recvonly'){if(audioTransceiver.setDirection){audioTransceiver.setDirection('inactive');}else{audioTransceiver.direction='inactive';}}}else if(offerOptions.offerToReceiveAudio===true&&!audioTransceiver){this.addTransceiver('audio');}
+if(typeof offerOptions.offerToReceiveVideo!=='undefined'){offerOptions.offerToReceiveVideo=!!offerOptions.offerToReceiveVideo;}
+var videoTransceiver=this.getTransceivers().find(function(transceiver){return transceiver.sender.track&&transceiver.sender.track.kind==='video';});if(offerOptions.offerToReceiveVideo===false&&videoTransceiver){if(videoTransceiver.direction==='sendrecv'){if(videoTransceiver.setDirection){videoTransceiver.setDirection('sendonly');}else{videoTransceiver.direction='sendonly';}}else if(videoTransceiver.direction==='recvonly'){if(videoTransceiver.setDirection){videoTransceiver.setDirection('inactive');}else{videoTransceiver.direction='inactive';}}}else if(offerOptions.offerToReceiveVideo===true&&!videoTransceiver){this.addTransceiver('video');}}
+return origCreateOffer.apply(this,arguments);};}},{"../utils":15}],15:[function(require,module,exports){'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj;}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj;};exports.extractVersion=extractVersion;exports.wrapPeerConnectionEvent=wrapPeerConnectionEvent;exports.disableLog=disableLog;exports.disableWarnings=disableWarnings;exports.log=log;exports.deprecated=deprecated;exports.detectBrowser=detectBrowser;exports.compactObject=compactObject;function _defineProperty(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true});}else{obj[key]=value;}return obj;}
+var logDisabled_=true;var deprecationWarnings_=true;function extractVersion(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos],10);}
+function wrapPeerConnectionEvent(window,eventNameToWrap,wrapper){if(!window.RTCPeerConnection){return;}
+var proto=window.RTCPeerConnection.prototype;var nativeAddEventListener=proto.addEventListener;proto.addEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap){return nativeAddEventListener.apply(this,arguments);}
+var wrappedCallback=function wrappedCallback(e){var modifiedEvent=wrapper(e);if(modifiedEvent){cb(modifiedEvent);}};this._eventMap=this._eventMap||{};this._eventMap[cb]=wrappedCallback;return nativeAddEventListener.apply(this,[nativeEventName,wrappedCallback]);};var nativeRemoveEventListener=proto.removeEventListener;proto.removeEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap||!this._eventMap||!this._eventMap[cb]){return nativeRemoveEventListener.apply(this,arguments);}
+var unwrappedCb=this._eventMap[cb];delete this._eventMap[cb];return nativeRemoveEventListener.apply(this,[nativeEventName,unwrappedCb]);};Object.defineProperty(proto,'on'+eventNameToWrap,{get:function get(){return this['_on'+eventNameToWrap];},set:function set(cb){if(this['_on'+eventNameToWrap]){this.removeEventListener(eventNameToWrap,this['_on'+eventNameToWrap]);delete this['_on'+eventNameToWrap];}
+if(cb){this.addEventListener(eventNameToWrap,this['_on'+eventNameToWrap]=cb);}},enumerable:true,configurable:true});}
+function disableLog(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+(typeof bool==='undefined'?'undefined':_typeof(bool))+'. Please use a boolean.');}
+logDisabled_=bool;return bool?'adapter.js logging disabled':'adapter.js logging enabled';}
+function disableWarnings(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+(typeof bool==='undefined'?'undefined':_typeof(bool))+'. Please use a boolean.');}
+deprecationWarnings_=!bool;return'adapter.js deprecation warnings '+(bool?'disabled':'enabled');}
+function log(){if((typeof window==='undefined'?'undefined':_typeof(window))==='object'){if(logDisabled_){return;}
+if(typeof console!=='undefined'&&typeof console.log==='function'){console.log.apply(console,arguments);}}}
+function deprecated(oldMethod,newMethod){if(!deprecationWarnings_){return;}
+console.warn(oldMethod+' is deprecated, please use '+newMethod+' instead.');}
+function detectBrowser(window){var navigator=window.navigator;var result={browser:null,version:null};if(typeof window==='undefined'||!window.navigator){result.browser='Not a browser.';return result;}
+if(navigator.mozGetUserMedia){result.browser='firefox';result.version=extractVersion(navigator.userAgent,/Firefox\/(\d+)\./,1);}else if(navigator.webkitGetUserMedia){result.browser='chrome';result.version=extractVersion(navigator.userAgent,/Chrom(e|ium)\/(\d+)\./,2);}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){result.browser='edge';result.version=extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2);}else if(window.RTCPeerConnection&&navigator.userAgent.match(/AppleWebKit\/(\d+)\./)){result.browser='safari';result.version=extractVersion(navigator.userAgent,/AppleWebKit\/(\d+)\./,1);}else{result.browser='Not a supported browser.';return result;}
+return result;}
+function compactObject(data){if((typeof data==='undefined'?'undefined':_typeof(data))!=='object'){return data;}
+return Object.keys(data).reduce(function(accumulator,key){var isObject=_typeof(data[key])==='object';var value=isObject?compactObject(data[key]):data[key];var isEmptyObject=isObject&&!Object.keys(value).length;if(value===undefined||isEmptyObject){return accumulator;}
+return Object.assign(accumulator,_defineProperty({},key,value));},{});}},{}],16:[function(require,module,exports){'use strict';var SDPUtils=require('sdp');function fixStatsType(stat){return{inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[stat.type]||stat.type;}
+function writeMediaSection(transceiver,caps,type,stream,dtlsRole){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),type==='offer'?'actpass':dtlsRole||'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 trackId=transceiver.rtpSender._initialTrackId||transceiver.rtpSender.track.id;transceiver.rtpSender._initialTrackId=trackId;var msid='msid:'+(stream?stream.id:'-')+' '+
+trackId+'\r\n';sdp+='a='+msid;sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' '+msid;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+
+transceiver.sendEncodingParameters[0].ssrc+' '+
+transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}}
+sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' cname:'+SDPUtils.localCName+'\r\n';}
+return sdp;}
+function filterIceServers(iceServers,edgeVersion){var hasTurn=false;iceServers=JSON.parse(JSON.stringify(iceServers));return iceServers.filter(function(server){if(server&&(server.urls||server.url)){var urls=server.urls||server.url;if(server.url&&!server.urls){console.warn('RTCIceServer.url is deprecated! Use urls instead.');}
+var isString=typeof urls==='string';if(isString){urls=[urls];}
+urls=urls.filter(function(url){var validTurn=url.indexOf('turn:')===0&&url.indexOf('transport=udp')!==-1&&url.indexOf('turn:[')===-1&&!hasTurn;if(validTurn){hasTurn=true;return true;}
+return url.indexOf('stun:')===0&&edgeVersion>=14393&&url.indexOf('?transport=udp')===-1;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;}});}
+function getCommonCapabilities(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};var findCodecByPayloadType=function(pt,codecs){pt=parseInt(pt,10);for(var i=0;i<codecs.length;i++){if(codecs[i].payloadType===pt||codecs[i].preferredPayloadType===pt){return codecs[i];}}};var rtxCapabilityMatches=function(lRtx,rRtx,lCodecs,rCodecs){var lCodec=findCodecByPayloadType(lRtx.parameters.apt,lCodecs);var rCodec=findCodecByPayloadType(rRtx.parameters.apt,rCodecs);return lCodec&&rCodec&&lCodec.name.toLowerCase()===rCodec.name.toLowerCase();};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){if(lCodec.name.toLowerCase()==='rtx'&&lCodec.parameters&&rCodec.parameters.apt){if(!rtxCapabilityMatches(lCodec,rCodec,localCapabilities.codecs,remoteCapabilities.codecs)){continue;}}
+rCodec=JSON.parse(JSON.stringify(rCodec));rCodec.numChannels=Math.min(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;}
+function isActionAllowedInSignalingState(action,type,signalingState){return{offer:{setLocalDescription:['stable','have-local-offer'],setRemoteDescription:['stable','have-remote-offer']},answer:{setLocalDescription:['have-remote-offer','have-local-pranswer'],setRemoteDescription:['have-local-offer','have-remote-pranswer']}}[type][action].indexOf(signalingState)!==-1;}
+function maybeAddCandidate(iceTransport,candidate){var alreadyAdded=iceTransport.getRemoteCandidates().find(function(remoteCandidate){return candidate.foundation===remoteCandidate.foundation&&candidate.ip===remoteCandidate.ip&&candidate.port===remoteCandidate.port&&candidate.priority===remoteCandidate.priority&&candidate.protocol===remoteCandidate.protocol&&candidate.type===remoteCandidate.type;});if(!alreadyAdded){iceTransport.addRemoteCandidate(candidate);}
+return!alreadyAdded;}
+function makeError(name,description){var e=new Error(description);e.name=name;e.code={NotSupportedError:9,InvalidStateError:11,InvalidAccessError:15,TypeError:undefined,OperationError:undefined}[name];return e;}
+module.exports=function(window,edgeVersion){function addTrackToStreamAndFireEvent(track,stream){stream.addTrack(track);stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack',{track:track}));}
+function removeTrackFromStreamAndFireEvent(track,stream){stream.removeTrack(track);stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack',{track:track}));}
+function fireAddTrack(pc,track,receiver,streams){var trackEvent=new Event('track');trackEvent.track=track;trackEvent.receiver=receiver;trackEvent.transceiver={receiver:receiver};trackEvent.streams=streams;window.setTimeout(function(){pc._dispatchEvent('track',trackEvent);});}
+var RTCPeerConnection=function(config){var pc=this;var _eventTarget=document.createDocumentFragment();['addEventListener','removeEventListener','dispatchEvent'].forEach(function(method){pc[method]=_eventTarget[method].bind(_eventTarget);});this.canTrickleIceCandidates=null;this.needNegotiation=false;this.localStreams=[];this.remoteStreams=[];this._localDescription=null;this._remoteDescription=null;this.signalingState='stable';this.iceConnectionState='new';this.connectionState='new';this.iceGatheringState='new';config=JSON.parse(JSON.stringify(config||{}));this.usingBundle=config.bundlePolicy==='max-bundle';if(config.rtcpMuxPolicy==='negotiate'){throw(makeError('NotSupportedError','rtcpMuxPolicy \'negotiate\' is not supported'));}else if(!config.rtcpMuxPolicy){config.rtcpMuxPolicy='require';}
+switch(config.iceTransportPolicy){case'all':case'relay':break;default:config.iceTransportPolicy='all';break;}
+switch(config.bundlePolicy){case'balanced':case'max-compat':case'max-bundle':break;default:config.bundlePolicy='balanced';break;}
+config.iceServers=filterIceServers(config.iceServers||[],edgeVersion);this._iceGatherers=[];if(config.iceCandidatePoolSize){for(var i=config.iceCandidatePoolSize;i>0;i--){this._iceGatherers.push(new window.RTCIceGatherer({iceServers:config.iceServers,gatherPolicy:config.iceTransportPolicy}));}}else{config.iceCandidatePoolSize=0;}
+this._config=config;this.transceivers=[];this._sdpSessionId=SDPUtils.generateSessionId();this._sdpSessionVersion=0;this._dtlsRole=undefined;this._isClosed=false;};Object.defineProperty(RTCPeerConnection.prototype,'localDescription',{configurable:true,get:function(){return this._localDescription;}});Object.defineProperty(RTCPeerConnection.prototype,'remoteDescription',{configurable:true,get:function(){return this._remoteDescription;}});RTCPeerConnection.prototype.onicecandidate=null;RTCPeerConnection.prototype.onaddstream=null;RTCPeerConnection.prototype.ontrack=null;RTCPeerConnection.prototype.onremovestream=null;RTCPeerConnection.prototype.onsignalingstatechange=null;RTCPeerConnection.prototype.oniceconnectionstatechange=null;RTCPeerConnection.prototype.onconnectionstatechange=null;RTCPeerConnection.prototype.onicegatheringstatechange=null;RTCPeerConnection.prototype.onnegotiationneeded=null;RTCPeerConnection.prototype.ondatachannel=null;RTCPeerConnection.prototype._dispatchEvent=function(name,event){if(this._isClosed){return;}
+this.dispatchEvent(event);if(typeof this['on'+name]==='function'){this['on'+name](event);}};RTCPeerConnection.prototype._emitGatheringStateChange=function(){var event=new Event('icegatheringstatechange');this._dispatchEvent('icegatheringstatechange',event);};RTCPeerConnection.prototype.getConfiguration=function(){return this._config;};RTCPeerConnection.prototype.getLocalStreams=function(){return this.localStreams;};RTCPeerConnection.prototype.getRemoteStreams=function(){return this.remoteStreams;};RTCPeerConnection.prototype._createTransceiver=function(kind,doNotAdd){var hasBundleTransport=this.transceivers.length>0;var transceiver={track:null,iceGatherer:null,iceTransport:null,dtlsTransport:null,localCapabilities:null,remoteCapabilities:null,rtpSender:null,rtpReceiver:null,kind:kind,mid:null,sendEncodingParameters:null,recvEncodingParameters:null,stream:null,associatedRemoteMediaStreams:[],wantReceive:true};if(this.usingBundle&&hasBundleTransport){transceiver.iceTransport=this.transceivers[0].iceTransport;transceiver.dtlsTransport=this.transceivers[0].dtlsTransport;}else{var transports=this._createIceAndDtlsTransports();transceiver.iceTransport=transports.iceTransport;transceiver.dtlsTransport=transports.dtlsTransport;}
+if(!doNotAdd){this.transceivers.push(transceiver);}
+return transceiver;};RTCPeerConnection.prototype.addTrack=function(track,stream){if(this._isClosed){throw makeError('InvalidStateError','Attempted to call addTrack on a closed peerconnection.');}
+var alreadyExists=this.transceivers.find(function(s){return s.track===track;});if(alreadyExists){throw makeError('InvalidAccessError','Track already exists.');}
+var transceiver;for(var i=0;i<this.transceivers.length;i++){if(!this.transceivers[i].track&&this.transceivers[i].kind===track.kind){transceiver=this.transceivers[i];}}
+if(!transceiver){transceiver=this._createTransceiver(track.kind);}
+this._maybeFireNegotiationNeeded();if(this.localStreams.indexOf(stream)===-1){this.localStreams.push(stream);}
+transceiver.track=track;transceiver.stream=stream;transceiver.rtpSender=new window.RTCRtpSender(track,transceiver.dtlsTransport);return transceiver.rtpSender;};RTCPeerConnection.prototype.addStream=function(stream){var pc=this;if(edgeVersion>=15025){stream.getTracks().forEach(function(track){pc.addTrack(track,stream);});}else{var clonedStream=stream.clone();stream.getTracks().forEach(function(track,idx){var clonedTrack=clonedStream.getTracks()[idx];track.addEventListener('enabled',function(event){clonedTrack.enabled=event.enabled;});});clonedStream.getTracks().forEach(function(track){pc.addTrack(track,clonedStream);});}};RTCPeerConnection.prototype.removeTrack=function(sender){if(this._isClosed){throw makeError('InvalidStateError','Attempted to call removeTrack on a closed peerconnection.');}
+if(!(sender instanceof window.RTCRtpSender)){throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.');}
+var transceiver=this.transceivers.find(function(t){return t.rtpSender===sender;});if(!transceiver){throw makeError('InvalidAccessError','Sender was not created by this connection.');}
+var stream=transceiver.stream;transceiver.rtpSender.stop();transceiver.rtpSender=null;transceiver.track=null;transceiver.stream=null;var localStreams=this.transceivers.map(function(t){return t.stream;});if(localStreams.indexOf(stream)===-1&&this.localStreams.indexOf(stream)>-1){this.localStreams.splice(this.localStreams.indexOf(stream),1);}
+this._maybeFireNegotiationNeeded();};RTCPeerConnection.prototype.removeStream=function(stream){var pc=this;stream.getTracks().forEach(function(track){var sender=pc.getSenders().find(function(s){return s.track===track;});if(sender){pc.removeTrack(sender);}});};RTCPeerConnection.prototype.getSenders=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpSender;}).map(function(transceiver){return transceiver.rtpSender;});};RTCPeerConnection.prototype.getReceivers=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpReceiver;}).map(function(transceiver){return transceiver.rtpReceiver;});};RTCPeerConnection.prototype._createIceGatherer=function(sdpMLineIndex,usingBundle){var pc=this;if(usingBundle&&sdpMLineIndex>0){return this.transceivers[0].iceGatherer;}else if(this._iceGatherers.length){return this._iceGatherers.shift();}
+var iceGatherer=new window.RTCIceGatherer({iceServers:this._config.iceServers,gatherPolicy:this._config.iceTransportPolicy});Object.defineProperty(iceGatherer,'state',{value:'new',writable:true});this.transceivers[sdpMLineIndex].bufferedCandidateEvents=[];this.transceivers[sdpMLineIndex].bufferCandidates=function(event){var end=!event.candidate||Object.keys(event.candidate).length===0;iceGatherer.state=end?'completed':'gathering';if(pc.transceivers[sdpMLineIndex].bufferedCandidateEvents!==null){pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event);}};iceGatherer.addEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);return iceGatherer;};RTCPeerConnection.prototype._gather=function(mid,sdpMLineIndex){var pc=this;var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer.onlocalcandidate){return;}
+var bufferedCandidateEvents=this.transceivers[sdpMLineIndex].bufferedCandidateEvents;this.transceivers[sdpMLineIndex].bufferedCandidateEvents=null;iceGatherer.removeEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);iceGatherer.onlocalcandidate=function(evt){if(pc.usingBundle&&sdpMLineIndex>0){return;}
+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==='new'||iceGatherer.state==='gathering'){iceGatherer.state='completed';}}else{if(iceGatherer.state==='new'){iceGatherer.state='gathering';}
+cand.component=1;cand.ufrag=iceGatherer.getLocalParameters().usernameFragment;var serializedCandidate=SDPUtils.writeCandidate(cand);event.candidate=Object.assign(event.candidate,SDPUtils.parseCandidate(serializedCandidate));event.candidate.candidate=serializedCandidate;event.candidate.toJSON=function(){return{candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex,usernameFragment:event.candidate.usernameFragment};};}
+var sections=SDPUtils.getMediaSections(pc._localDescription.sdp);if(!end){sections[event.candidate.sdpMLineIndex]+='a='+event.candidate.candidate+'\r\n';}else{sections[event.candidate.sdpMLineIndex]+='a=end-of-candidates\r\n';}
+pc._localDescription.sdp=SDPUtils.getDescription(pc._localDescription.sdp)+
+sections.join('');var complete=pc.transceivers.every(function(transceiver){return transceiver.iceGatherer&&transceiver.iceGatherer.state==='completed';});if(pc.iceGatheringState!=='gathering'){pc.iceGatheringState='gathering';pc._emitGatheringStateChange();}
+if(!end){pc._dispatchEvent('icecandidate',event);}
+if(complete){pc._dispatchEvent('icecandidate',new Event('icecandidate'));pc.iceGatheringState='complete';pc._emitGatheringStateChange();}};window.setTimeout(function(){bufferedCandidateEvents.forEach(function(e){iceGatherer.onlocalcandidate(e);});},0);};RTCPeerConnection.prototype._createIceAndDtlsTransports=function(){var pc=this;var iceTransport=new window.RTCIceTransport(null);iceTransport.onicestatechange=function(){pc._updateIceConnectionState();pc._updateConnectionState();};var dtlsTransport=new window.RTCDtlsTransport(iceTransport);dtlsTransport.ondtlsstatechange=function(){pc._updateConnectionState();};dtlsTransport.onerror=function(){Object.defineProperty(dtlsTransport,'state',{value:'failed',writable:true});pc._updateConnectionState();};return{iceTransport:iceTransport,dtlsTransport:dtlsTransport};};RTCPeerConnection.prototype._disposeIceAndDtlsTransports=function(sdpMLineIndex){var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer){delete iceGatherer.onlocalcandidate;delete this.transceivers[sdpMLineIndex].iceGatherer;}
+var iceTransport=this.transceivers[sdpMLineIndex].iceTransport;if(iceTransport){delete iceTransport.onicestatechange;delete this.transceivers[sdpMLineIndex].iceTransport;}
+var dtlsTransport=this.transceivers[sdpMLineIndex].dtlsTransport;if(dtlsTransport){delete dtlsTransport.ondtlsstatechange;delete dtlsTransport.onerror;delete this.transceivers[sdpMLineIndex].dtlsTransport;}};RTCPeerConnection.prototype._transceive=function(transceiver,send,recv){var params=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);if(send&&transceiver.rtpSender){params.encodings=transceiver.sendEncodingParameters;params.rtcp={cname:SDPUtils.localCName,compound:transceiver.rtcpParameters.compound};if(transceiver.recvEncodingParameters.length){params.rtcp.ssrc=transceiver.recvEncodingParameters[0].ssrc;}
+transceiver.rtpSender.send(params);}
+if(recv&&transceiver.rtpReceiver&&params.codecs.length>0){if(transceiver.kind==='video'&&transceiver.recvEncodingParameters&&edgeVersion<15019){transceiver.recvEncodingParameters.forEach(function(p){delete p.rtx;});}
+if(transceiver.recvEncodingParameters.length){params.encodings=transceiver.recvEncodingParameters;}else{params.encodings=[{}];}
+params.rtcp={compound:transceiver.rtcpParameters.compound};if(transceiver.rtcpParameters.cname){params.rtcp.cname=transceiver.rtcpParameters.cname;}
+if(transceiver.sendEncodingParameters.length){params.rtcp.ssrc=transceiver.sendEncodingParameters[0].ssrc;}
+transceiver.rtpReceiver.receive(params);}};RTCPeerConnection.prototype.setLocalDescription=function(description){var pc=this;if(['offer','answer'].indexOf(description.type)===-1){return Promise.reject(makeError('TypeError','Unsupported type "'+description.type+'"'));}
+if(!isActionAllowedInSignalingState('setLocalDescription',description.type,pc.signalingState)||pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set local '+description.type+' in state '+pc.signalingState));}
+var sections;var sessionpart;if(description.type==='offer'){sections=SDPUtils.splitSections(description.sdp);sessionpart=sections.shift();sections.forEach(function(mediaSection,sdpMLineIndex){var caps=SDPUtils.parseRtpParameters(mediaSection);pc.transceivers[sdpMLineIndex].localCapabilities=caps;});pc.transceivers.forEach(function(transceiver,sdpMLineIndex){pc._gather(transceiver.mid,sdpMLineIndex);});}else if(description.type==='answer'){sections=SDPUtils.splitSections(pc._remoteDescription.sdp);sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver=pc.transceivers[sdpMLineIndex];var iceGatherer=transceiver.iceGatherer;var iceTransport=transceiver.iceTransport;var dtlsTransport=transceiver.dtlsTransport;var localCapabilities=transceiver.localCapabilities;var remoteCapabilities=transceiver.remoteCapabilities;var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;if(!rejected&&!transceiver.rejected){var remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);var remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);if(isIceLite){remoteDtlsParameters.role='server';}
+if(!pc.usingBundle||sdpMLineIndex===0){pc._gather(transceiver.mid,sdpMLineIndex);if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,isIceLite?'controlling':'controlled');}
+if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}}
+var params=getCommonCapabilities(localCapabilities,remoteCapabilities);pc._transceive(transceiver,params.codecs.length>0,false);}});}
+pc._localDescription={type:description.type,sdp:description.sdp};if(description.type==='offer'){pc._updateSignalingState('have-local-offer');}else{pc._updateSignalingState('stable');}
+return Promise.resolve();};RTCPeerConnection.prototype.setRemoteDescription=function(description){var pc=this;if(['offer','answer'].indexOf(description.type)===-1){return Promise.reject(makeError('TypeError','Unsupported type "'+description.type+'"'));}
+if(!isActionAllowedInSignalingState('setRemoteDescription',description.type,pc.signalingState)||pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set remote '+description.type+' in state '+pc.signalingState));}
+var streams={};pc.remoteStreams.forEach(function(stream){streams[stream.id]=stream;});var receiverList=[];var sections=SDPUtils.splitSections(description.sdp);var sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;var usingBundle=SDPUtils.matchPrefix(sessionpart,'a=group:BUNDLE ').length>0;pc.usingBundle=usingBundle;var iceOptions=SDPUtils.matchPrefix(sessionpart,'a=ice-options:')[0];if(iceOptions){pc.canTrickleIceCandidates=iceOptions.substr(14).split(' ').indexOf('trickle')>=0;}else{pc.canTrickleIceCandidates=false;}
+sections.forEach(function(mediaSection,sdpMLineIndex){var lines=SDPUtils.splitLines(mediaSection);var kind=SDPUtils.getKind(mediaSection);var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;var protocol=lines[0].substr(2).split(' ')[2];var direction=SDPUtils.getDirection(mediaSection,sessionpart);var remoteMsid=SDPUtils.parseMsid(mediaSection);var mid=SDPUtils.getMid(mediaSection)||SDPUtils.generateIdentifier();if(rejected||(kind==='application'&&(protocol==='DTLS/SCTP'||protocol==='UDP/DTLS/SCTP'))){pc.transceivers[sdpMLineIndex]={mid:mid,kind:kind,protocol:protocol,rejected:true};return;}
+if(!rejected&&pc.transceivers[sdpMLineIndex]&&pc.transceivers[sdpMLineIndex].rejected){pc.transceivers[sdpMLineIndex]=pc._createTransceiver(kind,true);}
+var transceiver;var iceGatherer;var iceTransport;var dtlsTransport;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 rtcpParameters=SDPUtils.parseRtcpParameters(mediaSection);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'||description.type==='answer')&&!rejected&&usingBundle&&sdpMLineIndex>0&&pc.transceivers[sdpMLineIndex]){pc._disposeIceAndDtlsTransports(sdpMLineIndex);pc.transceivers[sdpMLineIndex].iceGatherer=pc.transceivers[0].iceGatherer;pc.transceivers[sdpMLineIndex].iceTransport=pc.transceivers[0].iceTransport;pc.transceivers[sdpMLineIndex].dtlsTransport=pc.transceivers[0].dtlsTransport;if(pc.transceivers[sdpMLineIndex].rtpSender){pc.transceivers[sdpMLineIndex].rtpSender.setTransport(pc.transceivers[0].dtlsTransport);}
+if(pc.transceivers[sdpMLineIndex].rtpReceiver){pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(pc.transceivers[0].dtlsTransport);}}
+if(description.type==='offer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex]||pc._createTransceiver(kind);transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,usingBundle);}
+if(cands.length&&transceiver.iceTransport.state==='new'){if(isComplete&&(!usingBundle||sdpMLineIndex===0)){transceiver.iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}}
+localCapabilities=window.RTCRtpReceiver.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});}
+sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+2)*1001}];var isNewTrack=false;if(direction==='sendrecv'||direction==='sendonly'){isNewTrack=!transceiver.rtpReceiver;rtpReceiver=transceiver.rtpReceiver||new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);if(isNewTrack){var stream;track=rtpReceiver.track;if(remoteMsid&&remoteMsid.stream==='-'){}else if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();Object.defineProperty(streams[remoteMsid.stream],'id',{get:function(){return remoteMsid.stream;}});}
+Object.defineProperty(track,'id',{get:function(){return remoteMsid.track;}});stream=streams[remoteMsid.stream];}else{if(!streams.default){streams.default=new window.MediaStream();}
+stream=streams.default;}
+if(stream){addTrackToStreamAndFireEvent(track,stream);transceiver.associatedRemoteMediaStreams.push(stream);}
+receiverList.push([track,rtpReceiver,stream]);}}else if(transceiver.rtpReceiver&&transceiver.rtpReceiver.track){transceiver.associatedRemoteMediaStreams.forEach(function(s){var nativeTrack=s.getTracks().find(function(t){return t.id===transceiver.rtpReceiver.track.id;});if(nativeTrack){removeTrackFromStreamAndFireEvent(nativeTrack,s);}});transceiver.associatedRemoteMediaStreams=[];}
+transceiver.localCapabilities=localCapabilities;transceiver.remoteCapabilities=remoteCapabilities;transceiver.rtpReceiver=rtpReceiver;transceiver.rtcpParameters=rtcpParameters;transceiver.sendEncodingParameters=sendEncodingParameters;transceiver.recvEncodingParameters=recvEncodingParameters;pc._transceive(pc.transceivers[sdpMLineIndex],false,isNewTrack);}else if(description.type==='answer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex];iceGatherer=transceiver.iceGatherer;iceTransport=transceiver.iceTransport;dtlsTransport=transceiver.dtlsTransport;rtpReceiver=transceiver.rtpReceiver;sendEncodingParameters=transceiver.sendEncodingParameters;localCapabilities=transceiver.localCapabilities;pc.transceivers[sdpMLineIndex].recvEncodingParameters=recvEncodingParameters;pc.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities;pc.transceivers[sdpMLineIndex].rtcpParameters=rtcpParameters;if(cands.length&&iceTransport.state==='new'){if((isIceLite||isComplete)&&(!usingBundle||sdpMLineIndex===0)){iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}}
+if(!usingBundle||sdpMLineIndex===0){if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,'controlling');}
+if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}}
+var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;}
+pc._transceive(transceiver,direction==='sendrecv'||direction==='recvonly',direction==='sendrecv'||direction==='sendonly');if(rtpReceiver&&(direction==='sendrecv'||direction==='sendonly')){track=rtpReceiver.track;if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();}
+addTrackToStreamAndFireEvent(track,streams[remoteMsid.stream]);receiverList.push([track,rtpReceiver,streams[remoteMsid.stream]]);}else{if(!streams.default){streams.default=new window.MediaStream();}
+addTrackToStreamAndFireEvent(track,streams.default);receiverList.push([track,rtpReceiver,streams.default]);}}else{delete transceiver.rtpReceiver;}}});if(pc._dtlsRole===undefined){pc._dtlsRole=description.type==='offer'?'active':'passive';}
+pc._remoteDescription={type:description.type,sdp:description.sdp};if(description.type==='offer'){pc._updateSignalingState('have-remote-offer');}else{pc._updateSignalingState('stable');}
+Object.keys(streams).forEach(function(sid){var stream=streams[sid];if(stream.getTracks().length){if(pc.remoteStreams.indexOf(stream)===-1){pc.remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;window.setTimeout(function(){pc._dispatchEvent('addstream',event);});}
+receiverList.forEach(function(item){var track=item[0];var receiver=item[1];if(stream.id!==item[2].id){return;}
+fireAddTrack(pc,track,receiver,[stream]);});}});receiverList.forEach(function(item){if(item[2]){return;}
+fireAddTrack(pc,item[0],item[1],[]);});window.setTimeout(function(){if(!(pc&&pc.transceivers)){return;}
+pc.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.iceTransport.state==='new'&&transceiver.iceTransport.getRemoteCandidates().length>0){console.warn('Timeout for addRemoteCandidate. Consider sending '+'an end-of-candidates notification');transceiver.iceTransport.addRemoteCandidate({});}});},4000);return Promise.resolve();};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._isClosed=true;this._updateSignalingState('closed');};RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState;var event=new Event('signalingstatechange');this._dispatchEvent('signalingstatechange',event);};RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){var pc=this;if(this.signalingState!=='stable'||this.needNegotiation===true){return;}
+this.needNegotiation=true;window.setTimeout(function(){if(pc.needNegotiation){pc.needNegotiation=false;var event=new Event('negotiationneeded');pc._dispatchEvent('negotiationneeded',event);}},0);};RTCPeerConnection.prototype._updateIceConnectionState=function(){var newState;var states={'new':0,closed:0,checking:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&!transceiver.rejected){states[transceiver.iceTransport.state]++;}});newState='new';if(states.failed>0){newState='failed';}else if(states.checking>0){newState='checking';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0){newState='connected';}else if(states.completed>0){newState='completed';}
+if(newState!==this.iceConnectionState){this.iceConnectionState=newState;var event=new Event('iceconnectionstatechange');this._dispatchEvent('iceconnectionstatechange',event);}};RTCPeerConnection.prototype._updateConnectionState=function(){var newState;var states={'new':0,closed:0,connecting:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.dtlsTransport&&!transceiver.rejected){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){newState='connecting';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0){newState='connected';}
+if(newState!==this.connectionState){this.connectionState=newState;var event=new Event('connectionstatechange');this._dispatchEvent('connectionstatechange',event);}};RTCPeerConnection.prototype.createOffer=function(){var pc=this;if(pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createOffer after close'));}
+var numAudioTracks=pc.transceivers.filter(function(t){return t.kind==='audio';}).length;var numVideoTracks=pc.transceivers.filter(function(t){return t.kind==='video';}).length;var offerOptions=arguments[0];if(offerOptions){if(offerOptions.mandatory||offerOptions.optional){throw new TypeError('Legacy mandatory/optional constraints not supported.');}
+if(offerOptions.offerToReceiveAudio!==undefined){if(offerOptions.offerToReceiveAudio===true){numAudioTracks=1;}else if(offerOptions.offerToReceiveAudio===false){numAudioTracks=0;}else{numAudioTracks=offerOptions.offerToReceiveAudio;}}
+if(offerOptions.offerToReceiveVideo!==undefined){if(offerOptions.offerToReceiveVideo===true){numVideoTracks=1;}else if(offerOptions.offerToReceiveVideo===false){numVideoTracks=0;}else{numVideoTracks=offerOptions.offerToReceiveVideo;}}}
+pc.transceivers.forEach(function(transceiver){if(transceiver.kind==='audio'){numAudioTracks--;if(numAudioTracks<0){transceiver.wantReceive=false;}}else if(transceiver.kind==='video'){numVideoTracks--;if(numVideoTracks<0){transceiver.wantReceive=false;}}});while(numAudioTracks>0||numVideoTracks>0){if(numAudioTracks>0){pc._createTransceiver('audio');numAudioTracks--;}
+if(numVideoTracks>0){pc._createTransceiver('video');numVideoTracks--;}}
+var sdp=SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,pc._sdpSessionVersion++);pc.transceivers.forEach(function(transceiver,sdpMLineIndex){var track=transceiver.track;var kind=transceiver.kind;var mid=transceiver.mid||SDPUtils.generateIdentifier();transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,pc.usingBundle);}
+var localCapabilities=window.RTCRtpSender.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});}
+localCapabilities.codecs.forEach(function(codec){if(codec.name==='H264'&&codec.parameters['level-asymmetry-allowed']===undefined){codec.parameters['level-asymmetry-allowed']='1';}
+if(transceiver.remoteCapabilities&&transceiver.remoteCapabilities.codecs){transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec){if(codec.name.toLowerCase()===remoteCodec.name.toLowerCase()&&codec.clockRate===remoteCodec.clockRate){codec.preferredPayloadType=remoteCodec.payloadType;}});}});localCapabilities.headerExtensions.forEach(function(hdrExt){var remoteExtensions=transceiver.remoteCapabilities&&transceiver.remoteCapabilities.headerExtensions||[];remoteExtensions.forEach(function(rHdrExt){if(hdrExt.uri===rHdrExt.uri){hdrExt.id=rHdrExt.id;}});});var sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+1)*1001}];if(track){if(edgeVersion>=15019&&kind==='video'&&!sendEncodingParameters[0].rtx){sendEncodingParameters[0].rtx={ssrc:sendEncodingParameters[0].ssrc+1};}}
+if(transceiver.wantReceive){transceiver.rtpReceiver=new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);}
+transceiver.localCapabilities=localCapabilities;transceiver.sendEncodingParameters=sendEncodingParameters;});if(pc._config.bundlePolicy!=='max-compat'){sdp+='a=group:BUNDLE '+pc.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';}
+sdp+='a=ice-options:trickle\r\n';pc.transceivers.forEach(function(transceiver,sdpMLineIndex){sdp+=writeMediaSection(transceiver,transceiver.localCapabilities,'offer',transceiver.stream,pc._dtlsRole);sdp+='a=rtcp-rsize\r\n';if(transceiver.iceGatherer&&pc.iceGatheringState!=='new'&&(sdpMLineIndex===0||!pc.usingBundle)){transceiver.iceGatherer.getLocalCandidates().forEach(function(cand){cand.component=1;sdp+='a='+SDPUtils.writeCandidate(cand)+'\r\n';});if(transceiver.iceGatherer.state==='completed'){sdp+='a=end-of-candidates\r\n';}}});var desc=new window.RTCSessionDescription({type:'offer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.createAnswer=function(){var pc=this;if(pc._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createAnswer after close'));}
+if(!(pc.signalingState==='have-remote-offer'||pc.signalingState==='have-local-pranswer')){return Promise.reject(makeError('InvalidStateError','Can not call createAnswer in signalingState '+pc.signalingState));}
+var sdp=SDPUtils.writeSessionBoilerplate(pc._sdpSessionId,pc._sdpSessionVersion++);if(pc.usingBundle){sdp+='a=group:BUNDLE '+pc.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';}
+sdp+='a=ice-options:trickle\r\n';var mediaSectionsInOffer=SDPUtils.getMediaSections(pc._remoteDescription.sdp).length;pc.transceivers.forEach(function(transceiver,sdpMLineIndex){if(sdpMLineIndex+1>mediaSectionsInOffer){return;}
+if(transceiver.rejected){if(transceiver.kind==='application'){if(transceiver.protocol==='DTLS/SCTP'){sdp+='m=application 0 DTLS/SCTP 5000\r\n';}else{sdp+='m=application 0 '+transceiver.protocol+' webrtc-datachannel\r\n';}}else if(transceiver.kind==='audio'){sdp+='m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n'+'a=rtpmap:0 PCMU/8000\r\n';}else if(transceiver.kind==='video'){sdp+='m=video 0 UDP/TLS/RTP/SAVPF 120\r\n'+'a=rtpmap:120 VP8/90000\r\n';}
+sdp+='c=IN IP4 0.0.0.0\r\n'+'a=inactive\r\n'+'a=mid:'+transceiver.mid+'\r\n';return;}
+if(transceiver.stream){var localTrack;if(transceiver.kind==='audio'){localTrack=transceiver.stream.getAudioTracks()[0];}else if(transceiver.kind==='video'){localTrack=transceiver.stream.getVideoTracks()[0];}
+if(localTrack){if(edgeVersion>=15019&&transceiver.kind==='video'&&!transceiver.sendEncodingParameters[0].rtx){transceiver.sendEncodingParameters[0].rtx={ssrc:transceiver.sendEncodingParameters[0].ssrc+1};}}}
+var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;}
+sdp+=writeMediaSection(transceiver,commonCapabilities,'answer',transceiver.stream,pc._dtlsRole);if(transceiver.rtcpParameters&&transceiver.rtcpParameters.reducedSize){sdp+='a=rtcp-rsize\r\n';}});var desc=new window.RTCSessionDescription({type:'answer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.addIceCandidate=function(candidate){var pc=this;var sections;if(candidate&&!(candidate.sdpMLineIndex!==undefined||candidate.sdpMid)){return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required'));}
+return new Promise(function(resolve,reject){if(!pc._remoteDescription){return reject(makeError('InvalidStateError','Can not add ICE candidate without a remote description'));}else if(!candidate||candidate.candidate===''){for(var j=0;j<pc.transceivers.length;j++){if(pc.transceivers[j].rejected){continue;}
+pc.transceivers[j].iceTransport.addRemoteCandidate({});sections=SDPUtils.getMediaSections(pc._remoteDescription.sdp);sections[j]+='a=end-of-candidates\r\n';pc._remoteDescription.sdp=SDPUtils.getDescription(pc._remoteDescription.sdp)+
+sections.join('');if(pc.usingBundle){break;}}}else{var sdpMLineIndex=candidate.sdpMLineIndex;if(candidate.sdpMid){for(var i=0;i<pc.transceivers.length;i++){if(pc.transceivers[i].mid===candidate.sdpMid){sdpMLineIndex=i;break;}}}
+var transceiver=pc.transceivers[sdpMLineIndex];if(transceiver){if(transceiver.rejected){return resolve();}
+var cand=Object.keys(candidate.candidate).length>0?SDPUtils.parseCandidate(candidate.candidate):{};if(cand.protocol==='tcp'&&(cand.port===0||cand.port===9)){return resolve();}
+if(cand.component&&cand.component!==1){return resolve();}
+if(sdpMLineIndex===0||(sdpMLineIndex>0&&transceiver.iceTransport!==pc.transceivers[0].iceTransport)){if(!maybeAddCandidate(transceiver.iceTransport,cand)){return reject(makeError('OperationError','Can not add ICE candidate'));}}
+var candidateString=candidate.candidate.trim();if(candidateString.indexOf('a=')===0){candidateString=candidateString.substr(2);}
+sections=SDPUtils.getMediaSections(pc._remoteDescription.sdp);sections[sdpMLineIndex]+='a='+
+(cand.type?candidateString:'end-of-candidates')
++'\r\n';pc._remoteDescription.sdp=SDPUtils.getDescription(pc._remoteDescription.sdp)+
+sections.join('');}else{return reject(makeError('OperationError','Can not add ICE candidate'));}}
+resolve();});};RTCPeerConnection.prototype.getStats=function(selector){if(selector&&selector instanceof window.MediaStreamTrack){var senderOrReceiver=null;this.transceivers.forEach(function(transceiver){if(transceiver.rtpSender&&transceiver.rtpSender.track===selector){senderOrReceiver=transceiver.rtpSender;}else if(transceiver.rtpReceiver&&transceiver.rtpReceiver.track===selector){senderOrReceiver=transceiver.rtpReceiver;}});if(!senderOrReceiver){throw makeError('InvalidAccessError','Invalid selector.');}
+return senderOrReceiver.getStats();}
+var promises=[];this.transceivers.forEach(function(transceiver){['rtpSender','rtpReceiver','iceGatherer','iceTransport','dtlsTransport'].forEach(function(method){if(transceiver[method]){promises.push(transceiver[method].getStats());}});});return Promise.all(promises).then(function(allStats){var results=new Map();allStats.forEach(function(stats){stats.forEach(function(stat){results.set(stat.id,stat);});});return results;});};var ortcObjects=['RTCRtpSender','RTCRtpReceiver','RTCIceGatherer','RTCIceTransport','RTCDtlsTransport'];ortcObjects.forEach(function(ortcObjectName){var obj=window[ortcObjectName];if(obj&&obj.prototype&&obj.prototype.getStats){var nativeGetstats=obj.prototype.getStats;obj.prototype.getStats=function(){return nativeGetstats.apply(this).then(function(nativeStats){var mapStats=new Map();Object.keys(nativeStats).forEach(function(id){nativeStats[id].type=fixStatsType(nativeStats[id]);mapStats.set(id,nativeStats[id]);});return mapStats;});};}});var methods=['createOffer','createAnswer'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[0]==='function'||typeof args[1]==='function'){return nativeMethod.apply(this,[arguments[2]]).then(function(description){if(typeof args[0]==='function'){args[0].apply(null,[description]);}},function(error){if(typeof args[1]==='function'){args[1].apply(null,[error]);}});}
+return nativeMethod.apply(this,arguments);};});methods=['setLocalDescription','setRemoteDescription','addIceCandidate'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'||typeof args[2]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}},function(error){if(typeof args[2]==='function'){args[2].apply(null,[error]);}});}
+return nativeMethod.apply(this,arguments);};});['getStats'].forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}});}
+return nativeMethod.apply(this,arguments);};});return RTCPeerConnection;};},{"sdp":17}],17:[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.getDescription=function(blob){var sections=SDPUtils.splitSections(blob);return sections&&sections[0];};SDPUtils.getMediaSections=function(blob){var sections=SDPUtils.splitSections(blob);sections.shift();return sections;};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:parseInt(parts[1],10),protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],address: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;case'ufrag':candidate.ufrag=parts[i+1];candidate.usernameFragment=parts[i+1];break;default:candidate[parts[i]]=parts[i+1];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.address||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);}
+if(candidate.usernameFragment||candidate.ufrag){sdp.push('ufrag');sdp.push(candidate.usernameFragment||candidate.ufrag);}
+return'candidate:'+sdp.join(' ');};SDPUtils.parseIceOptions=function(line){return line.substr(14).split(' ');};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.channels=parts.length===3?parseInt(parts[2],10):1;parsed.numChannels=parsed.channels;return parsed;};SDPUtils.writeRtpMap=function(codec){var pt=codec.payloadType;if(codec.preferredPayloadType!==undefined){pt=codec.preferredPayloadType;}
+var channels=codec.channels||codec.numChannels||1;return'a=rtpmap:'+pt+' '+codec.name+'/'+codec.clockRate+
+(channels!==1?'/'+channels:'')+'\r\n';};SDPUtils.parseExtmap=function(line){var parts=line.substr(9).split(' ');return{id:parseInt(parts[0],10),direction:parts[0].indexOf('/')>0?parts[0].split('/')[1]:'sendrecv',uri:parts[1]};};SDPUtils.writeExtmap=function(headerExtension){return'a=extmap:'+(headerExtension.id||headerExtension.preferredId)+
+(headerExtension.direction&&headerExtension.direction!=='sendrecv'?'/'+headerExtension.direction:'')+' '+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){if(codec.parameters[param]){params.push(param+'='+codec.parameters[param]);}else{params.push(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.parseSsrcGroup=function(line){var parts=line.substr(13).split(' ');return{semantics:parts.shift(),ssrcs:parts.map(function(ssrc){return parseInt(ssrc,10);})};};SDPUtils.getMid=function(mediaSection){var mid=SDPUtils.matchPrefix(mediaSection,'a=mid:')[0];if(mid){return mid.substr(6);}};SDPUtils.parseFingerprint=function(line){var parts=line.substr(14).split(' ');return{algorithm:parts[0].toLowerCase(),value:parts[1]};};SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.matchPrefix(mediaSection+sessionpart,'a=fingerprint:');return{role:'auto',fingerprints:lines.map(SDPUtils.parseFingerprint)};};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);});var maxptime=0;caps.codecs.forEach(function(codec){if(codec.maxptime>maxptime){maxptime=codec.maxptime;}});if(maxptime>0){sdp+='a=maxptime:'+maxptime+'\r\n';}
+sdp+='a=rtcp-mux\r\n';if(caps.headerExtensions){caps.headerExtensions.forEach(function(extension){sdp+=SDPUtils.writeExtmap(extension);});}
+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.substr(17).split(' ');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)};if(primarySsrc&&secondarySsrc){encParam.rtx={ssrc:secondarySsrc};}
+encodingParameters.push(encParam);if(hasRed){encParam=JSON.parse(JSON.stringify(encParam));encParam.fec={ssrc:primarySsrc,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)*1000*0.95
+-(50*40*8);}else{bandwidth=undefined;}
+encodingParameters.forEach(function(params){params.maxBitrate=bandwidth;});}
+return encodingParameters;};SDPUtils.parseRtcpParameters=function(mediaSection){var rtcpParameters={};var remoteSsrc=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(obj){return obj.attribute==='cname';})[0];if(remoteSsrc){rtcpParameters.cname=remoteSsrc.value;rtcpParameters.ssrc=remoteSsrc.ssrc;}
+var rsize=SDPUtils.matchPrefix(mediaSection,'a=rtcp-rsize');rtcpParameters.reducedSize=rsize.length>0;rtcpParameters.compound=rsize.length===0;var mux=SDPUtils.matchPrefix(mediaSection,'a=rtcp-mux');rtcpParameters.mux=mux.length>0;return rtcpParameters;};SDPUtils.parseMsid=function(mediaSection){var parts;var spec=SDPUtils.matchPrefix(mediaSection,'a=msid:');if(spec.length===1){parts=spec[0].substr(7).split(' ');return{stream:parts[0],track:parts[1]};}
+var planB=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(msidParts){return msidParts.attribute==='msid';});if(planB.length>0){parts=planB[0].value.split(' ');return{stream:parts[0],track:parts[1]};}};SDPUtils.generateSessionId=function(){return Math.random().toString().substr(2,21);};SDPUtils.writeSessionBoilerplate=function(sessId,sessVer,sessUser){var sessionId;var version=sessVer!==undefined?sessVer:2;if(sessId){sessionId=sessId;}else{sessionId=SDPUtils.generateSessionId();}
+var user=sessUser||'thisisadapterortc';return'v=0\r\n'+'o='+user+' '+sessionId+' '+version+' 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.direction){sdp+='a='+transceiver.direction+'\r\n';}else 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;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+
+transceiver.sendEncodingParameters[0].ssrc+' '+
+transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}}
+sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.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';};SDPUtils.getKind=function(mediaSection){var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');return mline[0].substr(2);};SDPUtils.isRejected=function(mediaSection){return mediaSection.split(' ',2)[1]==='0';};SDPUtils.parseMLine=function(mediaSection){var lines=SDPUtils.splitLines(mediaSection);var parts=lines[0].substr(2).split(' ');return{kind:parts[0],port:parseInt(parts[1],10),protocol:parts[2],fmt:parts.slice(3).join(' ')};};SDPUtils.parseOLine=function(mediaSection){var line=SDPUtils.matchPrefix(mediaSection,'o=')[0];var parts=line.substr(2).split(' ');return{username:parts[0],sessionId:parts[1],sessionVersion:parseInt(parts[2],10),netType:parts[3],addressType:parts[4],address:parts[5]};};SDPUtils.isValidSDP=function(blob){if(typeof blob!=='string'||blob.length===0){return false;}
+var lines=SDPUtils.splitLines(blob);for(var i=0;i<lines.length;i++){if(lines[i].length<2||lines[i].charAt(1)!=='='){return false;}}
+return true;};if(typeof module==='object'){module.exports=SDPUtils;}},{}]},{},[1])(1)});
\ No newline at end of file