diff --git a/bigbluebutton-client/locale/en_US/bbbResources.properties b/bigbluebutton-client/locale/en_US/bbbResources.properties
index b9dddb5732cfc6eb89162e057e02d502a73c88ab..e4ac26e6f67c460cda12988ac312fd5c25c79da5 100755
--- a/bigbluebutton-client/locale/en_US/bbbResources.properties
+++ b/bigbluebutton-client/locale/en_US/bbbResources.properties
@@ -71,6 +71,8 @@ bbb.webrtcWarning.failedError.1002 = Error 1002: Could not make a WebSocket conn
 bbb.webrtcWarning.failedError.1003 = Error 1003: Browser version not supported
 bbb.webrtcWarning.failedError.1004 = Error 1004: Failure on call
 bbb.webrtcWarning.failedError.1005 = Error 1005: Call ended unexpectedly
+bbb.webrtcWarning.failedError.1006 = Error 1006: Call timed out
+bbb.webrtcWarning.failedError.1007 = Error 1007: ICE negotiation failed
 bbb.webrtcWarning.failedError.unknown = Error {0}: Unknown error code
 bbb.mainToolbar.helpBtn = Help
 bbb.mainToolbar.logoutBtn = Logout
diff --git a/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js b/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js
index d6cfb1a3f7f6353e033d9eb8a69e249f1b8990fa..d8ff8036b5b19589cae161ecc914fcd3ad8a19ba 100755
--- a/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js
+++ b/bigbluebutton-client/resources/prod/lib/bbb_webrtc_bridge_sip.js
@@ -156,7 +156,6 @@ function createUA(username, server, callback) {
 		uri: 'sip:' + encodeURIComponent(username) + '@' + server,
 		wsServers: 'ws://' + server + '/ws',
 		displayName: username,
-		level: 3,
 		register: false,
 		traceSip: true,
 		autostart: false,
@@ -273,8 +272,12 @@ function make_call(username, voiceBridge, server, callback, recall) {
 	};
 	
 	callTimeout = setTimeout(function() {
-		console.log('Ten seconds without updates, retrying the call');
-		make_call(username, voiceBridge, server, callback, true);
+		console.log('Ten seconds without updates sending timeout code');
+		callback({'status':'failed', 'errorcode': 1006}); // Failure on call
+		currentSession = null;
+		var userAgentTemp = userAgent;
+		userAgent = null;
+		userAgentTemp.stop();
 	}, 10000);
 	
 	callActive = false;
@@ -343,6 +346,16 @@ function make_call(username, voiceBridge, server, callback, recall) {
 		callback({'status':'started'});
 		clearTimeout(callTimeout);
 	});
+	currentSession.mediaHandler.on('iceFailed', function() {
+		console.log('received ice negotiation failed');
+		callback({'status':'failed', 'errorcode': 1007}); // Failure on call
+		currentSession = null;
+		var userAgentTemp = userAgent;
+		userAgent = null;
+		userAgentTemp.stop();
+		
+		clearTimeout(callTimeout);
+	});
 }
 
 function webrtc_hangup(callback) {
diff --git a/bigbluebutton-client/resources/prod/lib/sip.js b/bigbluebutton-client/resources/prod/lib/sip.js
index dc19c551189a548a2cb0b5f6eea31400c57ba7b7..79255452631f3fc14d56d426d7a725e9ca38deea 100755
--- a/bigbluebutton-client/resources/prod/lib/sip.js
+++ b/bigbluebutton-client/resources/prod/lib/sip.js
@@ -33,125 +33,15 @@
 
 
 !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.SIP=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
-var nextTick = _dereq_('process/browser.js').nextTick;
-var slice = Array.prototype.slice;
-var immediateIds = {};
-var nextImmediateId = 0;
-
-// DOM APIs, for completeness
-
-if (typeof setTimeout !== 'undefined') exports.setTimeout = function() { return setTimeout.apply(window, arguments); };
-if (typeof clearTimeout !== 'undefined') exports.clearTimeout = function() { clearTimeout.apply(window, arguments); };
-if (typeof setInterval !== 'undefined') exports.setInterval = function() { return setInterval.apply(window, arguments); };
-if (typeof clearInterval !== 'undefined') exports.clearInterval = function() { clearInterval.apply(window, arguments); };
-
-// TODO: Change to more efficient list approach used in Node.js
-// For now, we just implement the APIs using the primitives above.
-
-exports.enroll = function(item, delay) {
-  item._timeoutID = setTimeout(item._onTimeout, delay);
-};
-
-exports.unenroll = function(item) {
-  clearTimeout(item._timeoutID);
-};
-
-exports.active = function(item) {
-  // our naive impl doesn't care (correctness is still preserved)
-};
-
-// That's not how node.js implements it but the exposed api is the same.
-exports.setImmediate = function(fn) {
-  var id = nextImmediateId++;
-  var args = arguments.length < 2 ? false : slice.call(arguments, 1);
-
-  immediateIds[id] = true;
-
-  nextTick(function onNextTick() {
-    if (immediateIds[id]) {
-      // fn.call() is faster so we optimize for the common use-case
-      // @see http://jsperf.com/call-apply-segu
-      if (args) {
-        fn.apply(null, args);
-      } else {
-        fn.call(null);
-      }
-      // Prevent ids from leaking
-      exports.clearImmediate(id);
-    }
-  });
-
-  return id;
-};
-
-exports.clearImmediate = function(id) {
-  delete immediateIds[id];
-};
-},{"process/browser.js":2}],2:[function(_dereq_,module,exports){
-// shim for using process in browser
-
-var process = module.exports = {};
-
-process.nextTick = (function () {
-    var canSetImmediate = typeof window !== 'undefined'
-    && window.setImmediate;
-    var canPost = typeof window !== 'undefined'
-    && window.postMessage && window.addEventListener
-    ;
-
-    if (canSetImmediate) {
-        return function (f) { return window.setImmediate(f) };
-    }
-
-    if (canPost) {
-        var queue = [];
-        window.addEventListener('message', function (ev) {
-            var source = ev.source;
-            if ((source === window || source === null) && ev.data === 'process-tick') {
-                ev.stopPropagation();
-                if (queue.length > 0) {
-                    var fn = queue.shift();
-                    fn();
-                }
-            }
-        }, true);
-
-        return function nextTick(fn) {
-            queue.push(fn);
-            window.postMessage('process-tick', '*');
-        };
-    }
-
-    return function nextTick(fn) {
-        setTimeout(fn, 0);
-    };
-})();
-
-process.title = 'browser';
-process.browser = true;
-process.env = {};
-process.argv = [];
-
-process.binding = function (name) {
-    throw new Error('process.binding is not supported');
-}
-
-// TODO(shtylman)
-process.cwd = function () { return '/' };
-process.chdir = function (dir) {
-    throw new Error('process.chdir is not supported');
-};
-
-},{}],3:[function(_dereq_,module,exports){
 module.exports={
   "name": "sip.js",
   "title": "SIP.js",
   "description": "A simple, intuitive, and powerful JavaScript signaling library",
   "version": "0.6.2",
-  "main": "src/SIP.js",
+  "main": "src/index.js",
   "browser": {
-    "promiscuous": "./src/polyfills/promiscuous.js",
-    "console": "./src/polyfills/console.js"
+    "./src/Grammar/index.js": "./src/Grammar/dist/Grammar.js",
+    "./src/environment.js": "./src/environment_browser.js"
   },
   "homepage": "http://sipjs.com",
   "author": "Will Mitchell <will@onsip.com>",
@@ -180,7 +70,6 @@ module.exports={
     "grunt-peg": "~1.3.1",
     "grunt-trimtrailingspaces": "^0.4.0",
     "node-minify": "~0.7.2",
-    "pegjs": "0.8.0",
     "sdp-transform": "~0.4.0",
     "grunt-contrib-copy": "^0.5.0",
     "browserify": "^4.1.8",
@@ -193,12 +82,16 @@ module.exports={
   "scripts": {
     "test": "grunt travis --verbose"
   },
+  "dependencies": {
+    "pegjs": "0.8.0",
+    "ws": "^0.4.32"
+  },
   "optionalDependencies": {
     "promiscuous": "^0.6.0"
   }
 }
 
-},{}],4:[function(_dereq_,module,exports){
+},{}],2:[function(_dereq_,module,exports){
 module.exports = function (SIP) {
 var ClientContext;
 
@@ -304,7 +197,7 @@ ClientContext.prototype.onTransportError = function () {
 SIP.ClientContext = ClientContext;
 };
 
-},{}],5:[function(_dereq_,module,exports){
+},{}],3:[function(_dereq_,module,exports){
 /**
  * @fileoverview SIP Constants
  */
@@ -466,7 +359,7 @@ return {
 };
 };
 
-},{}],6:[function(_dereq_,module,exports){
+},{}],4:[function(_dereq_,module,exports){
 
 /**
  * @fileoverview In-Dialog Request Sender
@@ -561,7 +454,7 @@ RequestSender.prototype = {
 return RequestSender;
 };
 
-},{}],7:[function(_dereq_,module,exports){
+},{}],5:[function(_dereq_,module,exports){
 /**
  * @fileoverview SIP Dialog
  */
@@ -819,7 +712,7 @@ Dialog.C = C;
 SIP.Dialog = Dialog;
 };
 
-},{"./Dialog/RequestSender":6}],8:[function(_dereq_,module,exports){
+},{"./Dialog/RequestSender":4}],6:[function(_dereq_,module,exports){
 
 /**
  * @fileoverview SIP Digest Authentication
@@ -989,7 +882,7 @@ DigestAuthentication.prototype.updateNcHex = function() {
 return DigestAuthentication;
 };
 
-},{}],9:[function(_dereq_,module,exports){
+},{}],7:[function(_dereq_,module,exports){
 /**
  * @fileoverview EventEmitter
  */
@@ -1191,7 +1084,7 @@ EventEmitter.C = C;
 SIP.EventEmitter = EventEmitter;
 };
 
-},{}],10:[function(_dereq_,module,exports){
+},{}],8:[function(_dereq_,module,exports){
 /**
  * @fileoverview Exceptions
  */
@@ -1245,9 +1138,27 @@ module.exports = {
   }())
 };
 
