diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
index 577578047f39a3c4b0fd9280383937a53fe9a322..a4caa2a5cf6ca861521c740bdec8cea79ee134a1 100755
--- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
+++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
@@ -1,18 +1,19 @@
-import _ from 'lodash';
 import VoiceUsers from '/imports/api/voice-users';
 import { Tracker } from 'meteor/tracker';
+import browser from 'browser-detect';
 import BaseAudioBridge from './base';
 import logger from '/imports/startup/client/logger';
 import { fetchStunTurnServers } from '/imports/utils/fetchStunTurnServers';
-import browser from 'browser-detect';
 
 const MEDIA = Meteor.settings.public.media;
 const MEDIA_TAG = MEDIA.mediaTag;
 const CALL_TRANSFER_TIMEOUT = MEDIA.callTransferTimeout;
 const CALL_HANGUP_TIMEOUT = MEDIA.callHangupTimeout;
 const CALL_HANGUP_MAX_RETRIES = MEDIA.callHangupMaximumRetries;
-const CONNECTION_TERMINATED_EVENTS = ['iceConnectionFailed', 'iceConnectionClosed'];
+const ICE_NEGOTIATION_FAILED = ['iceConnectionFailed'];
+const CALL_CONNECT_TIMEOUT = 10000;
 const CALL_CONNECT_NOTIFICATION_TIMEOUT = 500;
