diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
index 6b936afb086a01d6aca1d90acfc499371033cb8d..9394f84e2fd0db5ad73f72c2adc29ffcafdd172e 100755
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
@@ -4,6 +4,7 @@ import { withModalMounter } from '/imports/ui/components/modal/service';
 import AudioManager from '/imports/ui/services/audio-manager';
 import { makeCall } from '/imports/ui/services/api';
 import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
+import logger from '/imports/startup/client/logger';
 import AudioControls from './component';
 import AudioModalContainer from '../audio-modal/container';
 import Service from '../service';
@@ -32,16 +33,37 @@ const processToggleMuteFromOutside = (e) => {
   }
 };
 
+const handleLeaveAudio = () => {
+  Service.exitAudio();
+  logger.info({
+    logCode: 'audiocontrols_leave_audio',
+    extraInfo: { logType: 'user_action' },
+  }, 'audio connection closed by user');
+};
+
+const {
+  currentUser,
+  isConnected,
+  isListenOnly,
+  isEchoTest,
+  isMuted,
+  isConnecting,
+  isHangingUp,
+  isTalking,
+  toggleMuteMicrophone,
+  joinListenOnly,
+} = Service;
+
 export default lockContextContainer(withModalMounter(withTracker(({ mountModal, userLocks }) => ({
   processToggleMuteFromOutside: arg => processToggleMuteFromOutside(arg),
-  showMute: Service.isConnected() && !Service.isListenOnly() && !Service.isEchoTest() && !userLocks.userMic,
-  muted: Service.isConnected() && !Service.isListenOnly() && Service.isMuted(),
-  inAudio: Service.isConnected() && !Service.isEchoTest(),
-  listenOnly: Service.isConnected() && Service.isListenOnly(),
-  disable: Service.isConnecting() || Service.isHangingUp() || !Meteor.status().connected,
-  talking: Service.isTalking() && !Service.isMuted(),
-  currentUser: Service.currentUser(),
-  handleToggleMuteMicrophone: () => Service.toggleMuteMicrophone(),
-  handleJoinAudio: () => (Service.isConnected() ? Service.joinListenOnly() : mountModal(<AudioModalContainer />)),
-  handleLeaveAudio: () => Service.exitAudio(),
+  showMute: isConnected() && !isListenOnly() && !isEchoTest() && !userLocks.userMic,
+  muted: isConnected() && !isListenOnly() && isMuted(),
+  inAudio: isConnected() && !isEchoTest(),
+  listenOnly: isConnected() && isListenOnly(),
+  disable: isConnecting() || isHangingUp() || !Meteor.status().connected,
+  talking: isTalking() && !isMuted(),
+  currentUser: currentUser(),
+  handleToggleMuteMicrophone: () => toggleMuteMicrophone(),
+  handleJoinAudio: () => (isConnected() ? joinListenOnly() : mountModal(<AudioModalContainer />)),
+  handleLeaveAudio,
 }))(AudioControlsContainer)));
diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js
index 8a84c3a19dc4fa8629455aa02310222410ba359c..c70a5e910dc30f317c874bc1d3afeeee09e7d33c 100755
--- a/bigbluebutton-html5/imports/ui/components/audio/service.js
+++ b/bigbluebutton-html5/imports/ui/components/audio/service.js
@@ -3,6 +3,9 @@ import Auth from '/imports/ui/services/auth';
 import AudioManager from '/imports/ui/services/audio-manager';
 import Meetings from '/imports/api/meetings';
 import mapUser from '/imports/ui/services/user/mapUser';
+import { makeCall } from '/imports/ui/services/api';
+import VoiceUsers from '/imports/api/voice-users';
+import logger from '/imports/startup/client/logger';
 
 const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
 
