You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by se...@apache.org on 2020/06/13 01:36:55 UTC

[openmeetings] branch feature/update-kurento-utils-js created (now 832b02f)

This is an automated email from the ASF dual-hosted git repository.

sebawagner pushed a change to branch feature/update-kurento-utils-js
in repository https://gitbox.apache.org/repos/asf/openmeetings.git.


      at 832b02f  Trial update of kurento utils.

This branch includes the following new commits:

     new 832b02f  Trial update of kurento utils.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[openmeetings] 01/01: Trial update of kurento utils.

Posted by se...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sebawagner pushed a commit to branch feature/update-kurento-utils-js
in repository https://gitbox.apache.org/repos/asf/openmeetings.git

commit 832b02f35d53cef5ff873ba16d46442ff4673d3a
Author: Sebastian Wagner <se...@apache.org>
AuthorDate: Sat Jun 13 13:36:34 2020 +1200

    Trial update of kurento utils.
---
 .../apache/openmeetings/web/room/kurento-utils.js  | 2434 +++++++++-----------
 1 file changed, 1115 insertions(+), 1319 deletions(-)

diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/kurento-utils.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/kurento-utils.js
index ce5bb19..5235508 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/kurento-utils.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/kurento-utils.js
@@ -1,1468 +1,1264 @@
 (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.kurentoUtils = 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)ret [...]
