diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
index 3fd0794733df3dc357f3997f9375d5501340911d..e3bf03f8866591699996783e6f00f7f39bfd4519 100644
--- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
+++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
@@ -2,23 +2,24 @@ import VoiceUsers from '/imports/api/voice-users';
 import { Tracker } from 'meteor/tracker';
 import BaseAudioBridge from './base';
 
-const STUN_TURN_FETCH_URL = Meteor.settings.public.media.stunTurnServersFetchAddress;
-const MEDIA_TAG = Meteor.settings.public.media.mediaTag;
-const CALL_TRANSFER_TIMEOUT = Meteor.settings.public.media.callTransferTimeout;
-
-const handleStunTurnResponse = ({ result, stunServers, turnServers }) =>
-  new Promise((resolve) => {
-    if (result) {
-      resolve({ error: 404, stun: [], turn: [] });
-    }
-    resolve({
-      stun: stunServers.map(server => server.url),
-      turn: turnServers.map(server => server.url),
-    });
-  });
+const MEDIA = Meteor.settings.public.media;
+const STUN_TURN_FETCH_URL = MEDIA.stunTurnServersFetchAddress;
+const MEDIA_TAG = MEDIA.mediaTag;
+const CALL_TRANSFER_TIMEOUT = MEDIA.callTransferTimeout;
 
 const fetchStunTurnServers = sessionToken =>
   new Promise(async (resolve, reject) => {
+    const handleStunTurnResponse = ({ result, stunServers, turnServers }) =>
+      new Promise((resolve) => {
+        if (result) {
+          resolve({ error: 404, stun: [], turn: [] });
+        }
+        resolve({
+          stun: stunServers.map(server => server.url),
+          turn: turnServers.map(server => server.url),
+        });
+      });
+
     const url = `${STUN_TURN_FETCH_URL}?sessionToken=${sessionToken}`;
 
     const response = await fetch(url)
@@ -29,34 +30,28 @@ const fetchStunTurnServers = sessionToken =>
     return resolve(response);
   });
 