-},{}],11:[function(_dereq_,module,exports){
-/* jshint ignore:start */
-module.exports = function(SIP) {
+},{}],9:[function(_dereq_,module,exports){
+var Grammar = _dereq_('./Grammar/index.js');
+
+module.exports = function (SIP) {
+
+return {
+  parse: function parseCustom (input, startRule) {
+    var options = {startRule: startRule, SIP: SIP};
+    try {
+      Grammar.parse(input, options);
+    } catch (e) {
+      options.data = -1;
+    }
+    return options.data;
+  }
+};
+
+};
+
+},{"./Grammar/index.js":10}],10:[function(_dereq_,module,exports){
+module.exports = (function() {
   /*
    * Generated by PEG.js 0.8.0.
    *
@@ -1402,54 +1313,54 @@ module.exports = function(SIP) {
           /^[\x0E-]/,
           { type: "class", value: "[\\x0E-]", description: "[\\x0E-]" },
           function() {
-                                  data.uri = new SIP.URI(data.scheme, data.user, data.host, data.port);
-                                  delete data.scheme;
-                                  delete data.user;
-                                  delete data.host;
-                                  delete data.host_type;
-                                  delete data.port;
+                                  options.data.uri = new options.SIP.URI(options.data.scheme, options.data.user, options.data.host, options.data.port);
+                                  delete options.data.scheme;
+                                  delete options.data.user;
+                                  delete options.data.host;
+                                  delete options.data.host_type;
+                                  delete options.data.port;
                                 },
           function() {
-                                  data.uri = new SIP.URI(data.scheme, data.user, data.host, data.port, data.uri_params, data.uri_headers);
-                                  delete data.scheme;
-                                  delete data.user;
-                                  delete data.host;
-                                  delete data.host_type;
-                                  delete data.port;
-                                  delete data.uri_params;
-
-                                  if (options.startRule === 'SIP_URI') { data = data.uri;}
+                                  options.data.uri = new options.SIP.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers);
+                                  delete options.data.scheme;
+                                  delete options.data.user;
+                                  delete options.data.host;
+                                  delete options.data.host_type;
+                                  delete options.data.port;
+                                  delete options.data.uri_params;
+
+                                  if (options.startRule === 'SIP_URI') { options.data = options.data.uri;}
                                 },
           "sips",
           { type: "literal", value: "sips", description: "\"sips\"" },
           "sip",
           { type: "literal", value: "sip", description: "\"sip\"" },
           function(uri_scheme) {
-                              data.scheme = uri_scheme.toLowerCase(); },
+                              options.data.scheme = uri_scheme.toLowerCase(); },
           function() {
-                              data.user = decodeURIComponent(text().slice(0, -1));},
+                              options.data.user = decodeURIComponent(text().slice(0, -1));},
           function() {
-                              data.password = text(); },
+                              options.data.password = text(); },
           function() {
-                              data.host = text().toLowerCase();
-                              return data.host; },
+                              options.data.host = text().toLowerCase();
+                              return options.data.host; },
           function() {
-                            data.host_type = 'domain';
+                            options.data.host_type = 'domain';
                             return text(); },
           /^[a-zA-Z0-9_\-]/,
           { type: "class", value: "[a-zA-Z0-9_\\-]", description: "[a-zA-Z0-9_\\-]" },
           /^[a-zA-Z0-9\-]/,
           { type: "class", value: "[a-zA-Z0-9\\-]", description: "[a-zA-Z0-9\\-]" },
           function() {
-                              data.host_type = 'IPv6';
+                              options.data.host_type = 'IPv6';
                               return text(); },
           "::",
           { type: "literal", value: "::", description: "\"::\"" },
           function() {
-                            data.host_type = 'IPv6';
+                            options.data.host_type = 'IPv6';
                             return text(); },
           function() {
-                              data.host_type = 'IPv4';
+                              options.data.host_type = 'IPv4';
                               return text(); },
           "25",
           { type: "literal", value: "25", description: "\"25\"" },
@@ -1465,7 +1376,7 @@ module.exports = function(SIP) {
           { type: "class", value: "[1-9]", description: "[1-9]" },
           function(port) {
                               port = parseInt(port.join(''));
-                              data.port = port;
+                              options.data.port = port;
                               return port; },
           "transport=",
           { type: "literal", value: "transport=", description: "\"transport=\"" },
@@ -1478,8 +1389,8 @@ module.exports = function(SIP) {
           "tls",
           { type: "literal", value: "tls", description: "\"tls\"" },
           function(transport) {
-                                if(!data.uri_params) data.uri_params={};
-                                data.uri_params['transport'] = transport.toLowerCase(); },
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['transport'] = transport.toLowerCase(); },
           "user=",
           { type: "literal", value: "user=", description: "\"user=\"" },
           "phone",
@@ -1487,67 +1398,67 @@ module.exports = function(SIP) {
           "ip",
           { type: "literal", value: "ip", description: "\"ip\"" },
           function(user) {
-                                if(!data.uri_params) data.uri_params={};
-                                data.uri_params['user'] = user.toLowerCase(); },
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['user'] = user.toLowerCase(); },
           "method=",
           { type: "literal", value: "method=", description: "\"method=\"" },
           function(method) {
-                                if(!data.uri_params) data.uri_params={};
-                                data.uri_params['method'] = method; },
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['method'] = method; },
           "ttl=",
           { type: "literal", value: "ttl=", description: "\"ttl=\"" },
           function(ttl) {
-                                if(!data.params) data.params={};
-                                data.params['ttl'] = ttl; },
+                                if(!options.data.params) options.data.params={};
+                                options.data.params['ttl'] = ttl; },
           "maddr=",
           { type: "literal", value: "maddr=", description: "\"maddr=\"" },
           function(maddr) {
-                                if(!data.uri_params) data.uri_params={};
-                                data.uri_params['maddr'] = maddr; },
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['maddr'] = maddr; },
           "lr",
           { type: "literal", value: "lr", description: "\"lr\"" },
           function() {
-                                if(!data.uri_params) data.uri_params={};
-                                data.uri_params['lr'] = undefined; },
+                                if(!options.data.uri_params) options.data.uri_params={};
+                                options.data.uri_params['lr'] = undefined; },
           function(param, value) {
-                                if(!data.uri_params) data.uri_params = {};
+                                if(!options.data.uri_params) options.data.uri_params = {};
                                 if (value === null){
                                   value = undefined;
                                 }
                                 else {
                                   value = value[1];
                                 }
-                                data.uri_params[param.toLowerCase()] = value && value.toLowerCase();},
+                                options.data.uri_params[param.toLowerCase()] = value && value.toLowerCase();},
           function(pname) {return pname.join(''); },
           function(pvalue) {return pvalue.join(''); },
           function(hname, hvalue) {
                                 hname = hname.join('').toLowerCase();
                                 hvalue = hvalue.join('');
-                                if(!data.uri_headers) data.uri_headers = {};
-                                if (!data.uri_headers[hname]) {
-                                  data.uri_headers[hname] = [hvalue];
+                                if(!options.data.uri_headers) options.data.uri_headers = {};
+                                if (!options.data.uri_headers[hname]) {
+                                  options.data.uri_headers[hname] = [hvalue];
                                 } else {
-                                  data.uri_headers[hname].push(hvalue);
+                                  options.data.uri_headers[hname].push(hvalue);
                                 }},
           function() {
                                 // lots of tests fail if this isn't guarded...
                                 if (options.startRule === 'Refer_To') {
-                                  data.uri = new SIP.URI(data.scheme, data.user, data.host, data.port, data.uri_params, data.uri_headers);
-                                  delete data.scheme;
-                                  delete data.user;
-                                  delete data.host;
-                                  delete data.host_type;
-                                  delete data.port;
-                                  delete data.uri_params;
+                                  options.data.uri = new options.SIP.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers);
+                                  delete options.data.scheme;
+                                  delete options.data.user;
+                                  delete options.data.host;
+                                  delete options.data.host_type;
+                                  delete options.data.port;
+                                  delete options.data.uri_params;
                                 }
                               },
           "//",
           { type: "literal", value: "//", description: "\"//\"" },
           function() {
-                              data.scheme= text(); },
+                              options.data.scheme= text(); },
           { type: "literal", value: "SIP", description: "\"SIP\"" },
           function() {
-                              data.sip_version = text(); },
+                              options.data.sip_version = text(); },
           "INVITE",
           { type: "literal", value: "INVITE", description: "\"INVITE\"" },
           "ACK",
@@ -1570,40 +1481,40 @@ module.exports = function(SIP) {
           { type: "literal", value: "REFER", description: "\"REFER\"" },
           function() {
 
-                              data.method = text();
-                              return data.method; },
+                              options.data.method = text();
+                              return options.data.method; },
           function(status_code) {
-                            data.status_code = parseInt(status_code.join('')); },
+                            options.data.status_code = parseInt(status_code.join('')); },
           function() {
-                            data.reason_phrase = text(); },
+                            options.data.reason_phrase = text(); },
           function() {
-                        data = text(); },
+                        options.data = text(); },
           function() {
                                   var idx, length;
-                                  length = data.multi_header.length;
+                                  length = options.data.multi_header.length;
                                   for (idx = 0; idx < length; idx++) {
-                                    if (data.multi_header[idx].parsed === null) {
-                                      data = null;
+                                    if (options.data.multi_header[idx].parsed === null) {
+                                      options.data = null;
                                       break;
                                     }
                                   }
-                                  if (data !== null) {
-                                    data = data.multi_header;
+                                  if (options.data !== null) {
+                                    options.data = options.data.multi_header;
                                   } else {
-                                    data = -1;
+                                    options.data = -1;
                                   }},
           function() {
                                   var header;
-                                  if(!data.multi_header) data.multi_header = [];
+                                  if(!options.data.multi_header) options.data.multi_header = [];
                                   try {
-                                    header = new SIP.NameAddrHeader(data.uri, data.displayName, data.params);
-                                    delete data.uri;
-                                    delete data.displayName;
-                                    delete data.params;
+                                    header = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                                    delete options.data.uri;
+                                    delete options.data.displayName;
+                                    delete options.data.params;
                                   } catch(e) {
                                     header = null;
                                   }
-                                  data.multi_header.push( { 'position': peg$currPos,
+                                  options.data.multi_header.push( { 'position': peg$currPos,
                                                             'offset': offset(),
                                                             'parsed': header
                                                           });},
@@ -1612,17 +1523,17 @@ module.exports = function(SIP) {
                                   if (displayName[0] === '\"') {
                                     displayName = displayName.substring(1, displayName.length-1);
                                   }
-                                  data.displayName = displayName; },
+                                  options.data.displayName = displayName; },
           "q",
           { type: "literal", value: "q", description: "\"q\"" },
           function(q) {
-                                  if(!data.params) data.params = {};
-                                  data.params['q'] = q; },
+                                  if(!options.data.params) options.data.params = {};
+                                  options.data.params['q'] = q; },
           "expires",
           { type: "literal", value: "expires", description: "\"expires\"" },
           function(expires) {
-                                  if(!data.params) data.params = {};
-                                  data.params['expires'] = expires; },
+                                  if(!options.data.params) options.data.params = {};
+                                  options.data.params['expires'] = expires; },
           function(delta_seconds) {
                                   return parseInt(delta_seconds.join('')); },
           "0",
@@ -1630,14 +1541,14 @@ module.exports = function(SIP) {
           function() {
                                   return parseFloat(text()); },
           function(param, value) {
-                                  if(!data.params) data.params = {};
+                                  if(!options.data.params) options.data.params = {};
                                   if (value === null){
                                     value = undefined;
                                   }
                                   else {
                                     value = value[1];
                                   }
-                                  data.params[param.toLowerCase()] = value;},
+                                  options.data.params[param.toLowerCase()] = value;},
           "render",
           { type: "literal", value: "render", description: "\"render\"" },
           "session",
@@ -1648,7 +1559,7 @@ module.exports = function(SIP) {
           { type: "literal", value: "alert", description: "\"alert\"" },
           function() {
                                       if (options.startRule === 'Content_Disposition') {
-                                        data.type = text().toLowerCase();
+                                        options.data.type = text().toLowerCase();
                                       }
                                     },
           "handling",
@@ -1658,9 +1569,9 @@ module.exports = function(SIP) {
           "required",
           { type: "literal", value: "required", description: "\"required\"" },
           function(length) {
-                                  data = parseInt(length.join('')); },
+                                  options.data = parseInt(length.join('')); },
           function() {
-                                  data = text(); },
+                                  options.data = text(); },
           "text",
           { type: "literal", value: "text", description: "\"text\"" },
           "image",
@@ -1678,45 +1589,45 @@ module.exports = function(SIP) {
           "x-",
           { type: "literal", value: "x-", description: "\"x-\"" },
           function(cseq_value) {
-                            data.value=parseInt(cseq_value.join('')); },
-          function(expires) {data = expires; },
+                            options.data.value=parseInt(cseq_value.join('')); },
+          function(expires) {options.data = expires; },
           function(event_type) {
-                                 data.event = event_type.toLowerCase(); },
+                                 options.data.event = event_type.toLowerCase(); },
           function() {
-                          var tag = data.tag;
-                            data = new SIP.NameAddrHeader(data.uri, data.displayName, data.params);
-                            if (tag) {data.setParam('tag',tag)}
+                          var tag = options.data.tag;
+                            options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                            if (tag) {options.data.setParam('tag',tag)}
                           },
           "tag",
           { type: "literal", value: "tag", description: "\"tag\"" },
-          function(tag) {data.tag = tag; },
+          function(tag) {options.data.tag = tag; },
           function(forwards) {
-                            data = parseInt(forwards.join('')); },
-          function(min_expires) {data = min_expires; },
+                            options.data = parseInt(forwards.join('')); },
+          function(min_expires) {options.data = min_expires; },
           function() {
-                                  data = new SIP.NameAddrHeader(data.uri, data.displayName, data.params);
+                                  options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
                                 },
           "digest",
           { type: "literal", value: "Digest", description: "\"Digest\"" },
           "realm",
           { type: "literal", value: "realm", description: "\"realm\"" },
-          function(realm) { data.realm = realm; },
+          function(realm) { options.data.realm = realm; },
           "domain",
           { type: "literal", value: "domain", description: "\"domain\"" },
           "nonce",
           { type: "literal", value: "nonce", description: "\"nonce\"" },
-          function(nonce) { data.nonce=nonce; },
+          function(nonce) { options.data.nonce=nonce; },
           "opaque",
           { type: "literal", value: "opaque", description: "\"opaque\"" },
-          function(opaque) { data.opaque=opaque; },
+          function(opaque) { options.data.opaque=opaque; },
           "stale",
           { type: "literal", value: "stale", description: "\"stale\"" },
           "true",
           { type: "literal", value: "true", description: "\"true\"" },
-          function() { data.stale=true; },
+          function() { options.data.stale=true; },
           "false",
           { type: "literal", value: "false", description: "\"false\"" },
-          function() { data.stale=false; },
+          function() { options.data.stale=false; },
           "algorithm",
           { type: "literal", value: "algorithm", description: "\"algorithm\"" },
           "md5",
@@ -1724,7 +1635,7 @@ module.exports = function(SIP) {
           "md5-sess",
           { type: "literal", value: "MD5-sess", description: "\"MD5-sess\"" },
           function(algorithm) {
-                                data.algorithm=algorithm.toUpperCase(); },
+                                options.data.algorithm=algorithm.toUpperCase(); },
           "qop",
           { type: "literal", value: "qop", description: "\"qop\"" },
           "auth-int",
@@ -1732,44 +1643,44 @@ module.exports = function(SIP) {
           "auth",
           { type: "literal", value: "auth", description: "\"auth\"" },
           function(qop_value) {
-                                  data.qop || (data.qop=[]);
-                                  data.qop.push(qop_value.toLowerCase()); },
+                                  options.data.qop || (options.data.qop=[]);
+                                  options.data.qop.push(qop_value.toLowerCase()); },
           function(rack_value) {
-                            data.value=parseInt(rack_value.join('')); },
+                            options.data.value=parseInt(rack_value.join('')); },
           function() {
                             var idx, length;
-                            length = data.multi_header.length;
+                            length = options.data.multi_header.length;
                             for (idx = 0; idx < length; idx++) {
-                              if (data.multi_header[idx].parsed === null) {
-                                data = null;
+                              if (options.data.multi_header[idx].parsed === null) {
+                                options.data = null;
                                 break;
                               }
                             }
-                            if (data !== null) {
-                              data = data.multi_header;
+                            if (options.data !== null) {
+                              options.data = options.data.multi_header;
                             } else {
-                              data = -1;
+                              options.data = -1;
                             }},
           function() {
                             var header;
-                            if(!data.multi_header) data.multi_header = [];
+                            if(!options.data.multi_header) options.data.multi_header = [];
                             try {
-                              header = new SIP.NameAddrHeader(data.uri, data.displayName, data.params);
-                              delete data.uri;
-                              delete data.displayName;
-                              delete data.params;
+                              header = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                              delete options.data.uri;
+                              delete options.data.displayName;
+                              delete options.data.params;
                             } catch(e) {
                               header = null;
                             }
-                            data.multi_header.push( { 'position': peg$currPos,
+                            options.data.multi_header.push( { 'position': peg$currPos,
                                                       'offset': offset(),
                                                       'parsed': header
                                                     });},
           function() {
-                        data = new SIP.NameAddrHeader(data.uri, data.displayName, data.params);
+                        options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
                       },
           function(rseq_value) {
-                            data.value=parseInt(rseq_value.join('')); },
+                            options.data.value=parseInt(rseq_value.join('')); },
           "active",
           { type: "literal", value: "active", description: "\"active\"" },
           "pending",
@@ -1777,17 +1688,17 @@ module.exports = function(SIP) {
           "terminated",
           { type: "literal", value: "terminated", description: "\"terminated\"" },
           function() {
-                                  data.state = text(); },
+                                  options.data.state = text(); },
           "reason",
           { type: "literal", value: "reason", description: "\"reason\"" },
           function(reason) {
-                                  if (typeof reason !== 'undefined') data.reason = reason; },
+                                  if (typeof reason !== 'undefined') options.data.reason = reason; },
           function(expires) {
-                                  if (typeof expires !== 'undefined') data.expires = expires; },
+                                  if (typeof expires !== 'undefined') options.data.expires = expires; },
           "retry_after",
           { type: "literal", value: "retry_after", description: "\"retry_after\"" },
           function(retry_after) {
-                                  if (typeof retry_after !== 'undefined') data.retry_after = retry_after; },
+                                  if (typeof retry_after !== 'undefined') options.data.retry_after = retry_after; },
           "deactivated",
           { type: "literal", value: "deactivated", description: "\"deactivated\"" },
           "probation",
@@ -1803,43 +1714,43 @@ module.exports = function(SIP) {
           "invariant",
           { type: "literal", value: "invariant", description: "\"invariant\"" },
           function() {
-                        var tag = data.tag;
-                          data = new SIP.NameAddrHeader(data.uri, data.displayName, data.params);
-                          if (tag) {data.setParam('tag',tag)}
+                        var tag = options.data.tag;
+                          options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
+                          if (tag) {options.data.setParam('tag',tag)}
                         },
           "ttl",
           { type: "literal", value: "ttl", description: "\"ttl\"" },
           function(via_ttl_value) {
-                                data.ttl = via_ttl_value; },
+                                options.data.ttl = via_ttl_value; },
           "maddr",
           { type: "literal", value: "maddr", description: "\"maddr\"" },
           function(via_maddr) {
-                                data.maddr = via_maddr; },
+                                options.data.maddr = via_maddr; },
           "received",
           { type: "literal", value: "received", description: "\"received\"" },
           function(via_received) {
-                                data.received = via_received; },
+                                options.data.received = via_received; },
           "branch",
           { type: "literal", value: "branch", description: "\"branch\"" },
           function(via_branch) {
-                                data.branch = via_branch; },
+                                options.data.branch = via_branch; },
           "rport",
           { type: "literal", value: "rport", description: "\"rport\"" },
           function() {
                                 if(typeof response_port !== 'undefined')
-                                  data.rport = response_port.join(''); },
+                                  options.data.rport = response_port.join(''); },
           function(via_protocol) {
-                                data.protocol = via_protocol; },
+                                options.data.protocol = via_protocol; },
           { type: "literal", value: "UDP", description: "\"UDP\"" },
           { type: "literal", value: "TCP", description: "\"TCP\"" },
           { type: "literal", value: "TLS", description: "\"TLS\"" },
           { type: "literal", value: "SCTP", description: "\"SCTP\"" },
           function(via_transport) {
-                                data.transport = via_transport; },
+                                options.data.transport = via_transport; },
           function() {
-                                data.host = text(); },
+                                options.data.host = text(); },
           function(via_sent_by_port) {
-                                data.port = parseInt(via_sent_by_port.join('')); },
+                                options.data.port = parseInt(via_sent_by_port.join('')); },
           function(ttl) {
                                 return parseInt(ttl.join('')); },
           "stuns",
@@ -1847,9 +1758,9 @@ module.exports = function(SIP) {
           "stun",
           { type: "literal", value: "stun", description: "\"stun\"" },
           function(scheme) {
-                                data.scheme = scheme; },
+                                options.data.scheme = scheme; },
           function(host) {
-                                data.host = host; },
+                                options.data.host = host; },
           function() {
                                 return text(); },
           "?transport=",
@@ -1859,9 +1770,9 @@ module.exports = function(SIP) {
           "turn",
           { type: "literal", value: "turn", description: "\"turn\"" },
           function() {
-                                data.transport = transport; },
+                                options.data.transport = transport; },
           function() {
-                            data = text(); }
+                            options.data = text(); }
         ],
 
         peg$bytecode = [
@@ -2515,29 +2426,27 @@ module.exports = function(SIP) {
       return stack[0];
     }
 
-    var data = {};
+    options.data = {};
 
     peg$result = peg$parseRule(peg$startRuleIndex);
 
     if (peg$result !== peg$FAILED && peg$currPos === input.length) {
-      return data;
+      return peg$result;
     } else {
       if (peg$result !== peg$FAILED && peg$currPos < input.length) {
         peg$fail({ type: "end", description: "end of input" });
       }
 
-      return -1;
+      throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos);
     }
   }
 
   return {
     SyntaxError: SyntaxError,
-    parse:       function (input, startRule) {return parse(input, {startRule: startRule});}
+    parse:       parse
   };
-};
-/* jshint ignore:end */
-
-},{}],12:[function(_dereq_,module,exports){
+})();
+},{}],11:[function(_dereq_,module,exports){
 /**
  * @fileoverview Hacks - This file contains all of the things we
  * wish we didn't have to do, just for interop.  It is similar to
@@ -2650,9 +2559,7 @@ var Hacks = module.exports = {
   }
 };
 
-},{}],13:[function(_dereq_,module,exports){
-var console = _dereq_('console');
-
+},{}],12:[function(_dereq_,module,exports){
 var levels = {
   'error': 0,
   'warn': 1,
@@ -2660,7 +2567,9 @@ var levels = {
   'debug': 3
 };
 
-var LoggerFactory = module.exports = function () {
+module.exports = function (console) {
+
+var LoggerFactory = function () {
   var logger,
     level = 2,
     builtinEnabled = true,
@@ -2762,7 +2671,10 @@ LoggerFactory.prototype.getLogger = function(category, label) {
   }
 };
 
-},{"console":35}],14:[function(_dereq_,module,exports){
+return LoggerFactory;
+};
+
+},{}],13:[function(_dereq_,module,exports){
 /**
  * @fileoverview MediaHandler
  */
@@ -2806,7 +2718,7 @@ MediaHandler.prototype = Object.create(EventEmitter.prototype, {
 return MediaHandler;
 };
 
-},{}],15:[function(_dereq_,module,exports){
+},{}],14:[function(_dereq_,module,exports){
 /**
  * @fileoverview SIP NameAddrHeader
  */
@@ -2904,7 +2816,7 @@ NameAddrHeader.parse = function(name_addr_header) {
 SIP.NameAddrHeader = NameAddrHeader;
 };
 
-},{}],16:[function(_dereq_,module,exports){
+},{}],15:[function(_dereq_,module,exports){
 /**
  * @fileoverview SIP Message Parser
  */
@@ -3166,7 +3078,7 @@ Parser.parseMessage = function(data, ua) {
 SIP.Parser = Parser;
 };
 
-},{}],17:[function(_dereq_,module,exports){
+},{}],16:[function(_dereq_,module,exports){
 module.exports = function (SIP) {
 
 var RegisterContext;
@@ -3445,7 +3357,7 @@ RegisterContext.prototype = {
 SIP.RegisterContext = RegisterContext;
 };
 
-},{}],18:[function(_dereq_,module,exports){
+},{}],17:[function(_dereq_,module,exports){
 
 /**
  * @fileoverview Request Sender
@@ -3585,8 +3497,7 @@ RequestSender.prototype = {
 SIP.RequestSender = RequestSender;
 };
 
-},{}],19:[function(_dereq_,module,exports){
-(function (global){
+},{}],18:[function(_dereq_,module,exports){
 /**
  * @name SIP
  * @namespace
@@ -3594,7 +3505,7 @@ SIP.RequestSender = RequestSender;
 "use strict";
 
 var SIP = {};
-module.exports = SIP;
+module.exports = function (environment) {
 
 var pkg = _dereq_('../package.json');
 
@@ -3607,13 +3518,13 @@ Object.defineProperties(SIP, {
   }
 });
 
-_dereq_('./Utils')(SIP);
-SIP.LoggerFactory = _dereq_('./LoggerFactory');
+_dereq_('./Utils')(SIP, environment);
+SIP.LoggerFactory = _dereq_('./LoggerFactory')(environment.console);
 _dereq_('./EventEmitter')(SIP);
 SIP.C = _dereq_('./Constants')(SIP.name, SIP.version);
 SIP.Exceptions = _dereq_('./Exceptions');
-SIP.Timers = _dereq_('./Timers');
-_dereq_('./Transport')(SIP, global.WebSocket);
+SIP.Timers = _dereq_('./Timers')(environment.timers);
+_dereq_('./Transport')(SIP, environment.WebSocket);
 _dereq_('./Parser')(SIP);
 _dereq_('./SIPMessage')(SIP);
 _dereq_('./URI')(SIP);
@@ -3625,17 +3536,19 @@ _dereq_('./RegisterContext')(SIP);
 SIP.MediaHandler = _dereq_('./MediaHandler')(SIP.EventEmitter);
 _dereq_('./ClientContext')(SIP);
 _dereq_('./ServerContext')(SIP);
-_dereq_('./Session')(SIP);
+_dereq_('./Session')(SIP, environment);
 _dereq_('./Subscription')(SIP);
-SIP.WebRTC = _dereq_('./WebRTC')(SIP);
+SIP.WebRTC = _dereq_('./WebRTC')(SIP, environment);
 _dereq_('./UA')(SIP);
 SIP.Hacks = _dereq_('./Hacks');
 _dereq_('./SanityCheck')(SIP);
 SIP.DigestAuthentication = _dereq_('./DigestAuthentication')(SIP.Utils);
-SIP.Grammar = _dereq_('./Grammar/dist/Grammar')(SIP);
+SIP.Grammar = _dereq_('./Grammar')(SIP);
 
-}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"../package.json":3,"./ClientContext":4,"./Constants":5,"./Dialogs":7,"./DigestAuthentication":8,"./EventEmitter":9,"./Exceptions":10,"./Grammar/dist/Grammar":11,"./Hacks":12,"./LoggerFactory":13,"./MediaHandler":14,"./NameAddrHeader":15,"./Parser":16,"./RegisterContext":17,"./RequestSender":18,"./SIPMessage":20,"./SanityCheck":21,"./ServerContext":22,"./Session":23,"./Subscription":25,"./Timers":26,"./Transactions":27,"./Transport":28,"./UA":29,"./URI":30,"./Utils":31,"./WebRTC":32}],20:[function(_dereq_,module,exports){
+return SIP;
+};
+
+},{"../package.json":1,"./ClientContext":2,"./Constants":3,"./Dialogs":5,"./DigestAuthentication":6,"./EventEmitter":7,"./Exceptions":8,"./Grammar":9,"./Hacks":11,"./LoggerFactory":12,"./MediaHandler":13,"./NameAddrHeader":14,"./Parser":15,"./RegisterContext":16,"./RequestSender":17,"./SIPMessage":19,"./SanityCheck":20,"./ServerContext":21,"./Session":22,"./Subscription":24,"./Timers":25,"./Transactions":26,"./Transport":27,"./UA":28,"./URI":29,"./Utils":30,"./WebRTC":31}],19:[function(_dereq_,module,exports){
 /**
  * @fileoverview SIP Message
  */
@@ -4165,7 +4078,7 @@ SIP.IncomingRequest = IncomingRequest;
 SIP.IncomingResponse = IncomingResponse;
 };
 
-},{}],21:[function(_dereq_,module,exports){
+},{}],20:[function(_dereq_,module,exports){
 /**
  * @fileoverview Incoming SIP Message Sanity Check
  */
@@ -4394,7 +4307,7 @@ sanityCheck = function(m, u, t) {
 SIP.sanityCheck = sanityCheck;
 };
 
-},{}],22:[function(_dereq_,module,exports){
+},{}],21:[function(_dereq_,module,exports){
 module.exports = function (SIP) {
 var ServerContext;
 
@@ -4479,9 +4392,8 @@ ServerContext.prototype.onTransportError = function () {
 SIP.ServerContext = ServerContext;
 };
 
-},{}],23:[function(_dereq_,module,exports){
-(function (global){
-module.exports = function (SIP) {
+},{}],22:[function(_dereq_,module,exports){
+module.exports = function (SIP, environment) {
 
 var DTMF = _dereq_('./Session/DTMF')(SIP);
 
@@ -4742,10 +4654,10 @@ Session.prototype = {
       var target = request.parseHeader('refer-to').uri;
       if (!target.scheme.match("^sips?$")) {
         var targetString = target.toString();
-        if (typeof global.open === "function") {
-          global.open(targetString);
+        if (typeof environment.open === "function") {
+          environment.open(targetString);
         } else {
-          this.logger.warn("referred to non-SIP URI but `open` isn't a global function: " + targetString);
+          this.logger.warn("referred to non-SIP URI but `open` isn't in the environment: " + targetString);
         }
         return;
       }
@@ -5436,6 +5348,7 @@ InviteServerContext = function(ua, request) {
   //TODO: move this into media handler
   SIP.Hacks.Firefox.cannotHandleRelayCandidates(request);
   SIP.Hacks.Firefox.cannotHandleExtraWhitespace(request);
+  SIP.Hacks.AllBrowsers.maskDtls(request);
 
   SIP.Utils.augment(this, SIP.ServerContext, [ua, request]);
   SIP.Utils.augment(this, SIP.Session, [ua.configuration.mediaHandlerFactory]);
@@ -5900,6 +5813,7 @@ InviteServerContext.prototype = {
             // ACK contains answer to an INVITE w/o SDP negotiation
             SIP.Hacks.Firefox.cannotHandleRelayCandidates(request);
             SIP.Hacks.Firefox.cannotHandleExtraWhitespace(request);
+            SIP.Hacks.AllBrowsers.maskDtls(request);
 
             this.hasAnswer = true;
             this.mediaHandler.setDescription(request.body)
@@ -6569,8 +6483,7 @@ SIP.InviteClientContext = InviteClientContext;
 
 };
 
-}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"./Session/DTMF":24}],24:[function(_dereq_,module,exports){
+},{"./Session/DTMF":23}],23:[function(_dereq_,module,exports){
 /**
  * @fileoverview DTMF
  */
@@ -6755,7 +6668,7 @@ DTMF.C = C;
 return DTMF;
 };
 
-},{}],25:[function(_dereq_,module,exports){
+},{}],24:[function(_dereq_,module,exports){
 
 /**
  * @fileoverview SIP Subscriber (SIP-Specific Event Notifications RFC6665)
@@ -7045,7 +6958,7 @@ SIP.Subscription.prototype = {
 };
 };
 
-},{}],26:[function(_dereq_,module,exports){
+},{}],25:[function(_dereq_,module,exports){
 /**
  * @fileoverview SIP TIMERS
  */
@@ -7054,11 +6967,11 @@ SIP.Subscription.prototype = {
  * @augments SIP
  */
 var
-  timers = _dereq_('timers'),
   T1 = 500,
   T2 = 4000,
   T4 = 5000;
-module.exports = {
+module.exports = function (timers) {
+  var Timers = {
     T1: T1,
     T2: T2,
     T4: T4,
@@ -7079,12 +6992,15 @@ module.exports = {
   .forEach(function (name) {
     // can't just use timers[name].bind(timers) since it bypasses jasmine's
     // clock-mocking
-    module.exports[name] = function () {
+    Timers[name] = function () {
       return timers[name].apply(timers, arguments);
     };
   });
 
-},{"timers":1}],27:[function(_dereq_,module,exports){
+  return Timers;
+};
+
+},{}],26:[function(_dereq_,module,exports){
 /**
  * @fileoverview SIP Transactions
  */
@@ -7800,7 +7716,7 @@ SIP.Transactions = {
 
 };
 
-},{}],28:[function(_dereq_,module,exports){
+},{}],27:[function(_dereq_,module,exports){
 /**
  * @fileoverview Transport
  */
@@ -8098,7 +8014,8 @@ Transport.C = C;
 SIP.Transport = Transport;
 };
 
-},{}],29:[function(_dereq_,module,exports){
+},{}],28:[function(_dereq_,module,exports){
+(function (global){
 /**
  * @augments SIP
  * @class Class creating a SIP User Agent.
@@ -8295,6 +8212,10 @@ UA = function(configuration) {
   if(this.configuration.autostart) {
     this.start();
   }
+
+  if (typeof global.addEventListener === 'function') {
+    global.addEventListener('unload', this.stop.bind(this));
+  }
 };
 UA.prototype = new SIP.EventEmitter();
 
@@ -9024,14 +8945,19 @@ UA.prototype.loadConfig = function(configuration) {
 
   SIP.Utils.optionsOverride(configuration, 'rel100', 'reliable', true, this.logger, SIP.C.supported.UNSUPPORTED);
 
+  var emptyArraysAllowed = ['stunServers', 'turnServers'];
+
   // Check Optional parameters
   for(parameter in UA.configuration_check.optional) {
     aliasUnderscored(parameter, this.logger);
     if(configuration.hasOwnProperty(parameter)) {
       value = configuration[parameter];
 
-      // If the parameter value is null, empty string,undefined, or empty array then apply its default value.
-      if(value === null || value === "" || value === undefined || (value instanceof Array && value.length === 0)) { continue; }
+      // If the parameter value is an empty array, but shouldn't be, apply its default value.
+      if (value instanceof Array && value.length === 0 && emptyArraysAllowed.indexOf(parameter) < 0) { continue; }
+
+      // If the parameter value is null, empty string, or undefined then apply its default value.
+      if(value === null || value === "" || value === undefined) { continue; }
       // If it's a number with NaN value then also apply its default value.
       // NOTE: JS does not allow "value === NaN", the following does the work:
       else if(typeof(value) === 'number' && isNaN(value)) { continue; }
@@ -9557,7 +9483,8 @@ UA.C = C;
 SIP.UA = UA;
 };
 
-},{}],30:[function(_dereq_,module,exports){
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],29:[function(_dereq_,module,exports){
 /**
  * @fileoverview SIP URI
  */
@@ -9760,20 +9687,17 @@ URI.parse = function(uri) {
 SIP.URI = URI;
 };
 
-},{}],31:[function(_dereq_,module,exports){
-(function (global){
+},{}],30:[function(_dereq_,module,exports){
 /**
  * @fileoverview Utils
  */
 
-var promise = global.Promise || _dereq_('promiscuous');
-
-module.exports = function (SIP) {
+module.exports = function (SIP, environment) {
 var Utils;
 
 Utils= {
 
-  Promise: promise,
+  Promise: environment.Promise,
 
   defer: function defer () {
     var deferred = {};
@@ -9784,26 +9708,6 @@ Utils= {
     return deferred;
   },
 
-  callbacksLast: function callbacksLast (f, thisArg) {
-    return function (arg, onSuccess, onFailure) {
-      return f.call(thisArg, onSuccess, onFailure, arg);
-    };
-  },
-
-  addPromise: function addPromise (f, thisArg, length) {
-    var callbacksIndex = (length || f.length) - 2;
-    return function withPromise () {
-      var nonCallbacks = [].slice.call(arguments, 0, callbacksIndex);
-      var bound = f.bind.apply(f, [thisArg].concat(nonCallbacks));
-      var promise = new Utils.Promise(bound);
-      var callbacks = [].slice.call(arguments, callbacksIndex);
-      if (callbacks.length) {
-        promise.then.apply(promise, callbacks);
-      }
-      return promise;
-    };
-  },
-
   augment: function (object, constructor, args, override) {
     var idx, proto;
 
@@ -9835,20 +9739,6 @@ Utils= {
     return encodeURIComponent(string).replace(/%[A-F\d]{2}/g, 'U').length;
   },
 
-  getPrefixedProperty: function (object, name) {
-    if (object == null) {
-      return;
-    }
-    var capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
-    var prefixedNames = [name, 'webkit' + capitalizedName, 'moz' + capitalizedName];
-    for (var i in prefixedNames) {
-      var property = object[prefixedNames[i]];
-      if (property) {
-        return property;
-      }
-    }
-  },
-
   generateFakeSDP: function(body) {
     if (!body) {
       return;
@@ -10292,20 +10182,18 @@ Utils= {
 SIP.Utils = Utils;
 };
 
-}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"promiscuous":36}],32:[function(_dereq_,module,exports){
-(function (global){
+},{}],31:[function(_dereq_,module,exports){
 /**
  * @fileoverview WebRTC
  */
 
-module.exports = function (SIP) {
+module.exports = function (SIP, environment) {
 var WebRTC;
 
 WebRTC = {};
 
 WebRTC.MediaHandler = _dereq_('./WebRTC/MediaHandler')(SIP);
-WebRTC.MediaStreamManager = _dereq_('./WebRTC/MediaStreamManager')(SIP);
+WebRTC.MediaStreamManager = _dereq_('./WebRTC/MediaStreamManager')(SIP, environment);
 
 var _isSupported;
 
@@ -10314,13 +10202,18 @@ WebRTC.isSupported = function () {
     return _isSupported;
   }
 
-  WebRTC.MediaStream = SIP.Utils.getPrefixedProperty(global, 'MediaStream');
-  WebRTC.getUserMedia = SIP.Utils.getPrefixedProperty(global.navigator, 'getUserMedia');
-  WebRTC.RTCPeerConnection = SIP.Utils.getPrefixedProperty(global, 'RTCPeerConnection');
-  WebRTC.RTCSessionDescription = SIP.Utils.getPrefixedProperty(global, 'RTCSessionDescription');
+  WebRTC.MediaStream = environment.MediaStream;
+  WebRTC.getUserMedia = environment.getUserMedia;
+  WebRTC.RTCPeerConnection = environment.RTCPeerConnection;
+  WebRTC.RTCSessionDescription = environment.RTCSessionDescription;
 
-  if (WebRTC.getUserMedia && WebRTC.RTCPeerConnection && WebRTC.RTCSessionDescription) {
-    WebRTC.getUserMedia = SIP.Utils.addPromise(WebRTC.getUserMedia, global.navigator);
+  if (WebRTC.RTCPeerConnection && WebRTC.RTCSessionDescription) {
+    if (WebRTC.getUserMedia) {
+      WebRTC.getUserMedia = function (constraints, onSuccess, onFailure) {
+        return new SIP.Utils.Promise(this.bind(null, constraints))
+          .then(onSuccess, onFailure);
+      }.bind(WebRTC.getUserMedia);
+    }
     _isSupported = true;
   }
   else {
@@ -10332,8 +10225,7 @@ WebRTC.isSupported = function () {
 return WebRTC;
 };
 
-}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"./WebRTC/MediaHandler":33,"./WebRTC/MediaStreamManager":34}],33:[function(_dereq_,module,exports){
+},{"./WebRTC/MediaHandler":32,"./WebRTC/MediaStreamManager":33}],32:[function(_dereq_,module,exports){
 /**
  * @fileoverview MediaHandler
  */
@@ -10529,10 +10421,9 @@ MediaHandler.prototype = Object.create(SIP.MediaHandler.prototype, {
     this.mediaHint = mediaHint;
 
     /*
-     * 1. acquire stream (skip if MediaStream passed in)
-     * 2. addStream
+     * 1. acquire streams (skip if MediaStreams passed in)
+     * 2. addStreams
      * 3. createOffer/createAnswer
-     * 4. call onSuccess()
      */
 
     var streamPromise;
@@ -10543,18 +10434,18 @@ MediaHandler.prototype = Object.create(SIP.MediaHandler.prototype, {
     else {
       self.logger.log('acquiring local media');
       streamPromise = self.mediaStreamManager.acquire(mediaHint)
-        .then(function acquireSucceeded(stream) {
-          self.logger.log('acquired local media stream');
-          self.localMedia = stream;
+        .then(function acquireSucceeded(streams) {
+          self.logger.log('acquired local media streams');
+          self.localMedia = streams;
           self.session.connecting();
-          return stream;
+          return streams;
         }, function acquireFailed(err) {
-          self.logger.error('unable to acquire stream');
+          self.logger.error('unable to acquire streams');
           self.logger.error(err);
           self.session.connecting();
           throw err;
         })
-        .then(this.addStream.bind(this))
+        .then(this.addStreams.bind(this))
       ;
     }
 
@@ -10729,9 +10620,7 @@ MediaHandler.prototype = Object.create(SIP.MediaHandler.prototype, {
     Object.keys(streamGetters).forEach(function (loc) {
       var streamGetter = streamGetters[loc];
       var streams = this[streamGetter]();
-      if (streams.length) {
-        SIP.WebRTC.MediaStreamManager.render(streams[0], renderHint[loc]);
-      }
+      SIP.WebRTC.MediaStreamManager.render(streams, renderHint[loc]);
     }.bind(this));
   }},
 
@@ -10746,12 +10635,17 @@ MediaHandler.prototype = Object.create(SIP.MediaHandler.prototype, {
     var self = this;
     var methodName, promisifiedMethod;
     var pc = self.peerConnection;
-    var setLocalDescription = SIP.Utils.addPromise(pc.setLocalDescription, pc, 3);
+    var setLocalDescription = function (description) {
+      return new SIP.Utils.Promise(pc.setLocalDescription.bind(pc, description));
+    };
 
     self.ready = false;
-
     methodName = self.hasOffer('remote') ? 'createAnswer' : 'createOffer';
-    promisifiedMethod = SIP.Utils.addPromise(SIP.Utils.callbacksLast(pc[methodName], pc));
+    promisifiedMethod = function (constraints) {
+      return new SIP.Utils.Promise(function (resolve, reject) {
+        pc[methodName](resolve, reject, constraints);
+      });
+    };
 
     return promisifiedMethod(constraints)
       .then(setLocalDescription)
@@ -10788,9 +10682,11 @@ MediaHandler.prototype = Object.create(SIP.MediaHandler.prototype, {
     ;
   }},
 
-  addStream: {writable: true, value: function addStream (stream) {
+  addStreams: {writable: true, value: function addStreams (streams) {
     try {
-      this.peerConnection.addStream(stream);
+      streams.forEach(function (stream) {
+        this.peerConnection.addStream(stream);
+      }, this);
     } catch(e) {
       this.logger.error('error adding stream');
       this.logger.error(e);
@@ -10821,8 +10717,7 @@ MediaHandler.prototype = Object.create(SIP.MediaHandler.prototype, {
 return MediaHandler;
 };
 
-},{}],34:[function(_dereq_,module,exports){
-(function (global){
+},{}],33:[function(_dereq_,module,exports){
 /**
  * @fileoverview MediaStreamManager
  */
@@ -10831,7 +10726,7 @@ return MediaHandler;
  * @class Manages the acquisition and release of MediaStreams.
  * @param {mediaHint} [defaultMediaHint] The mediaHint to use if none is provided to acquire()
  */
-module.exports = function (SIP) {
+module.exports = function (SIP, environment) {
 
 // Default MediaStreamManager provides single-use streams created with getUserMedia
 var MediaStreamManager = function MediaStreamManager (logger, defaultMediaHint) {
@@ -10864,20 +10759,22 @@ MediaStreamManager.streamId = function (stream) {
     .join('');
 };
 
-MediaStreamManager.render = function render (stream, elements) {
-  if (!elements) {
+MediaStreamManager.render = function render (streams, elements) {
+  // only render first stream, see pull request #76
+  var stream = streams[0];
+  if (!elements || !stream) {
     return false;
   }
 
   function attachAndPlay (element, stream) {
-    (global.attachMediaStream || attachMediaStream)(element, stream);
+    (environment.attachMediaStream || attachMediaStream)(element, stream);
     ensureMediaPlaying(element);
   }
 
   function attachMediaStream(element, stream) {
     if (typeof element.src !== 'undefined') {
-      global.URL.revokeObjectURL(element.src);
-      element.src = global.URL.createObjectURL(stream);
+      environment.revokeObjectURL(element.src);
+      element.src = environment.createObjectURL(stream);
     } else if (typeof (element.srcObject || element.mozSrcObject) !== 'undefined') {
       element.srcObject = element.mozSrcObject = stream;
     } else {
@@ -10914,10 +10811,13 @@ MediaStreamManager.prototype = Object.create(SIP.EventEmitter.prototype, {
   'acquire': {value: function acquire (mediaHint) {
     mediaHint = Object.keys(mediaHint || {}).length ? mediaHint : this.mediaHint;
 
-    var saveSuccess = function (isHintStream, stream) {
-      var streamId = MediaStreamManager.streamId(stream);
-      this.acquisitions[streamId] = !!isHintStream;
-      return SIP.Utils.Promise.resolve(stream);
+    var saveSuccess = function (isHintStream, streams) {
+      streams = [].concat(streams);
+      streams.forEach(function (stream) {
+        var streamId = MediaStreamManager.streamId(stream);
+        this.acquisitions[streamId] = !!isHintStream;
+      }, this);
+      return SIP.Utils.Promise.resolve(streams);
     }.bind(this);
 
     if (mediaHint.stream) {
@@ -10960,12 +10860,15 @@ MediaStreamManager.prototype = Object.create(SIP.EventEmitter.prototype, {
     }
   }},
 
-  'release': {value: function release (stream) {
-    var streamId = MediaStreamManager.streamId(stream);
-    if (this.acquisitions[streamId] === false) {
-      stream.stop();
-    }
-    delete this.acquisitions[streamId];
+  'release': {value: function release (streams) {
+    streams = [].concat(streams);
+    streams.forEach(function (stream) {
+      var streamId = MediaStreamManager.streamId(stream);
+      if (this.acquisitions[streamId] === false) {
+        stream.stop();
+      }
+      delete this.acquisitions[streamId];
+    }, this);
   }},
 });
 
@@ -10973,22 +10876,50 @@ MediaStreamManager.prototype = Object.create(SIP.EventEmitter.prototype, {
 return MediaStreamManager;
 };
 
-}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{}],35:[function(_dereq_,module,exports){
+},{}],34:[function(_dereq_,module,exports){
 (function (global){
-// Console is not defined in ECMAScript, so just in case...
-module.exports = global.console || {
-  debug: function () {},
-  log: function () {},
-  warn: function () {},
-  error: function () {}
+function getPrefixedProperty (object, name) {
+  if (object == null) {
+    return;
+  }
+  var capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
+  var prefixedNames = [name, 'webkit' + capitalizedName, 'moz' + capitalizedName];
+  for (var i in prefixedNames) {
+    var property = object[prefixedNames[i]];
+    if (property) {
+      return property.bind(object);
+    }
+  }
+}
+
+module.exports = {
+  WebSocket: global.WebSocket,
+  open: global.open,
+  Promise: global.Promise,
+  timers: global,
+
+  // Console is not defined in ECMAScript, so just in case...
+  console: global.console || {
+    debug: function () {},
+    log: function () {},
+    warn: function () {},
+    error: function () {}
+  },
+
+  MediaStream: getPrefixedProperty(global, 'MediaStream'),
+  getUserMedia: getPrefixedProperty(global.navigator, 'getUserMedia'),
+  RTCPeerConnection: getPrefixedProperty(global, 'RTCPeerConnection'),
+  RTCSessionDescription: getPrefixedProperty(global, 'RTCSessionDescription'),
+
+  attachMediaStream: global.attachMediaStream,
+  createObjectURL: global.URL && global.URL.createObjectURL,
+  revokeObjectURL: global.URL && global.URL.revokeObjectURL
 };
 
 }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{}],36:[function(_dereq_,module,exports){
-/**@license MIT-promiscuous-©Ruben Verborgh*/
-(function(n,t){function c(n,t){return(typeof t)[0]==n}function u(o,e){return e=function f(i,h,l,a,p,s){function y(n){return function(t){p&&(p=0,f(c,n,t))}}if(a=f.q,i!=c)return u(function(n,t){a.push({p:this,r:n,j:t,1:i,0:h})});if(l&&c(n,l)|c(t,l))try{p=l.then}catch(j){h=0,l=j}if(c(n,p))try{p.call(l,y(1),h=y(0))}catch(j){h(j)}else for(e=function(t,e){return c(n,t=h?t:e)?u(function(n,c){r(this,n,c,l,t)}):o},s=0;a.length>s;)p=a[s++],c(n,i=p[h])?r(p.p,p.r,p.j,l,i):(h?p.r:p.j)(l)},e.q=[],o.call(o={then:function(n,t){return e(n,t)},"catch":function(n){return e(0,n)}},function(n){e(c,1,n)},function(n){e(c,0,n)}),o}function r(u,r,o,e,f){setTimeout(function(){try{e=f(e),f=e&&c(t,e)|c(n,e)&&e.then,c(n,f)?e==u?o(TypeError()):f.call(e,r,o):r(e)}catch(i){o(i)}})}function o(n){return u(function(t){t(n)})}module.exports=u,u.resolve=o,u.reject=function(n){return u(function(t,c){c(n)})},u.all=function(n){return u(function(t,c,u,r){r=[],u=n.length||t(r),n.map(function(n,e){o(n).then(function(n){r[e]=n,--u||t(r)},c)})})}})("f","o");
+},{}],35:[function(_dereq_,module,exports){
+module.exports = _dereq_('./SIP')(_dereq_('./environment'));
 
-},{}]},{},[19])
-(19)
+},{"./SIP":18,"./environment":34}]},{},[35])
+(35)
 });
\ No newline at end of file
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/WebRTCCallManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/WebRTCCallManager.as
index bb5e7a3a1f41b3e4690640ca1e4fdaada4cb0bae..61c20f8a6b08926e6d21e7d1ec44b25148bb6a0f 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/WebRTCCallManager.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/WebRTCCallManager.as
@@ -175,8 +175,8 @@ package org.bigbluebutton.modules.phone.managers
     public function handleWebRTCEchoTestFailedEvent(errorCode:Number):void {
       model.state = Constants.INITED;
       endEchoTest();
-	  var errorString = ResourceUtil.getInstance().getString("bbb.webrtcWarning.failedError." + errorCode);
-	  if (errorString == null) {
+	  var errorString:String = ResourceUtil.getInstance().getString("bbb.webrtcWarning.failedError." + errorCode);
+	  if (!errorString) {
 		  errorString = ResourceUtil.getInstance().getString("bbb.webrtcWarning.failedError.unknown", [errorCode]);
 	  }
 	  
@@ -190,8 +190,8 @@ package org.bigbluebutton.modules.phone.managers
     
     public function handleWebRTCCallFailedEvent(errorCode:Number):void {
       model.state = Constants.INITED;
-	  var errorString = ResourceUtil.getInstance().getString("bbb.webrtcWarning.failedError." + errorCode);
-	  if (errorString == null) {
+	  var errorString:String = ResourceUtil.getInstance().getString("bbb.webrtcWarning.failedError." + errorCode);
+	  if (!errorString) {
 		  errorString = ResourceUtil.getInstance().getString("bbb.webrtcWarning.failedError.unknown", [errorCode]);
 	  }
       var alert:Alert = Alert.show(ResourceUtil.getInstance().getString("bbb.webrtcWarning.message", [errorString]), ResourceUtil.getInstance().getString("bbb.webrtcWarning.title"), Alert.YES | Alert.NO, null, handleCallFailedUserResponse, null, Alert.YES);