+const ICE_NEGOTIATION_TIMEOUT = 10000;
 
 export default class SIPBridge extends BaseAudioBridge {
   constructor(userData) {
@@ -36,30 +37,6 @@ export default class SIPBridge extends BaseAudioBridge {
 
     this.protocol = window.document.location.protocol;
     this.hostname = window.document.location.hostname;
-
-    const {
-      causes,
-    } = window.SIP.C;
-
-    this.errorCodes = {
-      [causes.REQUEST_TIMEOUT]: this.baseErrorCodes.REQUEST_TIMEOUT,
-      [causes.INVALID_TARGET]: this.baseErrorCodes.INVALID_TARGET,
-      [causes.CONNECTION_ERROR]: this.baseErrorCodes.CONNECTION_ERROR,
-      [causes.WEBRTC_NOT_SUPPORTED]: this.baseErrorCodes.WEBRTC_NOT_SUPPORTED,
-    };
-    this.webRtcError = {
-      1001: '1001',
-      1002: '1002',
-      1003: '1003',
-      1004: '1004',
-      1005: '1005',
-      1006: '1006',
-      1007: '1007',
-      1008: '1008',
-      1009: '1009',
-      1010: '1010',
-      1011: '1011',
-    };
   }
 
   joinAudio({ isListenOnly, extension, inputStream }, managerCallback) {
@@ -74,11 +51,6 @@ export default class SIPBridge extends BaseAudioBridge {
 
       return this.doCall({ callExtension, isListenOnly, inputStream })
         .catch((reason) => {
-          callback({
-            status: this.baseCallStates.failed,
-            error: this.baseErrorCodes.GENERIC_ERROR,
-            bridgeError: reason,
-          });
           reject(reason);
         });
     });
@@ -117,7 +89,7 @@ export default class SIPBridge extends BaseAudioBridge {
       const timeout = setTimeout(() => {
         clearTimeout(timeout);
         trackerControl.stop();
-        logger.error({logCode: "sip_js_transfer_timed_out"}, "Timeout on transfering from echo test to conference")
+        logger.error({ logCode: 'sip_js_transfer_timed_out' }, 'Timeout on transfering from echo test to conference');
         this.callback({
           status: this.baseCallStates.failed,
           error: 1008,
@@ -154,9 +126,15 @@ export default class SIPBridge extends BaseAudioBridge {
       let hangup = false;
       const { mediaHandler } = this.currentSession;
 
+      this.userRequestedHangup = true;
       // Removing termination events to avoid triggering an error
-      CONNECTION_TERMINATED_EVENTS.forEach(e => mediaHandler.off(e));
+      ICE_NEGOTIATION_FAILED.forEach(e => mediaHandler.off(e));
       const tryHangup = () => {
+        if (!!this.currentSession.endTime) {
+          hangup = true;
+          return resolve();
+        }
+
         this.currentSession.bye();
         hangupRetries += 1;
 
@@ -164,7 +142,7 @@ export default class SIPBridge extends BaseAudioBridge {
           if (hangupRetries > CALL_HANGUP_MAX_RETRIES) {
             this.callback({
               status: this.baseCallStates.failed,
-              error: this.baseErrorCodes.REQUEST_TIMEOUT,
+              error: 1006,
               bridgeError: 'Timeout on call hangup',
             });
             return reject(this.baseErrorCodes.REQUEST_TIMEOUT);
@@ -195,6 +173,10 @@ export default class SIPBridge extends BaseAudioBridge {
         callerIdName,
       } = this.user;
 
+      let userAgentConnected = false;
+
+      logger.debug('Creating the user agent');
+
       let userAgent = new window.SIP.UA({
         uri: `sip:${encodeURIComponent(callerIdName)}@${hostname}`,
         wsServers: `${(protocol === 'https:' ? 'wss://' : 'ws://')}${hostname}/ws`,
@@ -211,19 +193,29 @@ export default class SIPBridge extends BaseAudioBridge {
       userAgent.removeAllListeners('disconnected');
 
       const handleUserAgentConnection = () => {
+        userAgentConnected = true;
         resolve(userAgent);
       };
 
-      const handleUserAgentDisconnection = (event) => {
+      const handleUserAgentDisconnection = () => {
         userAgent.stop();
         userAgent = null;
-        const { lastTransportError } = event.transport;
-        const errorCode = lastTransportError.code;
-        const error = this.webRtcError[errorCode] || this.baseErrorCodes.CONNECTION_ERROR;
+
+        let error;
+        let bridgeError;
+        
+        if (userAgentConnected) {
+          error = 1001;
+          bridgeError = 'Websocket disconnected';
+        } else {
+          error = 1002;
+          bridgeError = 'Websocket failed to connect';
+        }
+
         this.callback({
           status: this.baseCallStates.failed,
           error,
-          bridgeError: 'User Agent Disconnected',
+          bridgeError,
         });
         reject(this.baseErrorCodes.CONNECTION_ERROR);
       };
@@ -269,46 +261,79 @@ export default class SIPBridge extends BaseAudioBridge {
     return new Promise((resolve) => {
       const { mediaHandler } = currentSession;
 
+      this.connectionCompleted = false;
+
       let connectionCompletedEvents = ['iceConnectionCompleted', 'iceConnectionConnected'];
       // Edge sends a connected first and then a completed, but the call isn't ready until
       // the completed comes in. Due to the way that we have the listeners set up, the only
       // way to ignore one status is to not listen for it.
       if (browser().name === 'edge') {
-        connectionCompletedEvents  = ['iceConnectionCompleted'];
+        connectionCompletedEvents = ['iceConnectionCompleted'];
       }
 
+      // Sometimes FreeSWITCH just won't respond with anything and hangs. This timeout is to 
+      // avoid that issue
+      const callTimeout = setTimeout(() => {
+        this.callback({
+          status: this.baseCallStates.failed,
+          error: 1006,
+          bridgeError: 'Call timed out on start after ' + CALL_CONNECT_TIMEOUT/1000 + 's',
+        });
+      }, CALL_CONNECT_TIMEOUT);
+
+      let iceNegotiationTimeout;
+
       const handleSessionAccepted = () => {
-        logger.info({logCode: "sip_js_session_accepted"}, "Audio call session accepted");
+        logger.info({ logCode: 'sip_js_session_accepted' }, 'Audio call session accepted');
+        clearTimeout(callTimeout);
+
+        // If ICE isn't connected yet then start timeout waiting for ICE to finish
+        if (!this.connectionCompleted) {
+          iceNegotiationTimeout = setTimeout(() => {
+            this.callback({
+              status: this.baseCallStates.failed,
+              error: 1010,
+              bridgeError: 'ICE negotiation timeout after ' + ICE_NEGOTIATION_TIMEOUT/1000 + 's',
+            });
+          }, ICE_NEGOTIATION_TIMEOUT);
+        }
       };
       currentSession.on('accepted', handleSessionAccepted);
 
       const handleConnectionCompleted = (peer) => {
-        logger.info({logCode: "sip_js_ice_connection_success"}, "ICE connection success. Current state - " + peer.iceConnectionState);
+        logger.info({ logCode: 'sip_js_ice_connection_success' }, `ICE connection success. Current state - ${peer.iceConnectionState}`);
+        clearTimeout(callTimeout);
+        clearTimeout(iceNegotiationTimeout);
         connectionCompletedEvents.forEach(e => mediaHandler.off(e, handleConnectionCompleted));
+        this.connectionCompleted = true;
         // We have to delay notifying that the call is connected because it is sometimes not
         // actually ready and if the user says "Yes they can hear themselves" too quickly the
         // B-leg transfer will fail
         const that = this;
         setTimeout(() => {
           that.callback({ status: that.baseCallStates.started });
-          that.connectionCompleted = true;
           resolve();
         }, CALL_CONNECT_NOTIFICATION_TIMEOUT);
       };
       connectionCompletedEvents.forEach(e => mediaHandler.on(e, handleConnectionCompleted));
 
       const handleSessionTerminated = (message, cause) => {
-        if (!message && !cause) {
+        clearTimeout(callTimeout);
+        clearTimeout(iceNegotiationTimeout);
+        if (!message && !cause && !!this.userRequestedHangup) {
           return this.callback({
             status: this.baseCallStates.ended,
           });
         }
 
-        logger.error({logCode: "sip_js_call_terminated"}, "Audio call terminated. message=" + message + ", cause=" + cause);
+        logger.error({ logCode: 'sip_js_call_terminated' }, `Audio call terminated. cause=${cause}`);
 
-        const mappedCause = cause in this.errorCodes
-          ? this.errorCodes[cause]
-          : this.baseErrorCodes.GENERIC_ERROR;
+        let mappedCause;
+        if (!this.connectionCompleted) {
+          mappedCause = '1004';
+        } else {
+          mappedCause = '1005';
+        }
 
         return this.callback({
           status: this.baseCallStates.failed,
@@ -318,16 +343,30 @@ export default class SIPBridge extends BaseAudioBridge {
       };
       currentSession.on('terminated', handleSessionTerminated);
 
-      const handleConnectionTerminated = (peer) => {
-        logger.error({logCode: "sip_js_ice_connection_error"}, "ICE connection error. Current state - " + peer.iceConnectionState);
-        CONNECTION_TERMINATED_EVENTS.forEach(e => mediaHandler.off(e, handleConnectionTerminated));
+      const handleIceNegotiationFailed = (peer) => {
+        clearTimeout(callTimeout);
+        clearTimeout(iceNegotiationTimeout);
+        ICE_NEGOTIATION_FAILED.forEach(e => mediaHandler.off(e, handleIceNegotiationFailed));
+        this.callback({
+          status: this.baseCallStates.failed,
+          error: 1007,
+          bridgeError: `ICE negotiation failed. Current state - ${peer.iceConnectionState}`,
+        });
+      };
+      ICE_NEGOTIATION_FAILED.forEach(e => mediaHandler.on(e, handleIceNegotiationFailed));
+
+      const handleIceConnectionTerminated = (peer) => {
+        ['iceConnectionClosed'].forEach(e => mediaHandler.off(e, handleIceConnectionTerminated));
+        logger.error({ logCode: 'sipjs_ice_closed' }, 'ICE connection closed');
+        /*
         this.callback({
           status: this.baseCallStates.failed,
-          error: this.baseErrorCodes.ICE_NEGOTIATION_FAILED,
-          bridgeError: peer,
+          error: 1012,
+          bridgeError: "ICE connection closed. Current state - " + peer.iceConnectionState,
         });
+        */
       };
-      CONNECTION_TERMINATED_EVENTS.forEach(e => mediaHandler.on(e, handleConnectionTerminated));
+      ['iceConnectionClosed'].forEach(e => mediaHandler.on(e, handleIceConnectionTerminated));
 
       this.currentSession = currentSession;
     });
diff --git a/bigbluebutton-html5/imports/ui/components/audio/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/container.jsx
index a7a75ebd2e271dd5e58d74e983637ec839ea9d8d..aca05aa168605054bc5e74900900d93a16a0a210 100755
--- a/bigbluebutton-html5/imports/ui/components/audio/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/container.jsx
@@ -47,10 +47,6 @@ const intlMessages = defineMessages({
     id: 'app.audioNotification.audioFailedError1003',
     description: 'browser not supported error messsage',
   },
-  iceNegotiationError: {
-    id: 'app.audioNotification.audioFailedError1007',
-    description: 'ice negociation error messsage',
-  },
   reconectingAsListener: {
     id: 'app.audioNotificaion.reconnectingAsListenOnly',
     description: 'ice negociation error messsage',
@@ -106,33 +102,32 @@ export default withModalMounter(injectIntl(withTracker(({ mountModal, intl }) =>
     },
   });
 
-  const webRtcError = _.range(1001, 1012)
+  const webRtcError = _.range(1001, 1011)
     .reduce((acc, value) => ({
       ...acc,
-      [value]: intl.formatMessage({ id: `app.audioNotification.audioFailedError${value}` }),
+      [value]: { id: `app.audioNotification.audioFailedError${value}` },
     }), {});
 
   const messages = {
     info: {
-      JOINED_AUDIO: intl.formatMessage(intlMessages.joinedAudio),
-      JOINED_ECHO: intl.formatMessage(intlMessages.joinedEcho),
-      LEFT_AUDIO: intl.formatMessage(intlMessages.leftAudio),
+      JOINED_AUDIO: intlMessages.joinedAudio,
+      JOINED_ECHO: intlMessages.joinedEcho,
+      LEFT_AUDIO: intlMessages.leftAudio,
     },
     error: {
-      GENERIC_ERROR: intl.formatMessage(intlMessages.genericError),
-      CONNECTION_ERROR: intl.formatMessage(intlMessages.connectionError),
-      REQUEST_TIMEOUT: intl.formatMessage(intlMessages.requestTimeout),
-      INVALID_TARGET: intl.formatMessage(intlMessages.invalidTarget),
-      MEDIA_ERROR: intl.formatMessage(intlMessages.mediaError),
-      WEBRTC_NOT_SUPPORTED: intl.formatMessage(intlMessages.BrowserNotSupported),
-      ICE_NEGOTIATION_FAILED: intl.formatMessage(intlMessages.iceNegotiationError),
+      GENERIC_ERROR: intlMessages.genericError,
+      CONNECTION_ERROR: intlMessages.connectionError,
+      REQUEST_TIMEOUT: intlMessages.requestTimeout,
+      INVALID_TARGET: intlMessages.invalidTarget,
+      MEDIA_ERROR: intlMessages.mediaError,
+      WEBRTC_NOT_SUPPORTED: intlMessages.BrowserNotSupported,
       ...webRtcError,
     },
   };
 
   return {
     init: () => {
-      Service.init(messages);
+      Service.init(messages, intl);
       Service.changeOutputDevice(document.querySelector('#remote-media').sinkId);
       if (!autoJoin || didMountAutoJoin) return;
 
diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js
index 0be7337cd7fb3d9e9514ed781cf0a586fd5d0ac6..027b83f8bd7d25bda2a8b37ca0f9e4d4cc58959f 100755
--- a/bigbluebutton-html5/imports/ui/components/audio/service.js
+++ b/bigbluebutton-html5/imports/ui/components/audio/service.js
@@ -4,8 +4,8 @@ import AudioManager from '/imports/ui/services/audio-manager';
 import Meetings from '/imports/api/meetings';
 import mapUser from '/imports/ui/services/user/mapUser';
 
-const init = (messages) => {
-  AudioManager.setAudioMessages(messages);
+const init = (messages, intl) => {
+  AudioManager.setAudioMessages(messages, intl);
   if (AudioManager.initialized) return;
   const meetingId = Auth.meetingID;
   const userId = Auth.userID;
diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
index 7348982e0c23c357a4c67b7ccc20fccf3683db86..2c39b2c0d896c99acb2aecb1c1aba1668d5d4844 100755
--- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
+++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
@@ -56,8 +56,9 @@ class AudioManager {
     this.initialized = true;
   }
 
-  setAudioMessages(messages) {
+  setAudioMessages(messages, intl) {
     this.messages = messages;
+    this.intl = intl;
   }
 
   defineProperties(obj) {
@@ -153,14 +154,14 @@ class AudioManager {
       try {
         await tryGenerateIceCandidates();
       } catch (e) {
-        this.notify(this.messages.error.ICE_NEGOTIATION_FAILED);
+        this.notify(this.intl.formatMessage(this.messages.error.ICE_NEGOTIATION_FAILED));
       }
     }
 
     // Call polyfills for webrtc client if navigator is "iOS Webview"
     const userAgent = window.navigator.userAgent.toLocaleLowerCase();
     if ((userAgent.indexOf('iphone') > -1 || userAgent.indexOf('ipad') > -1)
-       && userAgent.indexOf('safari') == -1) {
+       && userAgent.indexOf('safari') === -1) {
       iosWebviewAudioPolyfills();
     }
 
@@ -265,7 +266,8 @@ class AudioManager {
 
     if (!this.isEchoTest) {
       window.parent.postMessage({ response: 'joinedAudio' }, '*');
-      this.notify(this.messages.info.JOINED_AUDIO);
+      this.notify(this.intl.formatMessage(this.messages.info.JOINED_AUDIO));
+      logger.info({ logCode: 'audio_joined' }, 'Audio Joined');
     }
   }
 
@@ -287,7 +289,7 @@ class AudioManager {
     }
 
     if (!this.error && !this.isEchoTest) {
-      this.notify(this.messages.info.LEFT_AUDIO);
+      this.notify(this.intl.formatMessage(this.messages.info.LEFT_AUDIO));
     }
     window.parent.postMessage({ response: 'notInAudio' }, '*');
   }
@@ -310,11 +312,14 @@ class AudioManager {
         this.onAudioJoin();
         resolve(STARTED);
       } else if (status === ENDED) {
+        logger.debug({ logCode: 'audio_ended' }, 'Audio ended without issue');
         this.onAudioExit();
       } else if (status === FAILED) {
-        this.error = error;
-        this.notify(this.messages.error[error] || this.messages.error.GENERIC_ERROR, true);
-        logger.error({ logCode: 'audiomanager_audio_error' }, 'Audio Error:', error, bridgeError);
+        const errorKey = this.messages.error[error] || this.messages.error.GENERIC_ERROR;
+        const errorMsg = this.intl.formatMessage(errorKey, { 0: bridgeError });
+        this.error = !!error;
+        this.notify(errorMsg, true);
+        logger.error({ logCode: 'audio_failure', error, cause: bridgeError }, 'Audio Error:', error, bridgeError);
         this.exitAudio();
         this.onAudioExit();
       }
diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json
index 76fadde7ec1d92a0fcdaf09647ab918f59684c0f..76fee2335d88f0ae214f07c4521a56759179814a 100755
--- a/bigbluebutton-html5/private/locales/en.json
+++ b/bigbluebutton-html5/private/locales/en.json
@@ -287,7 +287,7 @@
     "app.audioNotification.audioFailedError1001": "Error 1001: WebSocket disconnected",
     "app.audioNotification.audioFailedError1002": "Error 1002: Could not make a WebSocket connection",
     "app.audioNotification.audioFailedError1003": "Error 1003: Browser version not supported",
-    "app.audioNotification.audioFailedError1004": "Error 1004: Failure on call",
+    "app.audioNotification.audioFailedError1004": "Error 1004: Failure on call (reason={0})",
     "app.audioNotification.audioFailedError1005": "Error 1005: Call ended unexpectedly",
     "app.audioNotification.audioFailedError1006": "Error 1006: Call timed out",
     "app.audioNotification.audioFailedError1007": "Error 1007: ICE negotiation failed",
@@ -295,6 +295,7 @@
     "app.audioNotification.audioFailedError1009": "Error 1009: Could not fetch STUN/TURN server information",
     "app.audioNotification.audioFailedError1010": "Error 1010: ICE negotiation timeout",
     "app.audioNotification.audioFailedError1011": "Error 1011: ICE gathering timeout",
+    "app.audioNotification.audioFailedError1012": "Error 1012: ICE connection closed",
     "app.audioNotification.audioFailedMessage": "Your audio connection failed to connect",
     "app.audioNotification.mediaFailedMessage": "getUserMicMedia failed as only secure origins are allowed",
     "app.audioNotification.closeLabel": "Close",