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:56 UTC

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

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)
 });