diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
index 5263804f479eb10fceb65880e67a61c8fd273d44..b8635661bc8b4ada85a9155c9edd4db2d455469c 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
@@ -113,7 +113,7 @@ class AudioModal extends Component {
       help: {
         title: intl.formatMessage(intlMessages.helpTitle),
         component: () => this.renderHelp(),
-      }
+      },
     };
   }
 
@@ -152,7 +152,7 @@ class AudioModal extends Component {
       this.setState({
         content: 'echoTest',
       });
-    }).catch(err => {
+    }).catch((err) => {
       if (err.type === 'MEDIA_ERROR') {
         this.setState({
           content: 'help',
@@ -166,7 +166,7 @@ class AudioModal extends Component {
       joinListenOnly,
     } = this.props;
 
-    return joinListenOnly().catch(err => {
+    return joinListenOnly().catch((err) => {
       if (err.type === 'MEDIA_ERROR') {
         this.setState({
           content: 'help',
@@ -317,8 +317,8 @@ class AudioModal extends Component {
               data-test="modalBaseCloseButton"
               className={styles.closeBtn}
               label={intl.formatMessage(intlMessages.closeLabel)}
-              icon={'close'}
-              size={'md'}
+              icon="close"
+              size="md"
               hideLabel
               onClick={this.closeModal}
             />
diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js
index 81fc11b73f6d7bfa975765acfe7f99bf2853b8eb..8628984225f68a18a20c1e48f026fd323c66cbdd 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/service.js
+++ b/bigbluebutton-html5/imports/ui/components/audio/service.js
@@ -36,9 +36,9 @@ export default {
   init,
   exitAudio: () => AudioManager.exitAudio(),
   transferCall: () => AudioManager.transferCall(),
-  joinListenOnly: () => AudioManager.joinAudio({ isListenOnly: true }),
-  joinMicrophone: () => AudioManager.joinAudio(),
-  joinEchoTest: () => AudioManager.joinAudio({ isEchoTest: true }),
+  joinListenOnly: () => AudioManager.joinListenOnly(),
+  joinMicrophone: () => AudioManager.joinMicrophone(),
+  joinEchoTest: () => AudioManager.joinEchoTest(),
   toggleMuteMicrophone: () => AudioManager.toggleMuteMicrophone(),
   changeInputDevice: inputDeviceId => AudioManager.changeInputDevice(inputDeviceId),
   changeOutputDevice: outputDeviceId => AudioManager.changeOutputDevice(outputDeviceId),
diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
index 06516e9580ef5c4d444646e4fee05b709800eae9..64374432013f4b3fd46a3264c7dc506d0ac85b4d 100644
--- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
+++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
@@ -64,49 +64,100 @@ class AudioManager {
     });
   }
 
-  joinAudio(options = {}) {
-    const {
-      isListenOnly,
-      isEchoTest,
-    } = options;
-
-    const permissionsTimeout = setTimeout(() => {
-      this.isWaitingPermissions = true;
+  askDevicesPermissions() {
+    // Only change the isWaitingPermissions for the case where the user didnt allowed it yet
+    const permTimeout = setTimeout(() => {
+      if (!this.devicesInitialized) { this.isWaitingPermissions = true; }
     }, 100);
 
-    const doCall = () => {
-      clearTimeout(permissionsTimeout);
-      this.isWaitingPermissions = false;
-      this.devicesInitialized = true;
-      this.isConnecting = true;
-      this.isMuted = false;
-      this.error = null;
-      this.isListenOnly = isListenOnly || false;
-      this.isEchoTest = isEchoTest || false;
-
-      const callOptions = {
-        isListenOnly: this.isListenOnly,
-        extension: isEchoTest ? ECHO_TEST_NUMBER : null,
-        inputStream: this.isListenOnly ? this.createListenOnlyStream() : this.inputStream,
-      };
-      return this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this));
-    };
-
-    if (this.devicesInitialized) return doCall();
+    this.isWaitingPermissions = false;
+    this.devicesInitialized = false;
 
     return Promise.all([
       this.setDefaultInputDevice(),
       this.setDefaultOutputDevice(),
-    ]).then(doCall)
+    ]).then(() => {
+      this.devicesInitialized = true;
+      this.isWaitingPermissions = false;
+    }).catch((err) => {
+      clearTimeout(permTimeout);
+      this.isConnecting = false;
+      this.isWaitingPermissions = false;
+      throw err;
+    });
+  }
+
+  joinMicrophone() {
+    this.isListenOnly = false;
+    this.isEchoTest = false;
+
+    const callOptions = {
+      isListenOnly: false,
+      extension: null,
+      inputStream: this.inputStream,
+    };
+
+    return this.askDevicesPermissions()
+      .then(this.onAudioJoining)
+      .then(() => this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this)));
+  }
+
+  joinEchoTest() {
+    this.isListenOnly = false;
+    this.isEchoTest = true;
+
+    const callOptions = {
+      isListenOnly: false,
+      extension: ECHO_TEST_NUMBER,
+      inputStream: this.inputStream,
+    };
+
+    return this.askDevicesPermissions()
+      .then(this.onAudioJoining)
+      .then(() => this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this)));
+  }
+
+  joinListenOnly() {
+    console.log('entrei');
+    this.isListenOnly = true;
+    this.isEchoTest = false;
+
+    const callOptions = {
+      isListenOnly: true,
+      extension: null,
+      inputStream: this.createListenOnlyStream(),
+    };
+
+    // We need this until we upgrade to SIP 9x. See #4690
+    const iceGatheringErr = 'ICE_TIMEOUT';
+    const iceGatheringTimeout = new Promise((resolve, reject) => {
+      setTimeout(reject, 12000, iceGatheringErr);
+    });
+
+    return this.onAudioJoining()
+      .then(() => Promise.race([
+        this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this)),
+        iceGatheringTimeout,
+      ]))
       .catch((err) => {
-        clearTimeout(permissionsTimeout);
-        this.isWaitingPermissions = false;
-        this.error = err;
-        this.notify(err.message);
-        return Promise.reject(err);
+        // If theres a iceGathering timeout we retry to join after asking device permissions
+        if (err === iceGatheringErr) {
+          return this.askDevicesPermissions()
+            .then(() => this.joinListenOnly());
+        }
+
+        throw err;
       });
   }
 
+  onAudioJoining() {
+    this.isConnecting = true;
+    this.isMuted = false;
+    this.error = false;
+
+    return Promise.resolve();
+  }
+
   exitAudio() {
     if (!this.isConnected) return Promise.resolve();
 
@@ -128,7 +179,7 @@ class AudioManager {
     this.isConnected = true;
 
     // listen to the VoiceUsers changes and update the flag
-    if(!this.muteHandle) {
+    if (!this.muteHandle) {
       const query = VoiceUsers.find({ intId: Auth.userID });
       this.muteHandle = query.observeChanges({
         changed: (id, fields) => {
@@ -153,11 +204,9 @@ class AudioManager {
     this.isConnecting = false;
     this.isHangingUp = false;
 
-
     if (!this.error && !this.isEchoTest) {
       this.notify(this.messages.info.LEFT_AUDIO);
     }
-    this.isEchoTest = false;
   }
 
   callStateCallback(response) {
@@ -181,7 +230,7 @@ class AudioManager {
         this.onAudioExit();
       } else if (status === FAILED) {
         this.error = error;
-        this.notify(this.messages.error[error]);
+        this.notify(this.messages.error[error], true);
         console.error('Audio Error:', error, bridgeError);
         this.onAudioExit();
       }
@@ -256,10 +305,10 @@ class AudioManager {
     return this._userData;
   }
 
-  notify(message) {
+  notify(message, error = false) {
     notify(
       message,
-      this.error ? 'error' : 'info',
+      error ? 'error' : 'info',
       this.isListenOnly ? 'audio_on' : 'unmute',
     );
   }