-const inviteUserAgent = (voiceBridge, server, userAgent, inputStream) => {
-  const options = {
-    media: {
-      stream: inputStream,
-      constraints: {
-        audio: true,
-        video: false,
-      },
-      render: {
-        remote: document.querySelector(MEDIA_TAG),
-      },
-    },
-    RTCConstraints: {
-      mandatory: {
-        OfferToReceiveAudio: true,
-        OfferToReceiveVideo: false,
-      },
-    },
-  };
-
-  return userAgent.invite(`sip:${voiceBridge}@${server}`, options);
-};
-
 export default class SIPBridge extends BaseAudioBridge {
   constructor(userData) {
     super(userData);
-    this.isConnected = false;
 
+    const {
+      userId,
+      username,
+      sessionToken,
+    } = userData;
+
+    this.user = {
+      userId,
+      sessionToken,
+      name: username,
+    };
+
+    this.media = {
+      inputDevice: {},
+    };
+
+    this.protocol = window.document.location.protocol;
+    this.hostname = window.document.location.hostname;
     this.errorCodes = {
       'Request Timeout': this.baseErrorCodes.REQUEST_TIMEOUT,
       'Invalid Target': this.baseErrorCodes.INVALID_TARGET,
@@ -82,16 +77,27 @@ export default class SIPBridge extends BaseAudioBridge {
     });
   }
 
-  transferCall(onTransferStart, onTransferSuccess) {
+  transferCall(onTransferSuccess) {
     return new Promise((resolve, reject) => {
-      onTransferStart();
-      this.currentSession.dtmf(1);
       let trackerControl = null;
+
+      const timeout = setTimeout(() => {
+        clearTimeout(timeout);
+        trackerControl.stop();
+        this.callback({
+          status: this.baseCallStates.failed,
+          error: this.baseErrorCodes.REQUEST_TIMEOUT,
+          bridgeError: 'Timeout on call transfer' });
+        reject('Timeout on call transfer');
+      }, CALL_TRANSFER_TIMEOUT);
+
+      // This is is the call transfer code ask @chadpilkey
+      this.currentSession.dtmf(1);
+
       Tracker.autorun((c) => {
         trackerControl = c;
         const selector = { meetingId: this.userData.meetingId, intId: this.userData.userId };
         const query = VoiceUsers.find(selector);
-        window.Kappa = query;
 
         query.observeChanges({
           changed: (id, fields) => {
@@ -104,16 +110,6 @@ export default class SIPBridge extends BaseAudioBridge {
           },
         });
       });
-
-      const timeout = setTimeout(() => {
-        clearTimeout(timeout);
-        trackerControl.stop();
-        this.callback({
-          status: this.baseCallStates.failed,
-          error: this.baseErrorCodes.REQUEST_TIMEOUT,
-          bridgeError: 'Could not transfer the call' });
-        reject('Call transfer timeout');
-      }, CALL_TRANSFER_TIMEOUT);
     });
   }
 
@@ -126,42 +122,50 @@ export default class SIPBridge extends BaseAudioBridge {
     });
   }
 
-  doCall({ isListenOnly, callExtension, inputStream }, callback) {
+  doCall(options) {
+    const {
+      isListenOnly,
+    } = options;
+
     const {
       userId,
-      username,
+      name,
       sessionToken,
-    } = this.userData;
-
-    const server = window.document.location.hostname;
+    } = this.user;
 
-    const callerIdPrefix = userId;
-    const callerIdSufix = isListenOnly ? `LINSTENONLY-${username}` : username;
     const callerIdName = [
-      callerIdPrefix,
+      userId,
       'bbbID',
-      callerIdSufix,
+      isListenOnly ? `LISTENONLY-${name}` : name,
     ].join('-');
 
+    this.user.callerIdName = callerIdName;
+    this.callOptions = options;
+
     return fetchStunTurnServers(sessionToken)
-                        .then(stunTurnServers =>
-                          this.createUserAgent(server, callerIdName, stunTurnServers))
-                        .then(userAgent =>
-                          inviteUserAgent(callExtension, server, userAgent, inputStream))
-                        .then(currentSession =>
-                          this.setupEventHandlers(currentSession, callback));
+                        .then(this.createUserAgent.bind(this))
+                        .then(this.inviteUserAgent.bind(this))
+                        .then(this.setupEventHandlers.bind(this));
   }
 
-  createUserAgent(server, username, { stun, turn }) {
+  createUserAgent({ stun, turn }) {
     return new Promise((resolve, reject) => {
-      const protocol = document.location.protocol;
-      this.userAgent = new window.SIP.UA({
-        uri: `sip:${encodeURIComponent(username)}@${server}`,
-        wsServers: `${(protocol === 'https:' ? 'wss://' : 'ws://')}${server}/ws`,
-        // log: {
-        //   builtinEnabled: false,
-        // },
-        displayName: username,
+      const {
+        hostname,
+        protocol,
+      } = this;
+
+      const {
+        callerIdName,
+      } = this.user;
+
+      let userAgent = new window.SIP.UA({
+        uri: `sip:${encodeURIComponent(callerIdName)}@${hostname}`,
+        wsServers: `${(protocol === 'https:' ? 'wss://' : 'ws://')}${hostname}/ws`,
+        log: {
+          builtinEnabled: false,
+        },
+        displayName: callerIdName,
         register: false,
         traceSip: true,
         autostart: false,
@@ -170,55 +174,149 @@ export default class SIPBridge extends BaseAudioBridge {
         turnServers: turn,
       });
 
-      this.userAgent.removeAllListeners('connected');
-      this.userAgent.removeAllListeners('disconnected');
+      userAgent.removeAllListeners('connected');
+      userAgent.removeAllListeners('disconnected');
+
+      const handleUserAgentConnection = () => {
+        resolve(userAgent);
+      };
+
+      const handleUserAgentDisconnection = () => {
+        userAgent.stop();
+        userAgent = null;
+        this.callback({
+          status: this.baseCallStates.failed,
+          error: this.baseErrorCodes.GENERIC_ERROR,
+          bridgeError: 'User Agent' });
+        reject('CONNECTION_ERROR');
+      };
 
-      this.userAgent.on('connected', () => this.handleUserAgentConnection(resolve));
-      this.userAgent.on('disconnected', () => this.handleUserAgentDisconnection(reject));
+      userAgent.on('connected', handleUserAgentConnection);
+      userAgent.on('disconnected', handleUserAgentDisconnection);
 
-      this.userAgent.start();
+      userAgent.start();
     });
   }
 
-  handleUserAgentConnection(resolve) {
-    this.isConnected = true;
-    resolve(this.userAgent);
-  }
+  inviteUserAgent(userAgent) {
+    const {
+      hostname,
+    } = this;
+
+    const {
+      inputStream,
+      callExtension,
+    } = this.callOptions;
+
+    const options = {
+      media: {
+        stream: inputStream,
+        constraints: {
+          audio: true,
+          video: false,
+        },
+        render: {
+          remote: document.querySelector(MEDIA_TAG),
+        },
+      },
+      RTCConstraints: {
+        mandatory: {
+          OfferToReceiveAudio: true,
+          OfferToReceiveVideo: false,
+        },
+      },
+    };
 
-  handleUserAgentDisconnection(reject) {
-    this.userAgent.stop();
-    this.userAgent = null;
-    this.callback({
-      status: this.baseCallStates.failed,
-      error: this.baseErrorCodes.GENERIC_ERROR,
-      bridgeError: 'User Agent' });
-    reject('CONNECTION_ERROR');
+    return userAgent.invite(`sip:${callExtension}@${hostname}`, options);
   }
 
-  setupEventHandlers(currentSession, callback) {
+  setupEventHandlers(currentSession) {
     return new Promise((resolve) => {
-      currentSession.on('terminated', (message, cause) => this.handleSessionTerminated(message, cause, callback));
+      const handleConnectionCompleted = () => {
+        this.callback({ status: this.baseCallStates.started });
+        resolve();
+      };
+
+      const handleSessionTerminated = (message, cause) => {
+        if (!message && !cause) {
+          return this.callback({
+            status: this.baseCallStates.ended,
+          });
+        }
+
+        const mappedCause = cause in this.errorCodes ?
+                            this.errorCodes[cause] :
+                            this.baseErrorCodes.GENERIC_ERROR;
+        return this.callback({
+          status: this.baseCallStates.failed,
+          error: mappedCause,
+          bridgeError: cause,
+        });
+      };
 
-      currentSession.mediaHandler.on('iceConnectionCompleted', () => this.handleConnectionCompleted(resolve));
-      currentSession.mediaHandler.on('iceConnectsionConnected', () => this.handleConnectionCompleted(resolve));
+      currentSession.on('terminated', handleSessionTerminated);
+      currentSession.mediaHandler.on('iceConnectionCompleted', handleConnectionCompleted);
+      currentSession.mediaHandler.on('iceConnectsionConnected', handleConnectionCompleted);
 
       this.currentSession = currentSession;
     });
   }
 
-  handleConnectionCompleted(resolve) {
-    this.callback({ status: this.baseCallStates.started });
-    resolve();
-  }
+  async changeInputDevice(value) {
+    const {
+      media,
+    } = this;
+
+    const getMediaStream = constraints =>
+      navigator.mediaDevices.getUserMedia(constraints);
+
+    if (!value) {
+      const mediaStream = await getMediaStream({ audio: true });
+      const deviceLabel = mediaStream.getAudioTracks()[0].label;
+      const mediaDevices = await navigator.mediaDevices.enumerateDevices();
+      const device = mediaDevices.find(d => d.label === deviceLabel);
+      return this.changeInputDevice(device.deviceId);
+    }
+
+    if (media.inputDevice.audioContext) {
+      media.inputDevice.audioContext.close().then(() => {
+        media.inputDevice.audioContext = null;
+        media.inputDevice.scriptProcessor = null;
+        media.inputDevice.source = null;
+        return this.changeInputDevice(value);
+      });
+    }
 
-  handleSessionTerminated(message, cause, callback) {
-    if (!message && !cause) {
-      return callback({ status: this.baseCallStates.ended });
+    media.inputDevice.id = value;
+    if ('AudioContext' in window) {
+      media.inputDevice.audioContext = new window.AudioContext();
+    } else {
+      media.inputDevice.audioContext = new window.webkitAudioContext();
     }
+    media.inputDevice.scriptProcessor = media.inputDevice.audioContext
+                                            .createScriptProcessor(2048, 1, 1);
+    media.inputDevice.source = null;
+
+    const constraints = {
+      audio: {
+        deviceId: value,
+      },
+    };
 
-    const mappedCause = cause in this.errorCodes ?
-                        this.errorCodes[cause] :
-                        this.baseErrorCodes.GENERIC_ERROR;
-    return callback({ status: this.baseCallStates.failed, error: mappedCause, bridgeError: cause });
+    const mediaStream = await getMediaStream(constraints);
+    media.inputDevice.stream = mediaStream;
+    media.inputDevice.source = media.inputDevice.audioContext.createMediaStreamSource(mediaStream);
+    media.inputDevice.source.connect(media.inputDevice.scriptProcessor);
+    media.inputDevice.scriptProcessor.connect(media.inputDevice.audioContext.destination);
+
+    return this.media.inputDevice;
+  }
+
+  changeOutputDevice(value) {
+    const audioContext = document.querySelector(MEDIA_TAG);
+
+    if (audioContext.setSinkId) {
+      audioContext.setSinkId(deviceId);
+    }
   }
 }
diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx
index c86f0506eabc4ca7dbc9b737c55fc7e1c12cfdc6..b82aaf2edbe132cbc1a5c3e35ece03457fe14b0c 100644
--- a/bigbluebutton-html5/imports/ui/components/app/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx
@@ -28,13 +28,14 @@ const propTypes = {
   actionsbar: PropTypes.node,
   media: PropTypes.node,
   location: PropTypes.object.isRequired,
-  children: PropTypes.node.isRequired,
+  children: PropTypes.node,
 };
 
 const defaultProps = {
   navbar: <NavBarContainer />,
   actionsbar: <ActionsBarContainer />,
   media: <MediaContainer />,
+  children: null,
 };
 
 const intlMessages = defineMessages({
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
index e22a170d95b019b0cae15535978253089a9ff559..27d11d6302d0e06e17f3c5af452fcdee54f83380 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
@@ -11,8 +11,8 @@ export default withModalMounter(createContainer(({ mountModal }) =>
      closeModal: () => {
        if (!Service.isConnecting()) mountModal(null);
      },
-     joinMicrophone: () => {
-       return new Promise((resolve, reject) => {
+     joinMicrophone: () =>
+       new Promise((resolve, reject) => {
          Service.transferCall().then(() => {
            mountModal(null);
            resolve();
@@ -20,8 +20,7 @@ export default withModalMounter(createContainer(({ mountModal }) =>
            Service.exitAudio();
            reject();
          });
-       });
-     },
+       }),
      joinListenOnly: () => Service.joinListenOnly().then(() => mountModal(null)),
      leaveEchoTest: () => {
        if (!Service.isEchoTest()) {
diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
index 99e2e551ca0f4ca20303470fcfd4e416a0364c96..2086e464af7df9c188c7f72c2d74252879c37d57 100644
--- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
+++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
@@ -3,8 +3,9 @@ import { makeCall } from '/imports/ui/services/api';
 import VertoBridge from '/imports/api/audio/client/bridge/verto';
 import SIPBridge from '/imports/api/audio/client/bridge/sip';
 
-const USE_SIP = Meteor.settings.public.media.useSIPAudio;
-const OUTPUT_TAG = Meteor.settings.public.media.mediaTag;
+const MEDIA = Meteor.settings.public.media;
+const USE_SIP = MEDIA.useSIPAudio;
+const ECHO_TEST_NUMBER = MEDIA.echoTestNumber;
 
 const CALL_STATES = {
   STARTED: 'started',
@@ -18,17 +19,6 @@ class AudioManager {
       tracker: new Tracker.Dependency(),
     };
 
-    navigator.mediaDevices
-      .getUserMedia({ audio: true })
-      .then((stream) => {
-        const deviceLabel = stream.getAudioTracks()[0].label;
-        navigator.mediaDevices.enumerateDevices().then((devices) => {
-          const device = devices.find(d => d.label === deviceLabel);
-          this.changeInputDevice(device.deviceId);
-        });
-      }).catch((err) => { this.error = err; });
-
-
     this.defineProperties({
       isMuted: false,
       isConnected: false,
@@ -40,6 +30,13 @@ class AudioManager {
     });
   }
 
+  init(userData) {
+    this.bridge = USE_SIP ? new SIPBridge(userData) : new VertoBridge(userData);
+    this.userData = userData;
+
+    this.changeInputDevice();
+  }
+
   defineProperties(obj) {
     Object.keys(obj).forEach((key) => {
       const privateKey = `_${key}`;
@@ -61,11 +58,6 @@ class AudioManager {
     });
   }
 
-  init(userData) {
-    this.bridge = USE_SIP ? new SIPBridge(userData) : new VertoBridge(userData);
-    this.userData = userData;
-  }
-
   joinAudio(options = {}, callbacks = {}) {
     const {
       isListenOnly,
@@ -73,6 +65,7 @@ class AudioManager {
     } = options;
 
     this.isConnecting = true;
+    this.isMuted = false;
     this.error = null;
     this.isListenOnly = isListenOnly || false;
     this.isEchoTest = isEchoTest || false;
@@ -80,22 +73,20 @@ class AudioManager {
 
     const callOptions = {
       isListenOnly: this.isListenOnly,
-      extension: isEchoTest ? '9196' : null,
+      extension: isEchoTest ? ECHO_TEST_NUMBER : null,
       inputStream: this.isListenOnly ? this.createListenOnlyStream() : this.inputStream,
     };
 
-    // if (this.isListenOnly) makeCall('listenOnlyToggle', true);
-
     return this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this));
   }
 
   exitAudio() {
-    console.log('LOL');
     return this.bridge.exitAudio();
   }
 
   transferCall() {
-    return this.bridge.transferCall(this.onTransferStart.bind(this), this.onAudioJoin.bind(this));
+    this.onTransferStart();
+    return this.bridge.transferCall(this.onAudioJoin.bind(this));
   }
 
   toggleMuteMicrophone() {
@@ -109,9 +100,6 @@ class AudioManager {
       this.isConnected = true;
     }
 
-    if (this.isListenOnly) makeCall('listenOnlyToggle', true);
-    console.log('joined', this.isListenOnly);
-
     this.isConnecting = false;
   }
 
@@ -124,8 +112,6 @@ class AudioManager {
     this.isConnected = false;
     this.isConnecting = false;
 
-    if (this.isListenOnly) makeCall('listenOnlyToggle', false);
-
     if (this.isEchoTest) {
       this.isEchoTest = false;
     }
@@ -135,13 +121,6 @@ class AudioManager {
     this.isMuted = !this.isMuted;
   }
 
-  //---------------------------
-  // update(key, value) {
-  //   const query = { _id: this.stateId };
-  //   const modifier = { $set: { [key]: value }};
-  //   collection.update(query, modifier);
-  // }
-
   callStateCallback(response) {
     return new Promise((resolve) => {
       const {
@@ -161,77 +140,36 @@ class AudioManager {
       } else if (status === ENDED) {
         this.onAudioExit();
       } else if (status === FAILED) {
-        console.log('error happened');
         this.error = error;
         this.onAudioExit();
       }
     });
   }
 
-  set userData(value) {
-    this._userData = value;
-  }
-
-  get userData() {
-    return this._userData;
-  }
-
   createListenOnlyStream() {
     if (this.listenOnlyAudioContext) {
       this.listenOnlyAudioContext.close();
     }
 
-    if ('webkitAudioContext' in window) {
-      this.listenOnlyAudioContext = new window.webkitAudioContext();
-    } else {
-      this.listenOnlyAudioContext = new window.AudioContext();
-    }
+    this.listenOnlyAudioContext = window.AudioContext ?
+                                  new window.AudioContext() :
+                                  new window.webkitAudioContext();
 
     return this.listenOnlyAudioContext.createMediaStreamDestination().stream;
   }
 
-  changeInputDevice(value) {
-    if (this._inputDevice.audioContext) {
-      this._inputDevice.audioContext.close().then(() => {
-        this._inputDevice.audioContext = null;
-        this._inputDevice.scriptProcessor = null;
-        this._inputDevice.source = null;
-
-        this.changeInputDevice(value);
-      });
-      return;
-    }
-
-    this._inputDevice.id = value;
-    if ('webkitAudioContext' in window) {
-      this._inputDevice.audioContext = new window.webkitAudioContext();
-    } else {
-      this._inputDevice.audioContext = new AudioContext();
-    }
-    this._inputDevice.scriptProcessor = this._inputDevice.audioContext
-                                            .createScriptProcessor(2048, 1, 1);
-    this._inputDevice.source = null;
-
-    const constraints = {
-      audio: {
-        deviceId: value,
-      },
-    };
+  async changeInputDevice(deviceId) {
+    const device = await this.bridge.changeInputDevice(deviceId);
+    this.inputDevice = device;
+  }
 
-    navigator.mediaDevices
-      .getUserMedia(constraints)
-      .then((stream) => {
-        this._inputDevice.stream = stream;
-        this._inputDevice.source = this._inputDevice.audioContext.createMediaStreamSource(stream);
-        this._inputDevice.source.connect(this._inputDevice.scriptProcessor);
-        this._inputDevice.scriptProcessor.connect(this._inputDevice.audioContext.destination);
-        this._inputDevice.tracker.changed();
-      });
+  async changeOutputDevice(deviceId) {
+    this.outputDeviceId = await this.bridge.changeOutputDevice(deviceId);
   }
 
-  changeOutputDevice(deviceId) {
-    this.outputDeviceId = deviceId;
-    document.querySelector(OUTPUT_TAG).setSinkId(deviceId);
+  set inputDevice(value) {
+    Object.assign(this._inputDevice, value);
+    this._inputDevice.tracker.changed();
   }
 
   get inputStream() {
@@ -242,6 +180,14 @@ class AudioManager {
     this._inputDevice.tracker.depend();
     return this._inputDevice.id;
   }
+
+  set userData(value) {
+    this._userData = value;
+  }
+
+  get userData() {
+    return this._userData;
+  }
 }
 
 const audioManager = new AudioManager();
diff --git a/bigbluebutton-html5/private/config/public/media.yaml b/bigbluebutton-html5/private/config/public/media.yaml
index 124e4aeed3c26a15b717faaa56f215c00d472c25..500be9ecf044482819e4a368f7efe9d569f6873f 100644
--- a/bigbluebutton-html5/private/config/public/media.yaml
+++ b/bigbluebutton-html5/private/config/public/media.yaml
@@ -11,3 +11,4 @@ media:
   stunTurnServersFetchAddress: '/bigbluebutton/api/stuns'
   mediaTag: '#remote-media'
   callTransferTimeout: 5000
+  echoTestNumber: '9196'