@@ -34,6 +37,24 @@ const init = (messages, intl) => {
 
 const currentUser = () => mapUser(Users.findOne({ intId: Auth.userID }));
 
+const toggleMuteMicrophone = () => {
+  makeCall('toggleSelfVoice');
+  const cvu = VoiceUsers.findOne({ meetingId: Auth.meetingID, intId: Auth.userID });
+  if (cvu) {
+    if (cvu.muted) {
+      logger.info({
+        logCode: 'audiomanager_unmute_audio',
+        extraInfo: { logType: 'user_action' },
+      }, 'microphone unmuted by user');
+    } else {
+      logger.info({
+        logCode: 'audiomanager_mute_audio',
+        extraInfo: { logType: 'user_action' },
+      }, 'microphone muted by user');
+    }
+  }
+};
+
 export default {
   init,
   exitAudio: () => AudioManager.exitAudio(),
@@ -41,7 +62,7 @@ export default {
   joinListenOnly: () => AudioManager.joinListenOnly(),
   joinMicrophone: () => AudioManager.joinMicrophone(),
   joinEchoTest: () => AudioManager.joinEchoTest(),
-  toggleMuteMicrophone: () => AudioManager.toggleMuteMicrophone(),
+  toggleMuteMicrophone,
   changeInputDevice: inputDeviceId => AudioManager.changeInputDevice(inputDeviceId),
   changeOutputDevice: outputDeviceId => AudioManager.changeOutputDevice(outputDeviceId),
   isConnected: () => AudioManager.isConnected,
diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx
index faefdb13a699115546ef897b84093d1765cb2d07..faaa783a87501a91d7dc9c6c6e9d8ef8a0128afc 100755
--- a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx
@@ -1,7 +1,9 @@
 import React, { Component } from 'react';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, intlShape } from 'react-intl';
 import { withModalMounter } from '/imports/ui/components/modal/service';
 import Modal from '/imports/ui/components/modal/fullscreen/component';
+import logger from '/imports/startup/client/logger';
+import PropTypes from 'prop-types';
 import AudioService from '../audio/service';
 import VideoService from '../video-provider/service';
 import { styles } from './styles';
@@ -37,6 +39,19 @@ const intlMessages = defineMessages({
   },
 });
 
+const propTypes = {
+  intl: intlShape.isRequired,
+  breakout: PropTypes.objectOf(Object).isRequired,
+  getURL: PropTypes.func.isRequired,
+  mountModal: PropTypes.func.isRequired,
+  breakoutURL: PropTypes.string.isRequired,
+  isFreeJoin: PropTypes.bool.isRequired,
+  currentVoiceUser: PropTypes.objectOf(Object).isRequired,
+  requestJoinURL: PropTypes.func.isRequired,
+  breakouts: PropTypes.arrayOf(Object).isRequired,
+  breakoutName: PropTypes.string.isRequired,
+};
+
 class BreakoutJoinConfirmation extends Component {
   constructor(props) {
     super(props);
@@ -56,10 +71,21 @@ class BreakoutJoinConfirmation extends Component {
       mountModal,
       breakoutURL,
       isFreeJoin,
+      currentVoiceUser,
     } = this.props;
-    const url = isFreeJoin ? getURL(this.state.selectValue) : breakoutURL;
-    // leave main room's audio when joining a breakout room
-    AudioService.exitAudio();
+
+    const { selectValue } = this.state;
+    const url = isFreeJoin ? getURL(selectValue) : breakoutURL;
+
+    if (currentVoiceUser && currentVoiceUser.joined) {
+      // leave main room's audio when joining a breakout room
+      AudioService.exitAudio();
+      logger.info({
+        logCode: 'breakoutjoinconfirmation_ended_audio',
+        extraInfo: { logType: 'user_action' },
+      }, 'joining breakout room closed audio in the main room');
+    }
+
     VideoService.exitVideo();
     window.open(url);
     mountModal(null);
@@ -67,21 +93,32 @@ class BreakoutJoinConfirmation extends Component {
 
   handleSelectChange(e) {
     const { value } = e.target;
+    const { requestJoinURL } = this.props;
     this.setState({ selectValue: value });
-    this.props.requestJoinURL(value);
+    requestJoinURL(value);
   }
 
   renderSelectMeeting() {
     const { breakouts, intl } = this.props;
+    const { selectValue } = this.state;
     return (
       <div className={styles.selectParent}>
         {`${intl.formatMessage(intlMessages.freeJoinMessage)}`}
         <select
           className={styles.select}
-          value={this.state.selectValue}
+          value={selectValue}
           onChange={this.handleSelectChange}
         >
-          {breakouts.map(({ name, breakoutId }) => (<option key={breakoutId} value={breakoutId}>{name}</option>))}
+          {
+            breakouts.map(({ name, breakoutId }) => (
+              <option
+                key={breakoutId}
+                value={breakoutId}
+              >
+                {name}
+              </option>
+            ))
+          }
         </select>
       </div>
     );
@@ -89,6 +126,7 @@ class BreakoutJoinConfirmation extends Component {
 
   render() {
     const { intl, breakoutName, isFreeJoin } = this.props;
+
     return (
       <Modal
         title={intl.formatMessage(intlMessages.title)}
@@ -110,3 +148,5 @@ class BreakoutJoinConfirmation extends Component {
 }
 
 export default withModalMounter(injectIntl(BreakoutJoinConfirmation));
+
+BreakoutJoinConfirmation.propTypes = propTypes;
diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx
index 15a99b6f0cac37125e31a36517ab74cafee85ccb..56473f9bfe62c0aa0ba75f3e40ad525c297b4309 100755
--- a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx
@@ -2,12 +2,16 @@ import React from 'react';
 import { withTracker } from 'meteor/react-meteor-data';
 import Breakouts from '/imports/api/breakouts';
 import Auth from '/imports/ui/services/auth';
+import VoiceUsers from '/imports/api/voice-users/';
 import { makeCall } from '/imports/ui/services/api';
 import breakoutService from '/imports/ui/components/breakout-room/service';
 import BreakoutJoinConfirmationComponent from './component';
 
-const BreakoutJoinConfirmationContrainer = props =>
-  (<BreakoutJoinConfirmationComponent {...props} />);
+const BreakoutJoinConfirmationContrainer = props => (
+  <BreakoutJoinConfirmationComponent
+    {...props}
+  />
+);
 
 const getURL = (breakoutId) => {
   const currentUserId = Auth.userID;
@@ -31,6 +35,8 @@ export default withTracker(({ breakout, mountModal, breakoutName }) => {
     requestJoinURL(breakoutId);
   }
 
+  const currentVoiceUser = VoiceUsers.findOne({ meetingId: Auth.meetingID, intId: Auth.userID });
+
   return {
     isFreeJoin,
     mountModal,
@@ -39,5 +45,6 @@ export default withTracker(({ breakout, mountModal, breakoutName }) => {
     breakouts: breakoutService.getBreakouts(),
     requestJoinURL,
     getURL,
+    currentVoiceUser,
   };
 })(BreakoutJoinConfirmationContrainer);
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js
index 8601ccef6ac6a0952bef5c32b8cc9d63a828fe53..48841c0a425ec0437701a199b3ee94997cba61e8 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/service.js
+++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js
@@ -11,6 +11,8 @@ import { EMOJI_STATUSES } from '/imports/utils/statuses';
 import { makeCall } from '/imports/ui/services/api';
 import _ from 'lodash';
 import KEY_CODES from '/imports/utils/keyCodes';
+import AudioService from '/imports/ui/components/audio/service';
+import logger from '/imports/startup/client/logger';
 
 const CHAT_CONFIG = Meteor.settings.public.chat;
 const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
@@ -382,9 +384,13 @@ const removeUser = (userId) => {
 
 const toggleVoice = (userId) => {
   if (userId === Auth.userID) {
-    makeCall('toggleSelfVoice');
+    AudioService.toggleMuteMicrophone();
   } else {
     makeCall('toggleVoice', userId);
+    logger.info({
+      logCode: 'usermenu_option_mute_audio',
+      extraInfo: { logType: 'moderator_action' },
+    }, 'moderator muted user microphone');
   }
 };
 
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx
index c4ee43e7760c97e6cdef698d72379b924c9d7ee5..dde693a807fbc19bd8b0950adda43406b5b096fd 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
 import Auth from '/imports/ui/services/auth';
 import Service from '/imports/ui/components/actions-bar/service';
 import userListService from '/imports/ui/components/user-list/service';
+import logger from '/imports/startup/client/logger';
 import { defineMessages, injectIntl, intlShape } from 'react-intl';
 import { notify } from '/imports/ui/services/notification';
 import UserOptions from './component';
@@ -43,9 +44,38 @@ const UserOptionsContainer = withTracker((props) => {
     );
   };
 
+  const isMeetingMuteOnStart = () => {
+    const { voiceProp } = meeting;
+    const { muteOnStart } = voiceProp;
+    return muteOnStart;
+  };
+
+  const meetingMuteDisabledLog = () => logger.info({
+    logCode: 'useroptions_unmute_all',
+    extraInfo: { logType: 'moderator_action' },
+  }, 'moderator disabled meeting mute');
+
   return {
-    toggleMuteAllUsers: () => muteAllUsers(Auth.userID),
-    toggleMuteAllUsersExceptPresenter: () => muteAllExceptPresenter(Auth.userID),
+    toggleMuteAllUsers: () => {
+      muteAllUsers(Auth.userID);
+      if (isMeetingMuteOnStart()) {
+        return meetingMuteDisabledLog();
+      }
+      return logger.info({
+        logCode: 'useroptions_mute_all',
+        extraInfo: { logType: 'moderator_action' },
+      }, 'moderator enabled meeting mute, all users muted');
+    },
+    toggleMuteAllUsersExceptPresenter: () => {
+      muteAllExceptPresenter(Auth.userID);
+      if (isMeetingMuteOnStart()) {
+        return meetingMuteDisabledLog();
+      }
+      return logger.info({
+        logCode: 'useroptions_mute_all_except_presenter',
+        extraInfo: { logType: 'moderator_action' },
+      }, 'moderator enabled meeting mute, all users muted except presenter');
+    },
     toggleStatus,
     isMeetingMuted: meeting.voiceProp.muteOnStart,
     isUserPresenter: Service.isUserPresenter(),
diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
index 3c69c7c345dc51e96dac95947906b9258d5933b8..aed6453f353fee5313f3763b2a14a67dc2c17d3c 100755
--- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
+++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
@@ -1,5 +1,4 @@
 import { Tracker } from 'meteor/tracker';
-import { makeCall } from '/imports/ui/services/api';
 import KurentoBridge from '/imports/api/audio/client/bridge/kurento';
 import Auth from '/imports/ui/services/auth';
 import VoiceUsers from '/imports/api/voice-users';
@@ -138,11 +137,13 @@ class AudioManager {
           extension: ECHO_TEST_NUMBER,
           inputStream: this.inputStream,
         };
+        logger.info({ logCode: 'audiomanager_join_echotest', extraInfo: { logType: 'user_action' } }, 'User requested to join audio conference with mic');
         return this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this));
       });
   }
 
-  async joinListenOnly(retries = 0) {
+  async joinListenOnly(r = 0) {
+    let retries = r;
     this.isListenOnly = true;
     this.isEchoTest = false;
 
@@ -180,6 +181,11 @@ class AudioManager {
     });
 
     const handleListenOnlyError = async (err) => {
+      const error = {
+        type: 'MEDIA_ERROR',
+        message: this.messages.error.MEDIA_ERROR,
+      };
+
       if (iceGatheringTimeout) {
         clearTimeout(iceGatheringTimeout);
       }
@@ -191,12 +197,12 @@ class AudioManager {
           retries,
         },
       }, 'Listen only error');
-      throw {
-        type: 'MEDIA_ERROR',
-        message: this.messages.error.MEDIA_ERROR,
-      };
+
+      throw error;
     };
 
+    logger.info({ logCode: 'audiomanager_join_listenonly', extraInfo: { logType: 'user_action' } }, 'user requested to connect to audio conference as listen only');
+
     return this.onAudioJoining()
       .then(() => Promise.race([
         bridge.joinAudio(callOptions, this.callStateCallback.bind(this)),
@@ -214,13 +220,16 @@ class AudioManager {
           }
 
           try {
-            await this.joinListenOnly(++retries);
+            retries += 1;
+            await this.joinListenOnly(retries);
           } catch (error) {
             return handleListenOnlyError(error);
           }
         } else {
-          handleListenOnlyError(err);
+          return handleListenOnlyError(err);
         }
+
+        return null;
       });
   }
 
@@ -247,10 +256,6 @@ class AudioManager {
     return this.bridge.transferCall(this.onAudioJoin.bind(this));
   }
 
-  toggleMuteMicrophone() {
-    makeCall('toggleSelfVoice');
-  }
-
   onAudioJoin() {
     this.isConnecting = false;
     this.isConnected = true;
@@ -362,9 +367,11 @@ class AudioManager {
       this.listenOnlyAudioContext.close();
     }
 
-    this.listenOnlyAudioContext = window.AudioContext
-      ? new window.AudioContext()
-      : new window.webkitAudioContext();
+    const { AudioContext, WebkitAudioContext } = window;
+
+    this.listenOnlyAudioContext = AudioContext
+      ? new AudioContext()
+      : new WebkitAudioContext();
 
     const dest = this.listenOnlyAudioContext.createMediaStreamDestination();
 
@@ -402,9 +409,11 @@ class AudioManager {
       return Promise.resolve(inputDevice);
     };
 
-    const handleChangeInputDeviceError = () => Promise.reject({
-      type: 'MEDIA_ERROR',
-      message: this.messages.error.MEDIA_ERROR,
+    const handleChangeInputDeviceError = () => new Promise((reject) => {
+      reject({
+        type: 'MEDIA_ERROR',
+        message: this.messages.error.MEDIA_ERROR,
+      });
     });
 
     if (!deviceId) {