-var freeice = require('freeice');
-var inherits = require('inherits');
-var UAParser = require('ua-parser-js');
-window.uuidv4 = require('uuid/v4');
-var hark = require('hark');
-var EventEmitter = require('events').EventEmitter;
-var recursive = require('merge').recursive.bind(undefined, true);
-var sdpTranslator = require('sdp-translator');
-var logger = window.Logger || console;
-try {
-    require('kurento-browser-extensions');
-} catch (error) {
-    if (typeof getScreenConstraints === 'undefined') {
-        logger.warn('screen sharing is not available');
-        getScreenConstraints = function getScreenConstraints(sendSource, callback) {
-            callback(new Error('This library is not enabled for screen sharing'));
-        };
-    }
-}
-var MEDIA_CONSTRAINTS = {
-        audio: true,
-        video: {
-            width: 640,
-            framerate: 15
-        }
-    };
-var ua = window && window.navigator ? window.navigator.userAgent : '';
-var parser = new UAParser(ua);
-var browser = parser.getBrowser();
-var usePlanB = false;
-if (browser.name === 'Chrome' || browser.name === 'Chromium') {
-    logger.debug(browser.name + ': using SDP PlanB');
-    usePlanB = true;
-}
-function noop(error) {
-    if (error)
-        logger.error(error);
-}
-function trackStop(track) {
-    track.stop && track.stop();
-}
-function streamStop(stream) {
-    stream.getTracks().forEach(trackStop);
-}
-var dumpSDP = function (description) {
-    if (typeof description === 'undefined' || description === null) {
-        return '';
+require('kurento-utils')
+{}
+
+},{"kurento-utils":9}],2:[function(require,module,exports){
+/* jshint node: true */
+'use strict';
+
+var normalice = require('normalice');
+
+/**
+  # freeice
+
+  The `freeice` module is a simple way of getting random STUN or TURN server
+  for your WebRTC application.  The list of servers (just STUN at this stage)
+  were sourced from this [gist](https://gist.github.com/zziuni/3741933).
+
+  ## Example Use
+
+  The following demonstrates how you can use `freeice` with
+  [rtc-quickconnect](https://github.com/rtc-io/rtc-quickconnect):
+
+  <<< examples/quickconnect.js
+
+  As the `freeice` module generates ice servers in a list compliant with the
+  WebRTC spec you will be able to use it with raw `RTCPeerConnection`
+  constructors and other WebRTC libraries.
+
+  ## Hey, don't use my STUN/TURN server!
+
+  If for some reason your free STUN or TURN server ends up in the
+  list of servers ([stun](https://github.com/DamonOehlman/freeice/blob/master/stun.json) or
+  [turn](https://github.com/DamonOehlman/freeice/blob/master/turn.json))
+  that is used in this module, you can feel
+  free to open an issue on this repository and those servers will be removed
+  within 24 hours (or sooner).  This is the quickest and probably the most
+  polite way to have something removed (and provides us some visibility
+  if someone opens a pull request requesting that a server is added).
+
+  ## Please add my server!
+
+  If you have a server that you wish to add to the list, that's awesome! I'm
+  sure I speak on behalf of a whole pile of WebRTC developers who say thanks.
+  To get it into the list, feel free to either open a pull request or if you
+  find that process a bit daunting then just create an issue requesting
+  the addition of the server (make sure you provide all the details, and if
+  you have a Terms of Service then including that in the PR/issue would be
+  awesome).
+
+  ## I know of a free server, can I add it?
+
+  Sure, if you do your homework and make sure it is ok to use (I'm currently
+  in the process of reviewing the terms of those STUN servers included from
+  the original list).  If it's ok to go, then please see the previous entry
+  for how to add it.
+
+  ## Current List of Servers
+
+  * current as at the time of last `README.md` file generation
+
+  ### STUN
+
+  <<< stun.json
+
+  ### TURN
+
+  <<< turn.json
+
+**/
+
+var freeice = function(opts) {
+  // if a list of servers has been provided, then use it instead of defaults
+  var servers = {
+    stun: (opts || {}).stun || require('./stun.json'),
+    turn: (opts || {}).turn || require('./turn.json')
+  };
+
+  var stunCount = (opts || {}).stunCount || 2;
+  var turnCount = (opts || {}).turnCount || 0;
+  var selected;
+
+  function getServers(type, count) {
+    var out = [];
+    var input = [].concat(servers[type]);
+    var idx;
+
+    while (input.length && out.length < count) {
+      idx = (Math.random() * input.length) | 0;
+      out = out.concat(input.splice(idx, 1));
     }
-    return 'type: ' + description.type + '\r\n' + description.sdp;
-};
-function bufferizeCandidates(pc, onerror) {
-    var candidatesQueue = [];
-    function setSignalingstatechangeAccordingWwebBrowser(functionToExecute, pc) {
-        if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
-            pc.onsignalingstatechange = functionToExecute;
+
+    return out.map(function(url) {
+        //If it's a not a string, don't try to "normalice" it otherwise using type:url will screw it up
+        if ((typeof url !== 'string') && (! (url instanceof String))) {
+            return url;
         } else {
-            pc.addEventListener('signalingstatechange', functionToExecute);
-        }
-    }
-    var signalingstatechangeFunction = function () {
-        if (pc.signalingState === 'stable') {
-            while (candidatesQueue.length) {
-                var entry = candidatesQueue.shift();
-                pc.addIceCandidate(entry.candidate, entry.callback, entry.callback);
-            }
-        }
-    };
-    setSignalingstatechangeAccordingWwebBrowser(signalingstatechangeFunction, pc);
-    return function (candidate, callback) {
-        callback = callback || onerror;
-        switch (pc.signalingState) {
-        case 'closed':
-            callback(new Error('PeerConnection object is closed'));
-            break;
-        case 'stable':
-            if (pc.remoteDescription) {
-                pc.addIceCandidate(candidate, callback, callback);
-                break;
-            }
-        default:
-            candidatesQueue.push({
-                candidate: candidate,
-                callback: callback
-            });
-        }
-    };
-}
-function removeFIDFromOffer(sdp) {
-    var n = sdp.indexOf('a=ssrc-group:FID');
-    if (n > 0) {
-        return sdp.slice(0, n);
-    } else {
-        return sdp;
-    }
-}
-function getSimulcastInfo(videoStream) {
-    var videoTracks = videoStream.getVideoTracks();
-    if (!videoTracks.length) {
-        logger.warn('No video tracks available in the video stream');
-        return '';
-    }
-    var lines = [
-            'a=x-google-flag:conference',
-            'a=ssrc-group:SIM 1 2 3',
-            'a=ssrc:1 cname:localVideo',
-            'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id,
-            'a=ssrc:1 mslabel:' + videoStream.id,
-            'a=ssrc:1 label:' + videoTracks[0].id,
-            'a=ssrc:2 cname:localVideo',
-            'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id,
-            'a=ssrc:2 mslabel:' + videoStream.id,
-            'a=ssrc:2 label:' + videoTracks[0].id,
-            'a=ssrc:3 cname:localVideo',
-            'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id,
-            'a=ssrc:3 mslabel:' + videoStream.id,
-            'a=ssrc:3 label:' + videoTracks[0].id
-        ];
-    lines.push('');
-    return lines.join('\n');
-}
-function sleep(milliseconds) {
-    var start = new Date().getTime();
-    for (var i = 0; i < 10000000; i++) {
-        if (new Date().getTime() - start > milliseconds) {
-            break;
+            return normalice(type + ':' + url);
         }
+    });
+  }
+
+  // add stun servers
+  selected = [].concat(getServers('stun', stunCount));
+
+  if (turnCount) {
+    selected = selected.concat(getServers('turn', turnCount));
+  }
+
+  return selected;
+};
+
+module.exports = freeice;
+},{"./stun.json":3,"./turn.json":4,"normalice":11}],3:[function(require,module,exports){
+module.exports=[
+  "stun.l.google.com:19302",
+  "stun1.l.google.com:19302",
+  "stun2.l.google.com:19302",
+  "stun3.l.google.com:19302",
+  "stun4.l.google.com:19302",
+  "stun.ekiga.net",
+  "stun.ideasip.com",
+  "stun.schlund.de",
+  "stun.stunprotocol.org:3478",
+  "stun.voiparound.com",
+  "stun.voipbuster.com",
+  "stun.voipstunt.com",
+  "stun.voxgratia.org"
+]
+
+},{}],4:[function(require,module,exports){
+module.exports=[]
+
+},{}],5:[function(require,module,exports){
+var WildEmitter = require('wildemitter');
+
+function getMaxVolume (analyser, fftBins) {
+  var maxVolume = -Infinity;
+  analyser.getFloatFrequencyData(fftBins);
+
+  for(var i=4, ii=fftBins.length; i < ii; i++) {
+    if (fftBins[i] > maxVolume && fftBins[i] < 0) {
+      maxVolume = fftBins[i];
     }
+  };
+
+  return maxVolume;
 }
-function setIceCandidateAccordingWebBrowser(functionToExecute, pc) {
-    if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
-        pc.onicecandidate = functionToExecute;
-    } else {
-        pc.addEventListener('icecandidate', functionToExecute);
-    }
+
+
+var audioContextType;
+if (typeof window !== 'undefined') {
+  audioContextType = window.AudioContext || window.webkitAudioContext;
 }
-function WebRtcPeer(mode, options, callback) {
-    if (!(this instanceof WebRtcPeer)) {
-        return new WebRtcPeer(mode, options, callback);
-    }
-    WebRtcPeer.super_.call(this);
-    if (options instanceof Function) {
-        callback = options;
-        options = undefined;
-    }
-    options = options || {};
-    callback = (callback || noop).bind(this);
-    var self = this;
-    var localVideo = options.localVideo;
-    var remoteVideo = options.remoteVideo;
-    var videoStream = options.videoStream;
-    var audioStream = options.audioStream;
-    var mediaConstraints = options.mediaConstraints;
-    var connectionConstraints = options.connectionConstraints;
-    var pc = options.peerConnection;
-    var sendSource = options.sendSource || 'webcam';
-    var dataChannelConfig = options.dataChannelConfig;
-    var useDataChannels = options.dataChannels || false;
-    var dataChannel;
-    var guid = uuidv4();
-    var configuration = recursive({ iceServers: freeice() }, options.configuration);
-    var onicecandidate = options.onicecandidate;
-    if (onicecandidate)
-        this.on('icecandidate', onicecandidate);
-    var oncandidategatheringdone = options.oncandidategatheringdone;
-    if (oncandidategatheringdone) {
-        this.on('candidategatheringdone', oncandidategatheringdone);
-    }
-    var simulcast = options.simulcast;
-    var multistream = options.multistream;
-    var interop = new sdpTranslator.Interop();
-    var candidatesQueueOut = [];
-    var candidategatheringdone = false;
-    Object.defineProperties(this, {
-        'peerConnection': {
-            get: function () {
-                return pc;
-            }
-        },
-        'id': {
-            value: options.id || guid,
-            writable: false
-        },
-        'remoteVideo': {
-            get: function () {
-                return remoteVideo;
-            }
-        },
-        'localVideo': {
-            get: function () {
-                return localVideo;
-            }
-        },
-        'dataChannel': {
-            get: function () {
-                return dataChannel;
-            }
-        },
-        'currentFrame': {
-            get: function () {
-                if (!remoteVideo)
-                    return;
-                if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
-                    throw new Error('No video stream data available');
-                var canvas = document.createElement('canvas');
-                canvas.width = remoteVideo.videoWidth;
-                canvas.height = remoteVideo.videoHeight;
-                canvas.getContext('2d').drawImage(remoteVideo, 0, 0);
-                return canvas;
-            }
-        }
-    });
-    if (!pc) {
-        pc = new RTCPeerConnection(configuration);
-        if (useDataChannels && !dataChannel) {
-            var dcId = 'WebRtcPeer-' + self.id;
-            var dcOptions = undefined;
-            if (dataChannelConfig) {
-                dcId = dataChannelConfig.id || dcId;
-                dcOptions = dataChannelConfig.options;
-            }
-            dataChannel = pc.createDataChannel(dcId, dcOptions);
-            if (dataChannelConfig) {
-                dataChannel.onopen = dataChannelConfig.onopen;
-                dataChannel.onclose = dataChannelConfig.onclose;
-                dataChannel.onmessage = dataChannelConfig.onmessage;
-                dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow;
-                dataChannel.onerror = dataChannelConfig.onerror || noop;
-            }
-        }
-    }
-    if (!pc.getLocalStreams && pc.getSenders) {
-        pc.getLocalStreams = function () {
-            var stream = new MediaStream();
-            pc.getSenders().forEach(function (sender) {
-                stream.addTrack(sender.track);
-            });
-            return [stream];
-        };
-    }
-    if (!pc.getRemoteStreams && pc.getReceivers) {
-        pc.getRemoteStreams = function () {
-            var stream = new MediaStream();
-            pc.getReceivers().forEach(function (sender) {
-                stream.addTrack(sender.track);
-            });
-            return [stream];
-        };
-    }
-    var iceCandidateFunction = function (event) {
-        var candidate = event.candidate;
-        if (EventEmitter.listenerCount(self, 'icecandidate') || EventEmitter.listenerCount(self, 'candidategatheringdone')) {
-            if (candidate) {
-                var cand;
-                if (multistream && usePlanB) {
-                    cand = interop.candidateToUnifiedPlan(candidate);
-                } else {
-                    cand = candidate;
-                }
-                if (typeof AdapterJS === 'undefined') {
-                    self.emit('icecandidate', cand);
-                }
-                candidategatheringdone = false;
-            } else if (!candidategatheringdone) {
-                if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
-                    EventEmitter.prototype.emit('candidategatheringdone', cand);
-                } else {
-                    self.emit('candidategatheringdone');
-                }
-                candidategatheringdone = true;
-            }
-        } else if (!candidategatheringdone) {
-            candidatesQueueOut.push(candidate);
-            if (!candidate)
-                candidategatheringdone = true;
-        }
-    };
-    setIceCandidateAccordingWebBrowser(iceCandidateFunction, pc);
-    pc.onaddstream = options.onaddstream;
-    pc.onnegotiationneeded = options.onnegotiationneeded;
-    this.on('newListener', function (event, listener) {
-        if (event === 'icecandidate' || event === 'candidategatheringdone') {
-            while (candidatesQueueOut.length) {
-                var candidate = candidatesQueueOut.shift();
-                if (!candidate === (event === 'candidategatheringdone')) {
-                    listener(candidate);
-                }
-            }
-        }
-    });
-    var addIceCandidate = bufferizeCandidates(pc);
-    this.addIceCandidate = function (iceCandidate, callback) {
-        var candidate;
-        if (multistream && usePlanB) {
-            candidate = interop.candidateToPlanB(iceCandidate);
-        } else {
-            candidate = new RTCIceCandidate(iceCandidate);
-        }
-        logger.debug('Remote ICE candidate received', iceCandidate);
-        callback = (callback || noop).bind(this);
-        addIceCandidate(candidate, callback);
-    };
-    this.generateOffer = function (callback) {
-        callback = callback.bind(this);
-        if (mode === 'recvonly') {
-            var useAudio = mediaConstraints && typeof mediaConstraints.audio === 'boolean' ? mediaConstraints.audio : true;
-            var useVideo = mediaConstraints && typeof mediaConstraints.video === 'boolean' ? mediaConstraints.video : true;
-            if (useAudio) {
-                pc.addTransceiver('audio', { direction: 'recvonly' });
-            }
-            if (useVideo) {
-                pc.addTransceiver('video', { direction: 'recvonly' });
-            }
-        }
-        if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
-            var setLocalDescriptionOnSuccess = function () {
-                sleep(1000);
-                var localDescription = pc.localDescription;
-                logger.debug('Local description set\n', localDescription.sdp);
-                if (multistream && usePlanB) {
-                    localDescription = interop.toUnifiedPlan(localDescription);
-                    logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
-                }
-                callback(null, localDescription.sdp, self.processAnswer.bind(self));
-            };
-            var createOfferOnSuccess = function (offer) {
-                logger.debug('Created SDP offer');
-                logger.debug('Local description set\n', pc.localDescription);
-                pc.setLocalDescription(offer, setLocalDescriptionOnSuccess, callback);
-            };
-            pc.createOffer(createOfferOnSuccess, callback);
-        } else {
-            pc.createOffer().then(function (offer) {
-                logger.debug('Created SDP offer');
-                offer = mangleSdpToAddSimulcast(offer);
-                return pc.setLocalDescription(offer);
-            }).then(function () {
-                var localDescription = pc.localDescription;
-                logger.debug('Local description set\n', localDescription.sdp);
-                if (multistream && usePlanB) {
-                    localDescription = interop.toUnifiedPlan(localDescription);
-                    logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
-                }
-                callback(null, localDescription.sdp, self.processAnswer.bind(self));
-            }).catch(callback);
-        }
-    };
-    this.getLocalSessionDescriptor = function () {
-        return pc.localDescription;
-    };
-    this.getRemoteSessionDescriptor = function () {
-        return pc.remoteDescription;
-    };
-    function setRemoteVideo() {
-        if (remoteVideo) {
-            remoteVideo.pause();
-            var stream = pc.getRemoteStreams()[0];
-            remoteVideo.srcObject = stream;
-            logger.debug('Remote stream:', stream);
-            if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
-                remoteVideo = attachMediaStream(remoteVideo, stream);
-            } else {
-                remoteVideo.load();
-            }
-        }
-    }
-    this.showLocalVideo = function () {
-        localVideo.srcObject = videoStream;
-        localVideo.muted = true;
-        if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
-            localVideo = attachMediaStream(localVideo, videoStream);
-        }
-    };
-    this.send = function (data) {
-        if (dataChannel && dataChannel.readyState === 'open') {
-            dataChannel.send(data);
-        } else {
-            logger.warn('Trying to send data over a non-existing or closed data channel');
-        }
-    };
-    this.processAnswer = function (sdpAnswer, callback) {
-        callback = (callback || noop).bind(this);
-        var answer = new RTCSessionDescription({
-                type: 'answer',
-                sdp: sdpAnswer
-            });
-        if (multistream && usePlanB) {
-            var planBAnswer = interop.toPlanB(answer);
-            logger.debug('asnwer::planB', dumpSDP(planBAnswer));
-            answer = planBAnswer;
-        }
-        logger.debug('SDP answer received, setting remote description');
-        if (pc.signalingState === 'closed') {
-            return callback('PeerConnection is closed');
-        }
-        pc.setRemoteDescription(answer, function () {
-            setRemoteVideo();
-            callback();
-        }, callback);
-    };
-    this.processOffer = function (sdpOffer, callback) {
-        callback = callback.bind(this);
-        var offer = new RTCSessionDescription({
-                type: 'offer',
-                sdp: sdpOffer
-            });
-        if (multistream && usePlanB) {
-            var planBOffer = interop.toPlanB(offer);
-            logger.debug('offer::planB', dumpSDP(planBOffer));
-            offer = planBOffer;
-        }
-        logger.debug('SDP offer received, setting remote description');
-        if (pc.signalingState === 'closed') {
-            return callback('PeerConnection is closed');
-        }
-        pc.setRemoteDescription(offer).then(function () {
-            return setRemoteVideo();
-        }).then(function () {
-            return pc.createAnswer();
-        }).then(function (answer) {
-            answer = mangleSdpToAddSimulcast(answer);
-            logger.debug('Created SDP answer');
-            return pc.setLocalDescription(answer);
-        }).then(function () {
-            var localDescription = pc.localDescription;
-            if (multistream && usePlanB) {
-                localDescription = interop.toUnifiedPlan(localDescription);
-                logger.debug('answer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
-            }
-            logger.debug('Local description set\n', localDescription.sdp);
-            callback(null, localDescription.sdp);
-        }).catch(callback);
-    };
-    function mangleSdpToAddSimulcast(answer) {
-        if (simulcast) {
-            if (browser.name === 'Chrome' || browser.name === 'Chromium') {
-                logger.debug('Adding multicast info');
-                answer = new RTCSessionDescription({
-                    'type': answer.type,
-                    'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(videoStream)
-                });
-            } else {
-                logger.warn('Simulcast is only available in Chrome browser.');
-            }
-        }
-        return answer;
-    }
-    function start() {
-        if (pc.signalingState === 'closed') {
-            callback('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue');
-        }
-        if (videoStream && localVideo) {
-            self.showLocalVideo();
-        }
-        if (videoStream) {
-            videoStream.getTracks().forEach(function (track) {
-                pc.addTrack(track, videoStream);
-            });
-        }
-        if (audioStream) {
-            audioStream.getTracks().forEach(function (track) {
-                pc.addTrack(track, audioStream);
-            });
-        }
-        var browser = parser.getBrowser();
-        if (mode === 'sendonly' && (browser.name === 'Chrome' || browser.name === 'Chromium') && browser.major === 39) {
-            mode = 'sendrecv';
-        }
-        callback();
-    }
-    if (mode !== 'recvonly' && !videoStream && !audioStream) {
-        function getMedia(constraints) {
-            if (constraints === undefined) {
-                constraints = MEDIA_CONSTRAINTS;
-            }
-            if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
-                navigator.getUserMedia(constraints, function (stream) {
-                    videoStream = stream;
-                    start();
-                }, callback);
-            } else {
-                navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
-                    videoStream = stream;
-                    start();
-                }).catch(callback);
-            }
-        }
-        if (sendSource === 'webcam') {
-            getMedia(mediaConstraints);
-        } else {
-            getScreenConstraints(sendSource, function (error, constraints_) {
-                if (error)
-                    return callback(error);
-                constraints = [mediaConstraints];
-                constraints.unshift(constraints_);
-                getMedia(recursive.apply(undefined, constraints));
-            }, guid);
-        }
-    } else {
-        setTimeout(start, 0);
+// use a single audio context due to hardware limits
+var audioContext = null;
+module.exports = function(stream, options) {
+  var harker = new WildEmitter();
+
+  // make it not break in non-supported browsers
+  if (!audioContextType) return harker;
+
+  //Config
+  var options = options || {},
+      smoothing = (options.smoothing || 0.1),
+      interval = (options.interval || 50),
+      threshold = options.threshold,
+      play = options.play,
+      history = options.history || 10,
+      running = true;
+
+  // Ensure that just a single AudioContext is internally created
+  audioContext = options.audioContext || audioContext || new audioContextType();
+
+  var sourceNode, fftBins, analyser;
+
+  analyser = audioContext.createAnalyser();
+  analyser.fftSize = 512;
+  analyser.smoothingTimeConstant = smoothing;
+  fftBins = new Float32Array(analyser.frequencyBinCount);
+
+  if (stream.jquery) stream = stream[0];
+  if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) {
+    //Audio Tag
+    sourceNode = audioContext.createMediaElementSource(stream);
+    if (typeof play === 'undefined') play = true;
+    threshold = threshold || -50;
+  } else {
+    //WebRTC Stream
+    sourceNode = audioContext.createMediaStreamSource(stream);
+    threshold = threshold || -50;
+  }
+
+  sourceNode.connect(analyser);
+  if (play) analyser.connect(audioContext.destination);
+
+  harker.speaking = false;
+
+  harker.suspend = function() {
+    return audioContext.suspend();
+  }
+  harker.resume = function() {
+    return audioContext.resume();
+  }
+  Object.defineProperty(harker, 'state', { get: function() {
+    return audioContext.state;
+  }});
+  audioContext.onstatechange = function() {
+    harker.emit('state_change', audioContext.state);
+  }
+
+  harker.setThreshold = function(t) {
+    threshold = t;
+  };
+
+  harker.setInterval = function(i) {
+    interval = i;
+  };
+
+  harker.stop = function() {
+    running = false;
+    harker.emit('volume_change', -100, threshold);
+    if (harker.speaking) {
+      harker.speaking = false;
+      harker.emit('stopped_speaking');
     }
-    this.on('_dispose', function () {
-        if (localVideo) {
-            localVideo.pause();
-            localVideo.srcObject = null;
-            if (typeof AdapterJS === 'undefined') {
-                localVideo.load();
-            }
-            localVideo.muted = false;
+    analyser.disconnect();
+    sourceNode.disconnect();
+  };
+  harker.speakingHistory = [];
+  for (var i = 0; i < history; i++) {
+      harker.speakingHistory.push(0);
+  }
+
+  // Poll the analyser node to determine if speaking
+  // and emit events if changed
+  var looper = function() {
+    setTimeout(function() {
+
+      //check if stop has been called
+      if(!running) {
+        return;
+      }
+
+      var currentVolume = getMaxVolume(analyser, fftBins);
+
+      harker.emit('volume_change', currentVolume, threshold);
+
+      var history = 0;
+      if (currentVolume > threshold && !harker.speaking) {
+        // trigger quickly, short history
+        for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) {
+          history += harker.speakingHistory[i];
         }
-        if (remoteVideo) {
-            remoteVideo.pause();
-            remoteVideo.srcObject = null;
-            if (typeof AdapterJS === 'undefined') {
-                remoteVideo.load();
-            }
+        if (history >= 2) {
+          harker.speaking = true;
+          harker.emit('speaking');
         }
-        self.removeAllListeners();
-        if (window.cancelChooseDesktopMedia !== undefined) {
-            window.cancelChooseDesktopMedia(guid);
+      } else if (currentVolume < threshold && harker.speaking) {
+        for (var i = 0; i < harker.speakingHistory.length; i++) {
+          history += harker.speakingHistory[i];
         }
-    });
-}
-inherits(WebRtcPeer, EventEmitter);
-function createEnableDescriptor(type) {
-    var method = 'get' + type + 'Tracks';
-    return {
-        enumerable: true,
-        get: function () {
-            if (!this.peerConnection)
-                return;
-            var streams = this.peerConnection.getLocalStreams();
-            if (!streams.length)
-                return;
-            for (var i = 0, stream; stream = streams[i]; i++) {
-                var tracks = stream[method]();
-                for (var j = 0, track; track = tracks[j]; j++)
-                    if (!track.enabled)
-                        return false;
-            }
-            return true;
-        },
-        set: function (value) {
-            function trackSetEnable(track) {
-                track.enabled = value;
-            }
-            this.peerConnection.getLocalStreams().forEach(function (stream) {
-                stream[method]().forEach(trackSetEnable);
-            });
+        if (history == 0) {
+          harker.speaking = false;
+          harker.emit('stopped_speaking');
         }
-    };
+      }
+      harker.speakingHistory.shift();
+      harker.speakingHistory.push(0 + (currentVolume > threshold));
+
+      looper();
+    }, interval);
+  };
+  looper();
+
+  return harker;
 }
-Object.defineProperties(WebRtcPeer.prototype, {
-    'enabled': {
-        enumerable: true,
-        get: function () {
-            return this.audioEnabled && this.videoEnabled;
-        },
-        set: function (value) {
-            this.audioEnabled = this.videoEnabled = value;
-        }
-    },
-    'audioEnabled': createEnableDescriptor('Audio'),
-    'videoEnabled': createEnableDescriptor('Video')
-});
-WebRtcPeer.prototype.getLocalStream = function (index) {
-    if (this.peerConnection) {
-        return this.peerConnection.getLocalStreams()[index || 0];
-    }
-};
-WebRtcPeer.prototype.getRemoteStream = function (index) {
-    if (this.peerConnection) {
-        return this.peerConnection.getRemoteStreams()[index || 0];
-    }
-};
-WebRtcPeer.prototype.dispose = function () {
-    logger.debug('Disposing WebRtcPeer');
-    var pc = this.peerConnection;
-    var dc = this.dataChannel;
-    try {
-        if (dc) {
-            if (dc.signalingState === 'closed')
-                return;
-            dc.close();
-        }
-        if (pc) {
-            if (pc.signalingState === 'closed')
-                return;
-            pc.getLocalStreams().forEach(streamStop);
-            pc.close();
+
+},{"wildemitter":24}],6:[function(require,module,exports){
+if (typeof Object.create === 'function') {
+  // implementation from standard node.js 'util' module
+  module.exports = function inherits(ctor, superCtor) {
+    if (superCtor) {
+      ctor.super_ = superCtor
+      ctor.prototype = Object.create(superCtor.prototype, {
+        constructor: {
+          value: ctor,
+          enumerable: false,
+          writable: true,
+          configurable: true
         }
-    } catch (err) {
-        logger.warn('Exception disposing webrtc peer ' + err);
-    }
-    if (typeof AdapterJS === 'undefined') {
-        this.emit('_dispose');
-    }
-};
-function WebRtcPeerRecvonly(options, callback) {
-    if (!(this instanceof WebRtcPeerRecvonly)) {
-        return new WebRtcPeerRecvonly(options, callback);
-    }
-    WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback);
-}
-inherits(WebRtcPeerRecvonly, WebRtcPeer);
-function WebRtcPeerSendonly(options, callback) {
-    if (!(this instanceof WebRtcPeerSendonly)) {
-        return new WebRtcPeerSendonly(options, callback);
+      })
     }
-    WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback);
-}
-inherits(WebRtcPeerSendonly, WebRtcPeer);
-function WebRtcPeerSendrecv(options, callback) {
-    if (!(this instanceof WebRtcPeerSendrecv)) {
-        return new WebRtcPeerSendrecv(options, callback);
+  };
+} else {
+  // old school shim for old browsers
+  module.exports = function inherits(ctor, superCtor) {
+    if (superCtor) {
+      ctor.super_ = superCtor
+      var TempCtor = function () {}
+      TempCtor.prototype = superCtor.prototype
+      ctor.prototype = new TempCtor()
+      ctor.prototype.constructor = ctor
     }
-    WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback);
-}
-inherits(WebRtcPeerSendrecv, WebRtcPeer);
-function harkUtils(stream, options) {
-    return hark(stream, options);
-}
-exports.bufferizeCandidates = bufferizeCandidates;
-exports.WebRtcPeerRecvonly = WebRtcPeerRecvonly;
-exports.WebRtcPeerSendonly = WebRtcPeerSendonly;
-exports.WebRtcPeerSendrecv = WebRtcPeerSendrecv;
-exports.hark = harkUtils;
-exports.browser = browser;
-},{"events":4,"freeice":5,"hark":8,"inherits":9,"kurento-browser-extensions":10,"merge":11,"sdp-translator":18,"ua-parser-js":21,"uuid/v4":24}],2:[function(require,module,exports){
-if (window.addEventListener)
-    module.exports = require('./index');
-},{"./index":3}],3:[function(require,module,exports){
-var WebRtcPeer = require('./WebRtcPeer');
-exports.WebRtcPeer = WebRtcPeer;
-},{"./WebRtcPeer":1}],4:[function(require,module,exports){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-var objectCreate = Object.create || objectCreatePolyfill
-var objectKeys = Object.keys || objectKeysPolyfill
-var bind = Function.prototype.bind || functionBindPolyfill
-
-function EventEmitter() {
-  if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) {
-    this._events = objectCreate(null);
-    this._eventsCount = 0;
   }
-
-  this._maxListeners = this._maxListeners || undefined;
 }
-module.exports = EventEmitter;
-
-// Backwards-compat with node 0.10.x
-EventEmitter.EventEmitter = EventEmitter;
 
-EventEmitter.prototype._events = undefined;
-EventEmitter.prototype._maxListeners = undefined;
+},{}],7:[function(require,module,exports){
+// Does nothing at all.
 
-// By default EventEmitters will print a warning if more than 10 listeners are
-// added to it. This is a useful default which helps finding memory leaks.
-var defaultMaxListeners = 10;
+},{}],8:[function(require,module,exports){
+/*
+ * (C) Copyright 2014-2015 Kurento (http://kurento.org/)
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
-var hasDefineProperty;
-try {
-  var o = {};
-  if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 });
-  hasDefineProperty = o.x === 0;
-} catch (err) { hasDefineProperty = false }
-if (hasDefineProperty) {
-  Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
-    enumerable: true,
-    get: function() {
-      return defaultMaxListeners;
-    },
-    set: function(arg) {
-      // check whether the input is a positive number (whose value is zero or
-      // greater and not a NaN).
-      if (typeof arg !== 'number' || arg < 0 || arg !== arg)
-        throw new TypeError('"defaultMaxListeners" must be a positive number');
-      defaultMaxListeners = arg;
-    }
-  });
-} else {
-  EventEmitter.defaultMaxListeners = defaultMaxListeners;
-}
+var freeice = require('freeice')
+var inherits = require('inherits')
+var UAParser = require('ua-parser-js')
+var uuidv4 = require('uuid/v4')
+var hark = require('hark')
 
-// Obviously not all Emitters should be limited to 10. This function allows
-// that to be increased. Set to zero for unlimited.
-EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
-  if (typeof n !== 'number' || n < 0 || isNaN(n))
-    throw new TypeError('"n" argument must be a positive number');
-  this._maxListeners = n;
-  return this;
-};
+var EventEmitter = require('events').EventEmitter
+var recursive = require('merge').recursive.bind(undefined, true)
+var sdpTranslator = require('sdp-translator')
+var logger = window.Logger || console
 
-function $getMaxListeners(that) {
-  if (that._maxListeners === undefined)
-    return EventEmitter.defaultMaxListeners;
-  return that._maxListeners;
-}
+// var gUM = navigator.mediaDevices.getUserMedia || function (constraints) {
+//   return new Promise(navigator.getUserMedia(constraints, function (stream) {
+//     videoStream = stream
+//     start()
+//   }).eror(callback));
+// }
 
-EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
-  return $getMaxListeners(this);
-};
+try {
+  require('kurento-browser-extensions')
+} catch (error) {
+  if (typeof getScreenConstraints === 'undefined') {
+    logger.warn('screen sharing is not available')
 
-// These standalone emit* functions are used to optimize calling of event
-// handlers for fast cases because emit() itself often has a variable number of
-// arguments and can be deoptimized because of that. These functions always have
-// the same number of arguments and thus do not get deoptimized, so the code
-// inside them can execute faster.
-function emitNone(handler, isFn, self) {
-  if (isFn)
-    handler.call(self);
-  else {
-    var len = handler.length;
-    var listeners = arrayClone(handler, len);
-    for (var i = 0; i < len; ++i)
-      listeners[i].call(self);
+    getScreenConstraints = function getScreenConstraints(sendSource, callback) {
+      callback(new Error("This library is not enabled for screen sharing"))
+    }
   }
 }
-function emitOne(handler, isFn, self, arg1) {
-  if (isFn)
-    handler.call(self, arg1);
-  else {
-    var len = handler.length;
-    var listeners = arrayClone(handler, len);
-    for (var i = 0; i < len; ++i)
-      listeners[i].call(self, arg1);
+
+var MEDIA_CONSTRAINTS = {
+  audio: true,
+  video: {
+    width: 640,
+    framerate: 15
   }
 }
-function emitTwo(handler, isFn, self, arg1, arg2) {
-  if (isFn)
-    handler.call(self, arg1, arg2);
-  else {
-    var len = handler.length;
-    var listeners = arrayClone(handler, len);
-    for (var i = 0; i < len; ++i)
-      listeners[i].call(self, arg1, arg2);
-  }
+
+// Somehow, the UAParser constructor gets an empty window object.
+// We need to pass the user agent string in order to get information
+var ua = (window && window.navigator) ? window.navigator.userAgent : ''
+var parser = new UAParser(ua)
+var browser = parser.getBrowser()
+
+function insertScriptSrcInHtmlDom(scriptSrc) {
+  //Create a script tag
+  var script = document.createElement('script');
+  // Assign a URL to the script element
+  script.src = scriptSrc;
+  // Get the first script tag on the page (we'll insert our new one before it)
+  var ref = document.querySelector('script');
+  // Insert the new node before the reference node
+  ref.parentNode.insertBefore(script, ref);
 }
-function emitThree(handler, isFn, self, arg1, arg2, arg3) {
-  if (isFn)
-    handler.call(self, arg1, arg2, arg3);
-  else {
-    var len = handler.length;
-    var listeners = arrayClone(handler, len);
-    for (var i = 0; i < len; ++i)
-      listeners[i].call(self, arg1, arg2, arg3);
+
+function importScriptsDependsOnBrowser() {
+  if (browser.name === 'IE') {
+    insertScriptSrcInHtmlDom(
+      "https://cdn.temasys.io/adapterjs/0.15.x/adapter.debug.js");
   }
 }
 
-function emitMany(handler, isFn, self, args) {
-  if (isFn)
-    handler.apply(self, args);
-  else {
-    var len = handler.length;
-    var listeners = arrayClone(handler, len);
-    for (var i = 0; i < len; ++i)
-      listeners[i].apply(self, args);
-  }
+importScriptsDependsOnBrowser();
+var usePlanB = false
+if (browser.name === 'Chrome' || browser.name === 'Chromium') {
+  logger.debug(browser.name + ": using SDP PlanB")
+  usePlanB = true
 }
 
-EventEmitter.prototype.emit = function emit(type) {
-  var er, handler, len, args, i, events;
-  var doError = (type === 'error');
-
-  events = this._events;
-  if (events)
-    doError = (doError && events.error == null);
-  else if (!doError)
-    return false;
-
-  // If there is no 'error' event listener then throw.
-  if (doError) {
-    if (arguments.length > 1)
-      er = arguments[1];
-    if (er instanceof Error) {
-      throw er; // Unhandled 'error' event
-    } else {
-      // At least give some kind of context to the user
-      var err = new Error('Unhandled "error" event. (' + er + ')');
-      err.context = er;
-      throw err;
-    }
-    return false;
-  }
+function noop(error) {
+  if (error) logger.error(error)
+}
 
-  handler = events[type];
+function trackStop(track) {
+  track.stop && track.stop()
+}
 
-  if (!handler)
-    return false;
+function streamStop(stream) {
+  stream.getTracks().forEach(trackStop)
+}
 
-  var isFn = typeof handler === 'function';
-  len = arguments.length;
-  switch (len) {
-      // fast cases
-    case 1:
-      emitNone(handler, isFn, this);
-      break;
-    case 2:
-      emitOne(handler, isFn, this, arguments[1]);
-      break;
-    case 3:
-      emitTwo(handler, isFn, this, arguments[1], arguments[2]);
-      break;
-    case 4:
-      emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
-      break;
-      // slower
-    default:
-      args = new Array(len - 1);
-      for (i = 1; i < len; i++)
-        args[i - 1] = arguments[i];
-      emitMany(handler, isFn, this, args);
+/**
+ * Returns a string representation of a SessionDescription object.
+ */
+var dumpSDP = function (description) {
+  if (typeof description === 'undefined' || description === null) {
+    return ''
   }
 
-  return true;
-};
-
-function _addListener(target, type, listener, prepend) {
-  var m;
-  var events;
-  var existing;
+  return 'type: ' + description.type + '\r\n' + description.sdp
+}
 
-  if (typeof listener !== 'function')
-    throw new TypeError('"listener" argument must be a function');
+function bufferizeCandidates(pc, onerror) {
+  var candidatesQueue = []
 
-  events = target._events;
-  if (!events) {
-    events = target._events = objectCreate(null);
-    target._eventsCount = 0;
-  } else {
-    // To avoid recursion in the case that type === "newListener"! Before
-    // adding it to the listeners, first emit "newListener".
-    if (events.newListener) {
-      target.emit('newListener', type,
-          listener.listener ? listener.listener : listener);
-
-      // Re-assign `events` because a newListener handler could have caused the
-      // this._events to be assigned to a new object
-      events = target._events;
+  function setSignalingstatechangeAccordingWwebBrowser(functionToExecute, pc) {
+    if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser ===
+      'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
+      pc.onsignalingstatechange = functionToExecute;
+    } else {
+      pc.addEventListener('signalingstatechange', functionToExecute);
     }
-    existing = events[type];
+
   }
 
-  if (!existing) {
-    // Optimize the case of one listener. Don't need the extra array object.
-    existing = events[type] = listener;
-    ++target._eventsCount;
-  } else {
-    if (typeof existing === 'function') {
-      // Adding the second element, need to change to array.
-      existing = events[type] =
-          prepend ? [listener, existing] : [existing, listener];
-    } else {
-      // If we've already got an array, just append.
-      if (prepend) {
-        existing.unshift(listener);
-      } else {
-        existing.push(listener);
+  var signalingstatechangeFunction = function () {
+    if (pc.signalingState === 'stable') {
+      while (candidatesQueue.length) {
+        var entry = candidatesQueue.shift();
+        pc.addIceCandidate(entry.candidate, entry.callback, entry.callback);
       }
     }
+  };
+
+  setSignalingstatechangeAccordingWwebBrowser(signalingstatechangeFunction, pc);
+  return function (candidate, callback) {
+    callback = callback || onerror;
+    switch (pc.signalingState) {
+    case 'closed':
+      callback(new Error('PeerConnection object is closed'));
+      break;
+    case 'stable':
+      if (pc.remoteDescription) {
+        pc.addIceCandidate(candidate, callback, callback);
+        break;
 
-    // Check for listener leak
-    if (!existing.warned) {
-      m = $getMaxListeners(target);
-      if (m && m > 0 && existing.length > m) {
-        existing.warned = true;
-        var w = new Error('Possible EventEmitter memory leak detected. ' +
-            existing.length + ' "' + String(type) + '" listeners ' +
-            'added. Use emitter.setMaxListeners() to ' +
-            'increase limit.');
-        w.name = 'MaxListenersExceededWarning';
-        w.emitter = target;
-        w.type = type;
-        w.count = existing.length;
-        if (typeof console === 'object' && console.warn) {
-          console.warn('%s: %s', w.name, w.message);
-        }
       }
-    }
-  }
+      default:
+        candidatesQueue.push({
+          candidate: candidate,
+          callback: callback
 
-  return target;
+        });
+    }
+  };
 }
 
-EventEmitter.prototype.addListener = function addListener(type, listener) {
-  return _addListener(this, type, listener, false);
-};
-
-EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+/* Simulcast utilities */
 
-EventEmitter.prototype.prependListener =
-    function prependListener(type, listener) {
-      return _addListener(this, type, listener, true);
-    };
+function removeFIDFromOffer(sdp) {
+  var n = sdp.indexOf("a=ssrc-group:FID");
 
-function onceWrapper() {
-  if (!this.fired) {
-    this.target.removeListener(this.type, this.wrapFn);
-    this.fired = true;
-    switch (arguments.length) {
-      case 0:
-        return this.listener.call(this.target);
-      case 1:
-        return this.listener.call(this.target, arguments[0]);
-      case 2:
-        return this.listener.call(this.target, arguments[0], arguments[1]);
-      case 3:
-        return this.listener.call(this.target, arguments[0], arguments[1],
-            arguments[2]);
-      default:
-        var args = new Array(arguments.length);
-        for (var i = 0; i < args.length; ++i)
-          args[i] = arguments[i];
-        this.listener.apply(this.target, args);
-    }
+  if (n > 0) {
+    return sdp.slice(0, n);
+  } else {
+    return sdp;
   }
 }
 
-function _onceWrap(target, type, listener) {
-  var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
-  var wrapped = bind.call(onceWrapper, state);
-  wrapped.listener = listener;
-  state.wrapFn = wrapped;
-  return wrapped;
+function getSimulcastInfo(videoStream) {
+  var videoTracks = videoStream.getVideoTracks();
+  if (!videoTracks.length) {
+    logger.warn('No video tracks available in the video stream')
+    return ''
+  }
+  var lines = [
+    'a=x-google-flag:conference',
+    'a=ssrc-group:SIM 1 2 3',
+    'a=ssrc:1 cname:localVideo',
+    'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id,
+    'a=ssrc:1 mslabel:' + videoStream.id,
+    'a=ssrc:1 label:' + videoTracks[0].id,
+    'a=ssrc:2 cname:localVideo',
+    'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id,
+    'a=ssrc:2 mslabel:' + videoStream.id,
+    'a=ssrc:2 label:' + videoTracks[0].id,
+    'a=ssrc:3 cname:localVideo',
+    'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id,
+    'a=ssrc:3 mslabel:' + videoStream.id,
+    'a=ssrc:3 label:' + videoTracks[0].id
+  ];
+
+  lines.push('');
+
+  return lines.join('\n');
 }
 
-EventEmitter.prototype.once = function once(type, listener) {
-  if (typeof listener !== 'function')
-    throw new TypeError('"listener" argument must be a function');
-  this.on(type, _onceWrap(this, type, listener));
-  return this;
-};
-
-EventEmitter.prototype.prependOnceListener =
-    function prependOnceListener(type, listener) {
-      if (typeof listener !== 'function')
-        throw new TypeError('"listener" argument must be a function');
-      this.prependListener(type, _onceWrap(this, type, listener));
-      return this;
-    };
+function sleep(milliseconds) {
+  var start = new Date().getTime();
+  for (var i = 0; i < 1e7; i++) {
+    if ((new Date().getTime() - start) > milliseconds) {
+      break;
+    }
+  }
+}
 
-// Emits a 'removeListener' event if and only if the listener was removed.
-EventEmitter.prototype.removeListener =
-    function removeListener(type, listener) {
-      var list, events, position, i, originalListener;
+function setIceCandidateAccordingWebBrowser(functionToExecute, pc) {
+  if (typeof AdapterJS !== 'undefined' && AdapterJS.webrtcDetectedBrowser ===
+    'IE' && AdapterJS.webrtcDetectedVersion >= 9) {
+    pc.onicecandidate = functionToExecute;
+  } else {
+    pc.addEventListener('icecandidate', functionToExecute);
+  }
 
-      if (typeof listener !== 'function')
-        throw new TypeError('"listener" argument must be a function');
+}
 
-      events = this._events;
-      if (!events)
-        return this;
+/**
+ * Wrapper object of an RTCPeerConnection. This object is aimed to simplify the
+ * development of WebRTC-based applications.
+ *
+ * @constructor module:kurentoUtils.WebRtcPeer
+ *
+ * @param {String} mode Mode in which the PeerConnection will be configured.
+ *  Valid values are: 'recvonly', 'sendonly', and 'sendrecv'
+ * @param localVideo Video tag for the local stream
+ * @param remoteVideo Video tag for the remote stream
+ * @param {MediaStream} videoStream Stream to be used as primary source
+ *  (typically video and audio, or only video if combined with audioStream) for
+ *  localVideo and to be added as stream to the RTCPeerConnection
+ * @param {MediaStream} audioStream Stream to be used as second source
+ *  (typically for audio) for localVideo and to be added as stream to the
+ *  RTCPeerConnection
+ */
+function WebRtcPeer(mode, options, callback) {
+  if (!(this instanceof WebRtcPeer)) {
+    return new WebRtcPeer(mode, options, callback)
+  }
 
-      list = events[type];
-      if (!list)
-        return this;
+  WebRtcPeer.super_.call(this)
 
-      if (list === listener || list.listener === listener) {
-        if (--this._eventsCount === 0)
-          this._events = objectCreate(null);
-        else {
-          delete events[type];
-          if (events.removeListener)
-            this.emit('removeListener', type, list.listener || listener);
-        }
-      } else if (typeof list !== 'function') {
-        position = -1;
-
-        for (i = list.length - 1; i >= 0; i--) {
-          if (list[i] === listener || list[i].listener === listener) {
-            originalListener = list[i].listener;
-            position = i;
-            break;
-          }
-        }
+  if (options instanceof Function) {
+    callback = options
+    options = undefined
+  }
 
-        if (position < 0)
-          return this;
+  options = options || {}
+  callback = (callback || noop).bind(this)
 
-        if (position === 0)
-          list.shift();
-        else
-          spliceOne(list, position);
+  var self = this
+  var localVideo = options.localVideo
+  var remoteVideo = options.remoteVideo
+  var videoStream = options.videoStream
+  var audioStream = options.audioStream
+  var mediaConstraints = options.mediaConstraints
 
-        if (list.length === 1)
-          events[type] = list[0];
+  var connectionConstraints = options.connectionConstraints
+  var pc = options.peerConnection
+  var sendSource = options.sendSource || 'webcam'
 
-        if (events.removeListener)
-          this.emit('removeListener', type, originalListener || listener);
-      }
+  var dataChannelConfig = options.dataChannelConfig
+  var useDataChannels = options.dataChannels || false
+  var dataChannel
 
-      return this;
-    };
+  var guid = uuidv4()
+  var configuration = recursive({
+      iceServers: freeice()
+    },
+    options.configuration)
 
-EventEmitter.prototype.removeAllListeners =
-    function removeAllListeners(type) {
-      var listeners, events, i;
+  var onicecandidate = options.onicecandidate
+  if (onicecandidate) this.on('icecandidate', onicecandidate)
 
-      events = this._events;
-      if (!events)
-        return this;
+  var oncandidategatheringdone = options.oncandidategatheringdone
+  if (oncandidategatheringdone) {
+    this.on('candidategatheringdone', oncandidategatheringdone)
+  }
 
-      // not listening for removeListener, no need to emit
-      if (!events.removeListener) {
-        if (arguments.length === 0) {
-          this._events = objectCreate(null);
-          this._eventsCount = 0;
-        } else if (events[type]) {
-          if (--this._eventsCount === 0)
-            this._events = objectCreate(null);
-          else
-            delete events[type];
-        }
-        return this;
-      }
+  var simulcast = options.simulcast
+  var multistream = options.multistream
+  var interop = new sdpTranslator.Interop()
+  var candidatesQueueOut = []
+  var candidategatheringdone = false
 
-      // emit removeListener for all listeners on all events
-      if (arguments.length === 0) {
-        var keys = objectKeys(events);
-        var key;
-        for (i = 0; i < keys.length; ++i) {
-          key = keys[i];
-          if (key === 'removeListener') continue;
-          this.removeAllListeners(key);
-        }
-        this.removeAllListeners('removeListener');
-        this._events = objectCreate(null);
-        this._eventsCount = 0;
-        return this;
+  Object.defineProperties(this, {
+    'peerConnection': {
+      get: function () {
+        return pc
       }
+    },
 
-      listeners = events[type];
+    'id': {
+      value: options.id || guid,
+      writable: false
+    },
 
-      if (typeof listeners === 'function') {
-        this.removeListener(type, listeners);
-      } else if (listeners) {
-        // LIFO order
-        for (i = listeners.length - 1; i >= 0; i--) {
-          this.removeListener(type, listeners[i]);
-        }
+    'remoteVideo': {
+      get: function () {
+        return remoteVideo
       }
+    },
 
-      return this;
-    };
-
-function _listeners(target, type, unwrap) {
-  var events = target._events;
-
-  if (!events)
-    return [];
+    'localVideo': {
+      get: function () {
+        return localVideo
+      }
+    },
 
-  var evlistener = events[type];
-  if (!evlistener)
-    return [];
+    'dataChannel': {
+      get: function () {
+        return dataChannel
+      }
+    },
 
-  if (typeof evlistener === 'function')
-    return unwrap ? [evlistener.listener || evlistener] : [evlistener];
+    /**
+     * @member {(external:ImageData|undefined)} currentFrame
+     */
+    'currentFrame': {
+      get: function () {
+        // [ToDo] Find solution when we have a remote stream but we didn't set
+        // a remoteVideo tag
+        if (!remoteVideo) return;
 
-  return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
-}
+        if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
+          throw new Error('No video stream data available')
 
-EventEmitter.prototype.listeners = function listeners(type) {
-  return _listeners(this, type, true);
-};
+        var canvas = document.createElement('canvas')
+        canvas.width = remoteVideo.videoWidth
+        canvas.height = remoteVideo.videoHeight
 
-EventEmitter.prototype.rawListeners = function rawListeners(type) {
-  return _listeners(this, type, false);
-};
+        canvas.getContext('2d').drawImage(remoteVideo, 0, 0)
 
-EventEmitter.listenerCount = function(emitter, type) {
-  if (typeof emitter.listenerCount === 'function') {
-    return emitter.listenerCount(type);
-  } else {
-    return listenerCount.call(emitter, type);
+        return canvas
+      }
+    }
+  })
+
+  // Init PeerConnection
+  if (!pc) {
+    pc = new RTCPeerConnection(configuration);
+    if (useDataChannels && !dataChannel) {
+      var dcId = 'WebRtcPeer-' + self.id
+      var dcOptions = undefined
+      if (dataChannelConfig) {
+        dcId = dataChannelConfig.id || dcId
+        dcOptions = dataChannelConfig.options
+      }
+      dataChannel = pc.createDataChannel(dcId, dcOptions);
+      if (dataChannelConfig) {
+        dataChannel.onopen = dataChannelConfig.onopen;
+        dataChannel.onclose = dataChannelConfig.onclose;
+        dataChannel.onmessage = dataChannelConfig.onmessage;
+        dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow;
+        dataChannel.onerror = dataChannelConfig.onerror || noop;
+      }
+    }
   }
-};
-
-EventEmitter.prototype.listenerCount = listenerCount;
-function listenerCount(type) {
-  var events = this._events;
 
-  if (events) {
-    var evlistener = events[type];
-
-    if (typeof evlistener === 'function') {
-      return 1;
-    } else if (evlistener) {
-      return evlistener.length;
-    }
+  // Shims over the now deprecated getLocalStreams() and getRemoteStreams()
+  // (usage of these methods should be dropped altogether)
+  if (!pc.getLocalStreams && pc.getSenders) {
+    pc.getLocalStreams = function () {
+      var stream = new MediaStream();
+      pc.getSenders().forEach(function (sender) {
+        stream.addTrack(sender.track);
+      });
+      return [stream];
+    };
+  }
+  if (!pc.getRemoteStreams && pc.getReceivers) {
+    pc.getRemoteStreams = function () {
+      var stream = new MediaStream();
+      pc.getReceivers().forEach(function (sender) {
+        stream.addTrack(sender.track);
+      });
+      return [stream];
+    };
   }
 
-  return 0;
-}
+  var iceCandidateFunction = function (event) {
+    var candidate = event.candidate;
+    if (EventEmitter.listenerCount(self, 'icecandidate') || EventEmitter
+      .listenerCount(self, 'candidategatheringdone')) {
+      if (candidate) {
+        var cand;
+        if (multistream && usePlanB) {
+          cand = interop.candidateToUnifiedPlan(candidate);
+        } else {
+          cand = candidate;
+        }
+        if (typeof AdapterJS === 'undefined') {
+          self.emit('icecandidate', cand);
 
-EventEmitter.prototype.eventNames = function eventNames() {
-  return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
-};
+        }
+        candidategatheringdone = false;
+      } else if (!candidategatheringdone) {
+        if (typeof AdapterJS !== 'undefined' && AdapterJS
+          .webrtcDetectedBrowser === 'IE' && AdapterJS
+          .webrtcDetectedVersion >= 9) {
+          EventEmitter.prototype.emit('candidategatheringdone', cand);
+        } else {
+          self.emit('candidategatheringdone');
+        }
+        candidategatheringdone = true;
+      }
+    } else if (!candidategatheringdone) {
+      candidatesQueueOut.push(candidate);
+      if (!candidate)
+        candidategatheringdone = true;
 
-// About 1.5x faster than the two-arg version of Array#splice().
-function spliceOne(list, index) {
-  for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
-    list[i] = list[k];
-  list.pop();
-}
+    }
+  };
 
-function arrayClone(arr, n) {
-  var copy = new Array(n);
-  for (var i = 0; i < n; ++i)
-    copy[i] = arr[i];
-  return copy;
-}
+  setIceCandidateAccordingWebBrowser(iceCandidateFunction, pc);
+  pc.onaddstream = options.onaddstream
+  pc.onnegotiationneeded = options.onnegotiationneeded
+  this.on('newListener', function (event, listener) {
+    if (event === 'icecandidate' || event === 'candidategatheringdone') {
+      while (candidatesQueueOut.length) {
+        var candidate = candidatesQueueOut.shift()
 
-function unwrapListeners(arr) {
-  var ret = new Array(arr.length);
-  for (var i = 0; i < ret.length; ++i) {
-    ret[i] = arr[i].listener || arr[i];
-  }
-  return ret;
-}
+        if (!candidate === (event === 'candidategatheringdone')) {
+          listener(candidate)
+        }
+      }
+    }
+  })
+
+  var addIceCandidate = bufferizeCandidates(pc)
+
+  /**
+   * Callback function invoked when an ICE candidate is received. Developers are
+   * expected to invoke this function in order to complete the SDP negotiation.
+   *
+   * @function module:kurentoUtils.WebRtcPeer.prototype.addIceCandidate
+   *
+   * @param iceCandidate - Literal object with the ICE candidate description
+   * @param callback - Called when the ICE candidate has been added.
+   */
+  this.addIceCandidate = function (iceCandidate, callback) {
+    var candidate
+
+    if (multistream && usePlanB) {
+      candidate = interop.candidateToPlanB(iceCandidate)
+    } else {
+      candidate = new RTCIceCandidate(iceCandidate)
+    }
 
-function objectCreatePolyfill(proto) {
-  var F = function() {};
-  F.prototype = proto;
-  return new F;
-}
-function objectKeysPolyfill(obj) {
-  var keys = [];
-  for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) {
-    keys.push(k);
+    logger.debug('Remote ICE candidate received', iceCandidate)
+    callback = (callback || noop).bind(this)
+    addIceCandidate(candidate, callback)
   }
-  return k;
-}
-function functionBindPolyfill(context) {
-  var fn = this;
-  return function () {
-    return fn.apply(context, arguments);
-  };
-}
-
-},{}],5:[function(require,module,exports){
-/* jshint node: true */
-'use strict';
-
-var normalice = require('normalice');
 
-/**
-  # freeice
+  this.generateOffer = function (callback) {
+    callback = callback.bind(this)
+
+    if (mode === 'recvonly') {
+      /* Add reception tracks on the RTCPeerConnection. Send tracks are
+       * unconditionally added to "sendonly" and "sendrecv" modes, in the
+       * constructor's "start()" method, but nothing is done for "recvonly".
+       *
+       * Here, we add new transceivers to receive audio and/or video, so the
+       * SDP Offer that will be generated by the PC includes these medias
+       * with the "a=recvonly" attribute.
+       */
+      var useAudio =
+        (mediaConstraints && typeof mediaConstraints.audio === 'boolean') ?
+        mediaConstraints.audio : true
+      var useVideo =
+        (mediaConstraints && typeof mediaConstraints.video === 'boolean') ?
+        mediaConstraints.video : true
+
+      if (useAudio) {
+        pc.addTransceiver('audio', {
+          direction: 'recvonly'
+        });
+      }
 
-  The `freeice` module is a simple way of getting random STUN or TURN server
-  for your WebRTC application.  The list of servers (just STUN at this stage)
-  were sourced from this [gist](https://gist.github.com/zziuni/3741933).
+      if (useVideo) {
+        pc.addTransceiver('video', {
+          direction: 'recvonly'
+        });
+      }
+    }
 
-  ## Example Use
+    if (typeof AdapterJS !== 'undefined' && AdapterJS
+      .webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9
+    ) {
+      var setLocalDescriptionOnSuccess = function () {
+        sleep(1000);
+        var localDescription = pc.localDescription;
+        logger.debug('Local description set\n', localDescription.sdp);
+        if (multistream && usePlanB) {
+          localDescription = interop.toUnifiedPlan(localDescription);
+          logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(
+            localDescription));
+        }
+        callback(null, localDescription.sdp, self.processAnswer.bind(self));
+      };
+      var createOfferOnSuccess = function (offer) {
+        logger.debug('Created SDP offer');
+        logger.debug('Local description set\n', pc.localDescription);
+        pc.setLocalDescription(offer, setLocalDescriptionOnSuccess,
+          callback);
+      };
+      pc.createOffer(createOfferOnSuccess, callback);
+    } else {
+      pc.createOffer()
+        .then(function (offer) {
+          logger.debug('Created SDP offer');
+          offer = mangleSdpToAddSimulcast(offer);
+          return pc.setLocalDescription(offer);
+        })
+        .then(function () {
+          var localDescription = pc.localDescription;
+          logger.debug('Local description set\n', localDescription.sdp);
+          if (multistream && usePlanB) {
+            localDescription = interop.toUnifiedPlan(localDescription);
+            logger.debug('offer::origPlanB->UnifiedPlan', dumpSDP(
+              localDescription));
+          }
+          callback(null, localDescription.sdp, self.processAnswer.bind(
+            self));
+        })
+        .catch(callback);
+    }
+  }
 
-  The following demonstrates how you can use `freeice` with
-  [rtc-quickconnect](https://github.com/rtc-io/rtc-quickconnect):
+  this.getLocalSessionDescriptor = function () {
+    return pc.localDescription
+  }
 
-  <<< examples/quickconnect.js
+  this.getRemoteSessionDescriptor = function () {
+    return pc.remoteDescription
+  }
 
-  As the `freeice` module generates ice servers in a list compliant with the
-  WebRTC spec you will be able to use it with raw `RTCPeerConnection`
-  constructors and other WebRTC libraries.
+  function setRemoteVideo() {
+    if (remoteVideo) {
+      remoteVideo.pause()
 
-  ## Hey, don't use my STUN/TURN server!
+      var stream = pc.getRemoteStreams()[0]
+      remoteVideo.srcObject = stream
+      logger.debug('Remote stream:', stream)
 
-  If for some reason your free STUN or TURN server ends up in the
-  list of servers ([stun](https://github.com/DamonOehlman/freeice/blob/master/stun.json) or
-  [turn](https://github.com/DamonOehlman/freeice/blob/master/turn.json))
-  that is used in this module, you can feel
-  free to open an issue on this repository and those servers will be removed
-  within 24 hours (or sooner).  This is the quickest and probably the most
-  polite way to have something removed (and provides us some visibility
-  if someone opens a pull request requesting that a server is added).
+      if (typeof AdapterJS !== 'undefined' && AdapterJS
+        .webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9
+      ) {
+        remoteVideo = attachMediaStream(remoteVideo, stream);
+      } else {
+        remoteVideo.load();
+      }
+    }
+  }
 
-  ## Please add my server!
+  this.showLocalVideo = function () {
+    localVideo.srcObject = videoStream
+    localVideo.muted = true
 
-  If you have a server that you wish to add to the list, that's awesome! I'm
-  sure I speak on behalf of a whole pile of WebRTC developers who say thanks.
-  To get it into the list, feel free to either open a pull request or if you
-  find that process a bit daunting then just create an issue requesting
-  the addition of the server (make sure you provide all the details, and if
-  you have a Terms of Service then including that in the PR/issue would be
-  awesome).
+    if (typeof AdapterJS !== 'undefined' && AdapterJS
+      .webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9
+    ) {
+      localVideo = attachMediaStream(localVideo, videoStream);
+    }
+  };
+  this.send = function (data) {
+    if (dataChannel && dataChannel.readyState === 'open') {
+      dataChannel.send(data)
+    } else {
+      logger.warn(
+        'Trying to send data over a non-existing or closed data channel')
+    }
+  }
 
-  ## I know of a free server, can I add it?
+  /**
+   * Callback function invoked when a SDP answer is received. Developers are
+   * expected to invoke this function in order to complete the SDP negotiation.
+   *
+   * @function module:kurentoUtils.WebRtcPeer.prototype.processAnswer
+   *
+   * @param sdpAnswer - Description of sdpAnswer
+   * @param callback -
+   *            Invoked after the SDP answer is processed, or there is an error.
+   */
+  this.processAnswer = function (sdpAnswer, callback) {
+    callback = (callback || noop).bind(this)
+
+    var answer = new RTCSessionDescription({
+      type: 'answer',
+      sdp: sdpAnswer
+    })
+
+    if (multistream && usePlanB) {
+      var planBAnswer = interop.toPlanB(answer)
+      logger.debug('asnwer::planB', dumpSDP(planBAnswer))
+      answer = planBAnswer
+    }
 
-  Sure, if you do your homework and make sure it is ok to use (I'm currently
-  in the process of reviewing the terms of those STUN servers included from
-  the original list).  If it's ok to go, then please see the previous entry
-  for how to add it.
+    logger.debug('SDP answer received, setting remote description')
 
-  ## Current List of Servers
+    if (pc.signalingState === 'closed') {
+      return callback('PeerConnection is closed')
+    }
 
-  * current as at the time of last `README.md` file generation
+    pc.setRemoteDescription(answer, function () {
+        setRemoteVideo()
 
-  ### STUN
+        callback()
+      },
+      callback)
+  }
 
-  <<< stun.json
+  /**
+   * Callback function invoked when a SDP offer is received. Developers are
+   * expected to invoke this function in order to complete the SDP negotiation.
+   *
+   * @function module:kurentoUtils.WebRtcPeer.prototype.processOffer
+   *
+   * @param sdpOffer - Description of sdpOffer
+   * @param callback - Called when the remote description has been set
+   *  successfully.
+   */
+  this.processOffer = function (sdpOffer, callback) {
+    callback = callback.bind(this)
+
+    var offer = new RTCSessionDescription({
+      type: 'offer',
+      sdp: sdpOffer
+    })
+
+    if (multistream && usePlanB) {
+      var planBOffer = interop.toPlanB(offer)
+      logger.debug('offer::planB', dumpSDP(planBOffer))
+      offer = planBOffer
+    }
 
-  ### TURN
+    logger.debug('SDP offer received, setting remote description')
 
-  <<< turn.json
+    if (pc.signalingState === 'closed') {
+      return callback('PeerConnection is closed')
+    }
 
-**/
+    pc.setRemoteDescription(offer).then(function () {
+      return setRemoteVideo()
+    }).then(function () {
+      return pc.createAnswer()
+    }).then(function (answer) {
+      answer = mangleSdpToAddSimulcast(answer)
+      logger.debug('Created SDP answer')
+      return pc.setLocalDescription(answer)
+    }).then(function () {
+      var localDescription = pc.localDescription
+      if (multistream && usePlanB) {
+        localDescription = interop.toUnifiedPlan(localDescription)
+        logger.debug('answer::origPlanB->UnifiedPlan', dumpSDP(
+          localDescription))
+      }
+      logger.debug('Local description set\n', localDescription.sdp)
+      callback(null, localDescription.sdp)
+    }).catch(callback)
+  }
 
-var freeice = function(opts) {
-  // if a list of servers has been provided, then use it instead of defaults
-  var servers = {
-    stun: (opts || {}).stun || require('./stun.json'),
-    turn: (opts || {}).turn || require('./turn.json')
-  };
+  function mangleSdpToAddSimulcast(answer) {
+    if (simulcast) {
+      if (browser.name === 'Chrome' || browser.name === 'Chromium') {
+        logger.debug('Adding multicast info')
+        answer = new RTCSessionDescription({
+          'type': answer.type,
+          'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(
+            videoStream)
+        })
+      } else {
+        logger.warn('Simulcast is only available in Chrome browser.')
+      }
+    }
 
-  var stunCount = (opts || {}).stunCount || 2;
-  var turnCount = (opts || {}).turnCount || 0;
-  var selected;
+    return answer
+  }
 
-  function getServers(type, count) {
-    var out = [];
-    var input = [].concat(servers[type]);
-    var idx;
+  /**
+   * This function creates the RTCPeerConnection object taking into account the
+   * properties received in the constructor. It starts the SDP negotiation
+   * process: generates the SDP offer and invokes the onsdpoffer callback. This
+   * callback is expected to send the SDP offer, in order to obtain an SDP
+   * answer from another peer.
+   */
+  function start() {
+    if (pc.signalingState === 'closed') {
+      callback(
+        'The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'
+      )
+    }
 
-    while (input.length && out.length < count) {
-      idx = (Math.random() * input.length) | 0;
-      out = out.concat(input.splice(idx, 1));
+    if (videoStream && localVideo) {
+      self.showLocalVideo()
     }
 
-    return out.map(function(url) {
-        //If it's a not a string, don't try to "normalice" it otherwise using type:url will screw it up
-        if ((typeof url !== 'string') && (! (url instanceof String))) {
-            return url;
-        } else {
-            return normalice(type + ':' + url);
-        }
-    });
-  }
+    if (videoStream) {
+      videoStream.getTracks().forEach(function (track) {
+        pc.addTrack(track, videoStream);
+      });
+    }
 
-  // add stun servers
-  selected = [].concat(getServers('stun', stunCount));
+    if (audioStream) {
+      audioStream.getTracks().forEach(function (track) {
+        pc.addTrack(track, audioStream);
+      });
+    }
 
-  if (turnCount) {
-    selected = selected.concat(getServers('turn', turnCount));
+    // [Hack] https://code.google.com/p/chromium/issues/detail?id=443558
+    var browser = parser.getBrowser()
+    if (mode === 'sendonly' &&
+      (browser.name === 'Chrome' || browser.name === 'Chromium') &&
+      browser.major === 39) {
+      mode = 'sendrecv'
+    }
+
+    callback()
   }
 
-  return selected;
-};
+  if (mode !== 'recvonly' && !videoStream && !audioStream) {
+    function getMedia(constraints) {
+      if (constraints === undefined) {
+        constraints = MEDIA_CONSTRAINTS
+      }
+      if (typeof AdapterJS !== 'undefined' && AdapterJS
+        .webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion >= 9
+      ) {
+        navigator.getUserMedia(constraints, function (stream) {
+          videoStream = stream;
+          start();
+        }, callback);
+      } else {
+        navigator.mediaDevices.getUserMedia(constraints).then(function (
+          stream) {
+          videoStream = stream;
 
-module.exports = freeice;
-},{"./stun.json":6,"./turn.json":7,"normalice":12}],6:[function(require,module,exports){
-module.exports=[
-  "stun.l.google.com:19302",
-  "stun1.l.google.com:19302",
-  "stun2.l.google.com:19302",
-  "stun3.l.google.com:19302",
-  "stun4.l.google.com:19302",
-  "stun.ekiga.net",
-  "stun.ideasip.com",
-  "stun.schlund.de",
-  "stun.stunprotocol.org:3478",
-  "stun.voiparound.com",
-  "stun.voipbuster.com",
-  "stun.voipstunt.com",
-  "stun.voxgratia.org"
-]
+          start();
+        }).catch(callback);
+      }
+    }
+    if (sendSource === 'webcam') {
+      getMedia(mediaConstraints)
+    } else {
+      getScreenConstraints(sendSource, function (error, constraints_) {
+        if (error)
+          return callback(error)
+
+        constraints = [mediaConstraints]
+        constraints.unshift(constraints_)
+        getMedia(recursive.apply(undefined, constraints))
+      }, guid)
+    }
+  } else {
+    setTimeout(start, 0)
+  }
 
-},{}],7:[function(require,module,exports){
-module.exports=[]
+  this.on('_dispose', function () {
+    if (localVideo) {
+      localVideo.pause();
+      localVideo.srcObject = null;
 
-},{}],8:[function(require,module,exports){
-var WildEmitter = require('wildemitter');
+      if (typeof AdapterJS === 'undefined') {
+        localVideo.load();
+      }
+      localVideo.muted = false;
 
-function getMaxVolume (analyser, fftBins) {
-  var maxVolume = -Infinity;
-  analyser.getFloatFrequencyData(fftBins);
+    }
+    if (remoteVideo) {
+      remoteVideo.pause();
+      remoteVideo.srcObject = null;
+      if (typeof AdapterJS === 'undefined') {
+        remoteVideo.load();
 
-  for(var i=4, ii=fftBins.length; i < ii; i++) {
-    if (fftBins[i] > maxVolume && fftBins[i] < 0) {
-      maxVolume = fftBins[i];
+      }
     }
-  };
+    self.removeAllListeners();
 
-  return maxVolume;
+    if (window.cancelChooseDesktopMedia !== undefined) {
+      window.cancelChooseDesktopMedia(guid)
+    }
+  })
 }
+inherits(WebRtcPeer, EventEmitter)
 
+function createEnableDescriptor(type) {
+  var method = 'get' + type + 'Tracks'
 
-var audioContextType;
-if (typeof window !== 'undefined') {
-  audioContextType = window.AudioContext || window.webkitAudioContext;
-}
-// use a single audio context due to hardware limits
-var audioContext = null;
-module.exports = function(stream, options) {
-  var harker = new WildEmitter();
-
-  // make it not break in non-supported browsers
-  if (!audioContextType) return harker;
+  return {
+    enumerable: true,
+    get: function () {
+      // [ToDo] Should return undefined if not all tracks have the same value?
 
-  //Config
-  var options = options || {},
-      smoothing = (options.smoothing || 0.1),
-      interval = (options.interval || 50),
-      threshold = options.threshold,
-      play = options.play,
-      history = options.history || 10,
-      running = true;
+      if (!this.peerConnection) return
 
-  // Ensure that just a single AudioContext is internally created
-  audioContext = options.audioContext || audioContext || new audioContextType();
+      var streams = this.peerConnection.getLocalStreams()
+      if (!streams.length) return
 
-  var sourceNode, fftBins, analyser;
+      for (var i = 0, stream; stream = streams[i]; i++) {
+        var tracks = stream[method]()
+        for (var j = 0, track; track = tracks[j]; j++)
+          if (!track.enabled) return false
+      }
 
-  analyser = audioContext.createAnalyser();
-  analyser.fftSize = 512;
-  analyser.smoothingTimeConstant = smoothing;
-  fftBins = new Float32Array(analyser.frequencyBinCount);
+      return true
+    },
+    set: function (value) {
+      function trackSetEnable(track) {
+        track.enabled = value
+      }
 
-  if (stream.jquery) stream = stream[0];
-  if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) {
-    //Audio Tag
-    sourceNode = audioContext.createMediaElementSource(stream);
-    if (typeof play === 'undefined') play = true;
-    threshold = threshold || -50;
-  } else {
-    //WebRTC Stream
-    sourceNode = audioContext.createMediaStreamSource(stream);
-    threshold = threshold || -50;
+      this.peerConnection.getLocalStreams().forEach(function (stream) {
+        stream[method]().forEach(trackSetEnable)
+      })
+    }
   }
+}
 
-  sourceNode.connect(analyser);
-  if (play) analyser.connect(audioContext.destination);
-
-  harker.speaking = false;
+Object.defineProperties(WebRtcPeer.prototype, {
+  'enabled': {
+    enumerable: true,
+    get: function () {
+      return this.audioEnabled && this.videoEnabled
+    },
+    set: function (value) {
+      this.audioEnabled = this.videoEnabled = value
+    }
+  },
+  'audioEnabled': createEnableDescriptor('Audio'),
+  'videoEnabled': createEnableDescriptor('Video')
+})
 
-  harker.suspend = function() {
-    return audioContext.suspend();
-  }
-  harker.resume = function() {
-    return audioContext.resume();
+WebRtcPeer.prototype.getLocalStream = function (index) {
+  if (this.peerConnection) {
+    return this.peerConnection.getLocalStreams()[index || 0]
   }
-  Object.defineProperty(harker, 'state', { get: function() {
-    return audioContext.state;
-  }});
-  audioContext.onstatechange = function() {
-    harker.emit('state_change', audioContext.state);
+}
+
+WebRtcPeer.prototype.getRemoteStream = function (index) {
+  if (this.peerConnection) {
+    return this.peerConnection.getRemoteStreams()[index || 0]
   }
+}
 
-  harker.setThreshold = function(t) {
-    threshold = t;
-  };
+/**
+ * @description This method frees the resources used by WebRtcPeer.
+ *
+ * @function module:kurentoUtils.WebRtcPeer.prototype.dispose
+ */
+WebRtcPeer.prototype.dispose = function () {
+  logger.debug('Disposing WebRtcPeer')
 
-  harker.setInterval = function(i) {
-    interval = i;
-  };
+  var pc = this.peerConnection
+  var dc = this.dataChannel
+  try {
+    if (dc) {
+      if (dc.signalingState === 'closed') return
 
-  harker.stop = function() {
-    running = false;
-    harker.emit('volume_change', -100, threshold);
-    if (harker.speaking) {
-      harker.speaking = false;
-      harker.emit('stopped_speaking');
+      dc.close()
     }
-    analyser.disconnect();
-    sourceNode.disconnect();
-  };
-  harker.speakingHistory = [];
-  for (var i = 0; i < history; i++) {
-      harker.speakingHistory.push(0);
+
+    if (pc) {
+      if (pc.signalingState === 'closed') return
+
+      pc.getLocalStreams().forEach(streamStop)
+
+      // FIXME This is not yet implemented in firefox
+      // if(videoStream) pc.removeStream(videoStream);
+      // if(audioStream) pc.removeStream(audioStream);
+
+      pc.close()
+    }
+  } catch (err) {
+    logger.warn('Exception disposing webrtc peer ' + err)
   }
 
-  // Poll the analyser node to determine if speaking
-  // and emit events if changed
-  var looper = function() {
-    setTimeout(function() {
+  if (typeof AdapterJS === 'undefined') {
+    this.emit('_dispose');
+  }
 
-      //check if stop has been called
-      if(!running) {
-        return;
-      }
+}
 
-      var currentVolume = getMaxVolume(analyser, fftBins);
+//
+// Specialized child classes
+//
 
-      harker.emit('volume_change', currentVolume, threshold);
+function WebRtcPeerRecvonly(options, callback) {
+  if (!(this instanceof WebRtcPeerRecvonly)) {
+    return new WebRtcPeerRecvonly(options, callback)
+  }
 
-      var history = 0;
-      if (currentVolume > threshold && !harker.speaking) {
-        // trigger quickly, short history
-        for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) {
-          history += harker.speakingHistory[i];
-        }
-        if (history >= 2) {
-          harker.speaking = true;
-          harker.emit('speaking');
-        }
-      } else if (currentVolume < threshold && harker.speaking) {
-        for (var i = 0; i < harker.speakingHistory.length; i++) {
-          history += harker.speakingHistory[i];
-        }
-        if (history == 0) {
-          harker.speaking = false;
-          harker.emit('stopped_speaking');
-        }
-      }
-      harker.speakingHistory.shift();
-      harker.speakingHistory.push(0 + (currentVolume > threshold));
+  WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback)
+}
+inherits(WebRtcPeerRecvonly, WebRtcPeer)
 
-      looper();
-    }, interval);
-  };
-  looper();
+function WebRtcPeerSendonly(options, callback) {
+  if (!(this instanceof WebRtcPeerSendonly)) {
+    return new WebRtcPeerSendonly(options, callback)
+  }
 
-  return harker;
+  WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback)
 }
+inherits(WebRtcPeerSendonly, WebRtcPeer)
 
-},{"wildemitter":25}],9:[function(require,module,exports){
-if (typeof Object.create === 'function') {
-  // implementation from standard node.js 'util' module
-  module.exports = function inherits(ctor, superCtor) {
-    if (superCtor) {
-      ctor.super_ = superCtor
-      ctor.prototype = Object.create(superCtor.prototype, {
-        constructor: {
-          value: ctor,
-          enumerable: false,
-          writable: true,
-          configurable: true
-        }
-      })
-    }
-  };
-} else {
-  // old school shim for old browsers
-  module.exports = function inherits(ctor, superCtor) {
-    if (superCtor) {
-      ctor.super_ = superCtor
-      var TempCtor = function () {}
-      TempCtor.prototype = superCtor.prototype
-      ctor.prototype = new TempCtor()
-      ctor.prototype.constructor = ctor
-    }
+function WebRtcPeerSendrecv(options, callback) {
+  if (!(this instanceof WebRtcPeerSendrecv)) {
+    return new WebRtcPeerSendrecv(options, callback)
   }
+
+  WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback)
 }
+inherits(WebRtcPeerSendrecv, WebRtcPeer)
 
-},{}],10:[function(require,module,exports){
-// Does nothing at all.
+function harkUtils(stream, options) {
+  return hark(stream, options);
+}
 
-},{}],11:[function(require,module,exports){
+exports.bufferizeCandidates = bufferizeCandidates
+
+exports.WebRtcPeerRecvonly = WebRtcPeerRecvonly
+exports.WebRtcPeerSendonly = WebRtcPeerSendonly
+exports.WebRtcPeerSendrecv = WebRtcPeerSendrecv
+exports.hark = harkUtils
+
+},{"events":undefined,"freeice":2,"hark":5,"inherits":6,"kurento-browser-extensions":7,"merge":10,"sdp-translator":17,"ua-parser-js":20,"uuid/v4":23}],9:[function(require,module,exports){
+/*
+ * (C) Copyright 2014 Kurento (http://kurento.org/)
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * This module contains a set of reusable components that have been found useful
+ * during the development of the WebRTC applications with Kurento.
+ * 
+ * @module kurentoUtils
+ * 
+ * @copyright 2014 Kurento (http://kurento.org/)
+ * @license ALv2
+ */
+
+var WebRtcPeer = require('./WebRtcPeer');
+
+exports.WebRtcPeer = WebRtcPeer;
+
+},{"./WebRtcPeer":8}],10:[function(require,module,exports){
 /*!
  * @name JavaScript/NodeJS Merge v1.2.1
  * @author yeikos
@@ -1640,7 +1436,7 @@ if (typeof Object.create === 'function') {
 	}
 
 })(typeof module === 'object' && module && typeof module.exports === 'object' && module.exports);
-},{}],12:[function(require,module,exports){
+},{}],11:[function(require,module,exports){
 /**
   # normalice
 
@@ -1702,7 +1498,7 @@ module.exports = function(input) {
   return output;
 };
 
-},{}],13:[function(require,module,exports){
+},{}],12:[function(require,module,exports){
 var grammar = module.exports = {
   v: [{
       name: 'version',
@@ -1961,7 +1757,7 @@ Object.keys(grammar).forEach(function (key) {
   });
 });
 
-},{}],14:[function(require,module,exports){
+},{}],13:[function(require,module,exports){
 var parser = require('./parser');
 var writer = require('./writer');
 
@@ -1971,7 +1767,7 @@ exports.parseFmtpConfig = parser.parseFmtpConfig;
 exports.parsePayloads = parser.parsePayloads;
 exports.parseRemoteCandidates = parser.parseRemoteCandidates;
 
-},{"./parser":15,"./writer":16}],15:[function(require,module,exports){
+},{"./parser":14,"./writer":15}],14:[function(require,module,exports){
 var toIntIfInt = function (v) {
   return String(Number(v)) === v ? Number(v) : v;
 };
@@ -2066,7 +1862,7 @@ exports.parseRemoteCandidates = function (str) {
   return candidates;
 };
 
-},{"./grammar":13}],16:[function(require,module,exports){
+},{"./grammar":12}],15:[function(require,module,exports){
 var grammar = require('./grammar');
 
 // customized util.format - discards excess arguments and can void middle ones
@@ -2182,7 +1978,7 @@ module.exports = function (session, opts) {
   return sdp.join('\r\n') + '\r\n';
 };
 
-},{"./grammar":13}],17:[function(require,module,exports){
+},{"./grammar":12}],16:[function(require,module,exports){
 /* Copyright @ 2015 Atlassian Pty Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -2223,7 +2019,7 @@ module.exports = function arrayEquals(array) {
 };
 
 
-},{}],18:[function(require,module,exports){
+},{}],17:[function(require,module,exports){
 /* Copyright @ 2015 Atlassian Pty Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -2241,7 +2037,7 @@ module.exports = function arrayEquals(array) {
 
 exports.Interop = require('./interop');
 
-},{"./interop":19}],19:[function(require,module,exports){
+},{"./interop":18}],18:[function(require,module,exports){
 /* Copyright @ 2015 Atlassian Pty Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -3126,7 +2922,7 @@ Interop.prototype.toUnifiedPlan = function(desc) {
     //#endregion
 };
 
-},{"./array-equals":17,"./transform":20}],20:[function(require,module,exports){
+},{"./array-equals":16,"./transform":19}],19:[function(require,module,exports){
 /* Copyright @ 2015 Atlassian Pty Ltd
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -3240,7 +3036,7 @@ exports.parse = function(sdp) {
 };
 
 
-},{"sdp-transform":14}],21:[function(require,module,exports){
+},{"sdp-transform":13}],20:[function(require,module,exports){
 /*!
  * UAParser.js v0.7.21
  * Lightweight JavaScript-based User-Agent string parser
@@ -4150,7 +3946,7 @@ exports.parse = function(sdp) {
 
 })(typeof window === 'object' ? window : this);
 
-},{}],22:[function(require,module,exports){
+},{}],21:[function(require,module,exports){
 /**
  * Convert array of 16 byte values to UUID string format of the form:
  * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
@@ -4178,7 +3974,7 @@ function bytesToUuid(buf, offset) {
 
 module.exports = bytesToUuid;
 
-},{}],23:[function(require,module,exports){
+},{}],22:[function(require,module,exports){
 // Unique ID creation requires a high quality random # generator.  In the
 // browser this is a little complicated due to unknown quality of Math.random()
 // and inconsistent support for the `crypto` API.  We do the best we can via
@@ -4214,7 +4010,7 @@ if (getRandomValues) {
   };
 }
 
-},{}],24:[function(require,module,exports){
+},{}],23:[function(require,module,exports){
 var rng = require('./lib/rng');
 var bytesToUuid = require('./lib/bytesToUuid');
 
@@ -4245,7 +4041,7 @@ function v4(options, buf, offset) {
 
 module.exports = v4;
 
-},{"./lib/bytesToUuid":22,"./lib/rng":23}],25:[function(require,module,exports){
+},{"./lib/bytesToUuid":21,"./lib/rng":22}],24:[function(require,module,exports){
 /*
 WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
 on @visionmedia's Emitter from UI Kit.
@@ -4402,5 +4198,5 @@ WildEmitter.mixin = function (constructor) {
 
 WildEmitter.mixin(WildEmitter);
 
-},{}]},{},[2])(2)
+},{}]},{},[1])(1)
 });