From 02cdcb2562af6d6542acc1da636474c9ae8cc209 Mon Sep 17 00:00:00 2001
From: gcampes <gabrieldecampes@gmail.com>
Date: Tue, 10 Oct 2017 15:03:29 -0300
Subject: [PATCH] changes to styles and texts in audio flow

---
 .../api/2.0/audio/client/bridge/sip.js        |  66 +++---
 .../actions-bar/emoji-menu/component.jsx      |   2 +
 .../actions-bar/emoji-menu/styles.scss        |   5 +
 .../audio/audio-controls/styles.scss          |   4 +
 .../audio/audio-modal/component.jsx           | 200 +++++++++++-------
 .../audio/audio-modal/container.jsx           |  31 +--
 .../audio/audio-settings/component.jsx        |  86 ++++----
 .../audio/audio-settings/styles.scss          |  59 +++---
 .../audio/audio-stream-volume/component.jsx   |   4 +-
 .../components/audio/audio-test/styles.scss   |   8 +-
 .../ui/components/audio/echo-test/styles.scss |   1 -
 .../imports/ui/components/audio/service.js    |   6 +-
 .../ui/services/audio-manager/index.js        |  25 ++-
 bigbluebutton-html5/private/locales/en.json   |   4 +
 14 files changed, 284 insertions(+), 217 deletions(-)
 create mode 100644 bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/styles.scss

diff --git a/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/sip.js
index 47a2156b5a..1992b45dae 100644
--- a/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/sip.js
+++ b/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/sip.js
@@ -11,14 +11,14 @@ export default class SIPBridge extends BaseAudioBridge {
 
   joinAudio({ isListenOnly, extension, inputStream }, managerCallback) {
     return new Promise((resolve, reject) => {
-      extension = extension || this.userData.voiceBridge;
+      const callExtension = extension || this.userData.voiceBridge;
 
       const callback = (message) => {
         managerCallback(message).then(() => resolve());
       };
 
-      return this.doCall({ extension, isListenOnly, inputStream }, callback)
-                 .catch(reason => {
+      return this.doCall({ callExtension, isListenOnly, inputStream }, callback)
+                 .catch((reason) => {
                    callback({ status: 'failed', error: reason });
                    reject(reason);
                  });
@@ -35,7 +35,7 @@ export default class SIPBridge extends BaseAudioBridge {
     });
   }
 
-  doCall({ isListenOnly, extension, inputStream }, callback) {
+  doCall({ isListenOnly, callExtension, inputStream }, callback) {
     const {
       userId,
       username,
@@ -46,9 +46,12 @@ export default class SIPBridge extends BaseAudioBridge {
     const callerIdName = `${userId}-bbbID-${username}`;
 
     return this.fetchStunTurnServers(sessionToken)
-                        .then((stunTurnServers) => this.createUserAgent(server, callerIdName, stunTurnServers))
-                        .then((userAgent) => this.inviteUserAgent(extension, server, userAgent, inputStream))
-                        .then((currentSession) => this.setupEventHandlers(currentSession, callback));
+                        .then(stunTurnServers =>
+                          this.createUserAgent(server, callerIdName, stunTurnServers))
+                        .then(userAgent =>
+                          this.inviteUserAgent(callExtension, server, userAgent, inputStream))
+                        .then(currentSession =>
+                          this.setupEventHandlers(currentSession, callback));
   }
 
   fetchStunTurnServers(sessionToken) {
@@ -56,11 +59,11 @@ export default class SIPBridge extends BaseAudioBridge {
     return new Promise(async (resolve, reject) => {
       const url = `/bigbluebutton/api/stuns?sessionToken=${sessionToken}`;
 
-      let response = await fetch(url)
-        .then(response => response.json())
-        .then(({ response, stunServers, turnServers}) => {
+      const response = await fetch(url)
+        .then(res => res.json())
+        .then(({ result, stunServers, turnServers }) => {
           return new Promise((resolve) => {
-            if (response) {
+            if (result) {
               resolve({ error: 404, stun: [], turn: [] });
             }
             resolve({
@@ -70,8 +73,8 @@ export default class SIPBridge extends BaseAudioBridge {
           });
         });
 
-      if(response.error) return reject(`Could not fetch the stuns/turns servers!`);
-      resolve(response);
+      if (response.error) return reject('Could not fetch the stuns/turns servers!');
+      return resolve(response);
     })
   }
 
@@ -79,7 +82,7 @@ export default class SIPBridge extends BaseAudioBridge {
     console.log('CREATEUSERAGENT');
     return new Promise((resolve, reject) => {
       const protocol = document.location.protocol;
-
+      console.log('username', username);
       this.userAgent = new window.SIP.UA({
         uri: `sip:${encodeURIComponent(username)}@${server}`,
         wsServers: `${('https:' === protocol ? 'wss://' : 'ws://')}${server}/ws`,
@@ -115,23 +118,24 @@ export default class SIPBridge extends BaseAudioBridge {
   inviteUserAgent(voiceBridge, server, userAgent, inputStream) {
     console.log('INVITEUSERAGENT');
     const options = {
-			media: {
-				stream: inputStream,
-				constraints: {
-					audio: true,
-					video: false
-				},
-				render: {
-					remote: document.getElementById('remote-media')
-				}
-			},
-			RTCConstraints: {
-				mandatory: {
-					OfferToReceiveAudio: true,
-					OfferToReceiveVideo: false
-				}
-			}
-    }
+      media: {
+        stream: inputStream,
+        constraints: {
+          audio: true,
+          video: false,
+        },
+        render: {
+          remote: document.getElementById('remote-media'),
+        },
+      },
+      RTCConstraints: {
+        mandatory: {
+          OfferToReceiveAudio: true,
+          OfferToReceiveVideo: false,
+        },
+      },
+    };
+
     console.log(voiceBridge, server, userAgent);
     return userAgent.invite(`sip:${voiceBridge}@${server}`, options);
   }
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/component.jsx
index ac4833c1ae..3172879f85 100644
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/component.jsx
@@ -9,6 +9,7 @@ import DropdownContent from '/imports/ui/components/dropdown/content/component';
 import DropdownList from '/imports/ui/components/dropdown/list/component';
 import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
 import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
+import styles from './styles';
 
 const propTypes = {
   // Emoji status of the current user
@@ -32,6 +33,7 @@ class EmojiMenu extends Component {
       <Dropdown autoFocus>
         <DropdownTrigger tabIndex={0}>
           <Button
+            className={styles.button}
             role="button"
             label={intl.formatMessage(intlMessages.statusTriggerLabel)}
             aria-label={intl.formatMessage(intlMessages.changeStatusLabel)}
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/styles.scss b/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/styles.scss
new file mode 100644
index 0000000000..badb91c910
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/emoji-menu/styles.scss
@@ -0,0 +1,5 @@
+.button {
+  span:first-child {
+    box-shadow: 0 2px 5px 0 rgb(0, 0, 0);
+  }
+}
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss
index dd9268be76..d0e6f3eeed 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/styles.scss
@@ -4,6 +4,10 @@
 
   > * {
     margin: 0 1rem;
+
+    span:first-child {
+      box-shadow: 0 2px 5px 0 rgb(0, 0, 0);
+    }
   }
 
   > :last-child {
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 73804874e5..587bed2d1d 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
@@ -1,37 +1,30 @@
 import React, { Component } from 'react';
+import PropTypes from 'prop-types';
 import ModalBase from '/imports/ui/components/modal/base/component';
 import Button from '/imports/ui/components/button/component';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, intlShape } from 'react-intl';
 import styles from './styles';
 import AudioSettings from '../audio-settings/component';
 import EchoTest from '../echo-test/component';
 
-const intlMessages = defineMessages({
-  backLabel: {
-    id: 'app.audio.backLabel',
-    description: 'audio settings back button label',
-  },
-  titleLabel: {
-    id: 'app.audio.audioSettings.titleLabel',
-    description: 'audio setting title label',
-  },
-  descriptionLabel: {
-    id: 'app.audio.audioSettings.descriptionLabel',
-    description: 'audio settings description label',
-  },
-  micSourceLabel: {
-    id: 'app.audio.audioSettings.microphoneSourceLabel',
-    description: 'Label for mic source',
-  },
-  speakerSourceLabel: {
-    id: 'app.audio.audioSettings.speakerSourceLabel',
-    description: 'Label for speaker source',
-  },
-  streamVolumeLabel: {
-    id: 'app.audio.audioSettings.microphoneStreamLabel',
-    description: 'Label for stream volume',
-  },
+const propTypes = {
+  intl: intlShape.isRequired,
+  closeModal: PropTypes.func.isRequired,
+  joinMicrophone: PropTypes.func.isRequired,
+  joinListenOnly: PropTypes.func.isRequired,
+  joinEchoTest: PropTypes.func.isRequired,
+  exitAudio: PropTypes.func.isRequired,
+  leaveEchoTest: PropTypes.func.isRequired,
+  changeInputDevice: PropTypes.func.isRequired,
+  changeOutputDevice: PropTypes.func.isRequired,
+  isEchoTest: PropTypes.bool.isRequired,
+  isConnecting: PropTypes.bool.isRequired,
+  isConnected: PropTypes.bool.isRequired,
+  inputDeviceId: PropTypes.string.isRequired,
+  outputDeviceId: PropTypes.string.isRequired,
+};
 
+const intlMessages = defineMessages({
   microphoneLabel: {
     id: 'app.audioModal.microphoneLabel',
     description: 'Join mic audio button label',
@@ -48,6 +41,22 @@ const intlMessages = defineMessages({
     id: 'app.audioModal.audioChoiceLabel',
     description: 'Join audio modal title',
   },
+  echoTestTitle: {
+    id: 'app.audioModal.echoTestTitle',
+    description: 'Title for the echo test',
+  },
+  settingsTitle: {
+    id: 'app.audioModal.settingsTitle',
+    description: 'Title for the audio modal',
+  },
+  connecting: {
+    id: 'app.audioModal.connecting',
+    description: 'Message for audio connecting',
+  },
+  connectingEchoTest: {
+    id: 'app.audioModal.connectingEchoTest',
+    description: 'Message for echo test connecting',
+  },
 });
 
 class AudioModal extends Component {
@@ -55,32 +64,47 @@ class AudioModal extends Component {
     super(props);
 
     this.state = {
-      content: 'settings',
+      content: null,
     };
 
+    const {
+      intl,
+      closeModal,
+      joinMicrophone,
+      joinListenOnly,
+      joinEchoTest,
+      exitAudio,
+      leaveEchoTest,
+      changeInputDevice,
+      changeOutputDevice,
+    } = props;
+
     this.handleGoToAudioOptions = this.handleGoToAudioOptions.bind(this);
     this.handleGoToAudioSettings = this.handleGoToAudioSettings.bind(this);
     this.handleGoToEchoTest = this.handleGoToEchoTest.bind(this);
-    this.closeModal = props.closeModal;
-    this.handleJoinMicrophone = props.joinMicrophone;
-    this.handleJoinListenOnly = props.joinListenOnly;
-    this.joinEchoTest = props.joinEchoTest;
-    this.exitAudio = props.exitAudio;
-    this.leaveEchoTest = props.leaveEchoTest;
-    this.changeInputDevice = props.changeInputDevice;
-    this.changeOutputDevice = props.changeOutputDevice;
-
-    console.log(props);
+    this.closeModal = closeModal;
+    this.handleJoinMicrophone = joinMicrophone;
+    this.handleJoinListenOnly = joinListenOnly;
+    this.joinEchoTest = joinEchoTest;
+    this.exitAudio = exitAudio;
+    this.leaveEchoTest = leaveEchoTest;
+    this.changeInputDevice = changeInputDevice;
+    this.changeOutputDevice = changeOutputDevice;
 
     this.contents = {
-      echoTest: () => this.renderEchoTest(),
-      settings: () => this.renderAudioSettings(),
+      echoTest: {
+        title: intl.formatMessage(intlMessages.echoTestTitle),
+        component: () => this.renderEchoTest(),
+      },
+      settings: {
+        title: intl.formatMessage(intlMessages.settingsTitle),
+        component: () => this.renderAudioSettings(),
+      },
     };
   }
 
   componentWillUnmount() {
     const {
-      isConnected,
       isEchoTest,
     } = this.props;
 
@@ -138,41 +162,11 @@ class AudioModal extends Component {
     );
   }
 
-  render() {
-    const {
-      intl,
-      isConnecting,
-    } = this.props;
-
-    return (
-      <ModalBase
-        overlayClassName={styles.overlay}
-        className={styles.modal}
-        onRequestClose={this.closeModal}>
-          { isConnecting ? null :
-            <header className={styles.header}>
-              <h3 className={styles.title}>{intl.formatMessage(intlMessages.audioChoiceLabel)}</h3>
-              <Button
-                className={styles.closeBtn}
-                label={intl.formatMessage(intlMessages.closeLabel)}
-                icon={'close'}
-                size={'md'}
-                hideLabel
-                onClick={this.closeModal}
-              />
-            </header>
-          }
-        <div className={styles.content}>
-          { this.renderContent() }
-        </div>
-      </ModalBase>
-    );
-  }
-
   renderContent() {
     const {
       isConnecting,
       isEchoTest,
+      intl,
     } = this.props;
 
     const {
@@ -181,10 +175,15 @@ class AudioModal extends Component {
 
     if (isConnecting) {
       return (
-        <span className={styles.connecting}>Connecting</span>
-      )
+        <span className={styles.connecting}>
+          { isEchoTest ?
+            intl.formatMessage(intlMessages.connecting) :
+            intl.formatMessage(intlMessages.connectingEcho)
+          }
+        </span>
+      );
     }
-    return content ? this.contents[content]() : this.renderAudioOptions();
+    return content ? this.contents[content].component() : this.renderAudioOptions();
   }
 
   renderEchoTest() {
@@ -198,16 +197,18 @@ class AudioModal extends Component {
         joinEchoTest={this.joinEchoTest}
         leaveEchoTest={this.leaveEchoTest}
         handleNo={this.handleGoToAudioSettings}
-        handleYes={this.handleJoinMicrophone}/>
+        handleYes={this.handleJoinMicrophone}
+      />
     );
   }
 
-  renderAudioSettings () {
+  renderAudioSettings() {
     const {
       isConnecting,
       isConnected,
       isEchoTest,
-      inputDeviceId
+      inputDeviceId,
+      outputDeviceId,
     } = this.props;
 
     return (
@@ -222,9 +223,52 @@ class AudioModal extends Component {
         isConnected={isConnected}
         isEchoTest={isEchoTest}
         inputDeviceId={inputDeviceId}
+        outputDeviceId={outputDeviceId}
       />
-    )
+    );
+  }
+
+  render() {
+    const {
+      intl,
+      isConnecting,
+    } = this.props;
+
+    const {
+      content,
+    } = this.state;
+
+    return (
+      <ModalBase
+        overlayClassName={styles.overlay}
+        className={styles.modal}
+        onRequestClose={this.closeModal}
+      >
+        { isConnecting ? null :
+        <header className={styles.header}>
+          <h3 className={styles.title}>
+            { content ?
+              this.contents[content].title :
+              intl.formatMessage(intlMessages.audioChoiceLabel)}
+          </h3>
+          <Button
+            className={styles.closeBtn}
+            label={intl.formatMessage(intlMessages.closeLabel)}
+            icon={'close'}
+            size={'md'}
+            hideLabel
+            onClick={this.closeModal}
+          />
+        </header>
+        }
+        <div className={styles.content}>
+          { this.renderContent() }
+        </div>
+      </ModalBase>
+    );
   }
 }
 
+AudioModal.propTypes = propTypes;
+
 export default injectIntl(AudioModal);
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 c387fd191d..bb0f6df2bd 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
@@ -1,33 +1,16 @@
 import React from 'react';
 import { createContainer } from 'meteor/react-meteor-data';
-import PropTypes from 'prop-types';
 import { withModalMounter } from '/imports/ui/components/modal/service';
-// import AudioControls from './component';
-// import AudioModal from '../audio-modal/component';
 import AudioModal from './component';
 import Service from '../service';
 
-// const propTypes = {
-//   children: PropTypes.element,
-// };
-//
-// const defaultProps = {
-//   children: null,
-// };
-
 const AudioModalContainer = props => <AudioModal {...props} />;
 
 export default withModalMounter(createContainer(({ mountModal }) =>
-  // const APP_CONFIG = Meteor.settings.public.app;
-  //
-  // const { autoJoinAudio } = APP_CONFIG;
-  // const { isConnected, isConnecting, isListenOnly } = Service.getStats();
-  // let shouldShowMute = isConnected && !isListenOnly;
-  // let shouldShowUnmute = isConnected && !isListenOnly && isMuted;
-  // let shouldShowJoin = !isConnected;
-
    ({
-     closeModal: () => mountModal(null),
+     closeModal: () => {
+       if (!Service.isConnecting()) mountModal(null);
+     },
      joinMicrophone: () => {
        console.log('JOIN MIC FROM CONTAINER');
        Service.exitAudio().then(() => Service.joinMicrophone())
@@ -44,15 +27,13 @@ export default withModalMounter(createContainer(({ mountModal }) =>
        }
        return Service.exitAudio();
      },
-     changeInputDevice: (inputDeviceId) => Service.changeInputDevice(inputDeviceId),
-     changeOutputDevice: (outputDeviceId) => Service.changeOutputDevice(outputDeviceId),
+     changeInputDevice: inputDeviceId => Service.changeInputDevice(inputDeviceId),
+     changeOutputDevice: outputDeviceId => Service.changeOutputDevice(outputDeviceId),
      joinEchoTest: () => Service.joinEchoTest(),
      exitAudio: () => Service.exitAudio(),
      isConnecting: Service.isConnecting(),
      isConnected: Service.isConnected(),
      isEchoTest: Service.isEchoTest(),
      inputDeviceId: Service.inputDeviceId(),
+     outputDeviceId: Service.outputDeviceId(),
    }), AudioModalContainer));
-
-// AudioControlsContainer.propTypes = propTypes;
-// AudioControlsContainer.defaultProps = defaultProps;
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
index c818c8052f..13f82f54be 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
@@ -1,23 +1,31 @@
 import React from 'react';
-import { defineMessages, injectIntl } from 'react-intl';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl, intlShape } from 'react-intl';
 import Button from '/imports/ui/components/button/component';
 import { withModalMounter } from '/imports/ui/components/modal/service';
 import DeviceSelector from '/imports/ui/components/audio/device-selector/component';
 import AudioStreamVolume from '/imports/ui/components/audio/audio-stream-volume/component';
-import EnterAudioContainer from '/imports/ui/components/audio/enter-audio/container';
 import AudioTestContainer from '/imports/ui/components/audio/audio-test/container';
 import cx from 'classnames';
 import styles from './styles';
 
+const propTypes = {
+  intl: intlShape.isRequired,
+  exitAudio: PropTypes.func.isRequired,
+  changeInputDevice: PropTypes.func.isRequired,
+  changeOutputDevice: PropTypes.func.isRequired,
+  handleBack: PropTypes.func.isRequired,
+  handleRetry: PropTypes.func.isRequired,
+  isConnecting: PropTypes.bool.isRequired,
+  inputDeviceId: PropTypes.string.isRequired,
+  outputDeviceId: PropTypes.string.isRequired,
+};
+
 const intlMessages = defineMessages({
   backLabel: {
     id: 'app.audio.backLabel',
     description: 'audio settings back button label',
   },
-  titleLabel: {
-    id: 'app.audio.audioSettings.titleLabel',
-    description: 'audio setting title label',
-  },
   descriptionLabel: {
     id: 'app.audio.audioSettings.descriptionLabel',
     description: 'audio settings description label',
@@ -44,26 +52,28 @@ class AudioSettings extends React.Component {
   constructor(props) {
     super(props);
 
-    this.chooseAudio = this.chooseAudio.bind(this);
+    const {
+      handleBack,
+      handleRetry,
+      exitAudio,
+      changeInputDevice,
+      changeOutputDevice,
+    } = props;
+
     this.handleInputChange = this.handleInputChange.bind(this);
     this.handleOutputChange = this.handleOutputChange.bind(this);
-    this.handleBack = props.handleBack;
-    this.handleRetry = props.handleRetry;
-    this.exitAudio = props.exitAudio;
-    this.changeInputDevice = props.changeInputDevice;
-    this.changeOutputDevice = props.changeOutputDevice;
+    this.handleBack = handleBack;
+    this.handleRetry = handleRetry;
+    this.exitAudio = exitAudio;
+    this.changeInputDevice = changeInputDevice;
+    this.changeOutputDevice = changeOutputDevice;
 
-    console.log('inputDeviceId', props.inputDeviceId);
     this.state = {
       inputDeviceId: props.inputDeviceId,
-      outputDeviceId: undefined,
+      outputDeviceId: props.outputDeviceId,
     };
   }
 
-  chooseAudio() {
-    this.props.changeMenu(this.props.JOIN_AUDIO);
-  }
-
   handleInputChange(deviceId, device) {
     console.log(`INPUT DEVICE CHANGED: ${deviceId}`);
     console.log(device);
@@ -82,18 +92,12 @@ class AudioSettings extends React.Component {
     });
   }
 
-  // handleClose() {
-  //   this.setState({ isOpen: false });
-  //   this.props.mountModal(null);
-  // }
-
   render() {
     const {
       isConnecting,
       intl,
     } = this.props;
 
-    // return this.renderCalling();
     return (
       <div>
         <div className={styles.form}>
@@ -106,9 +110,13 @@ class AudioSettings extends React.Component {
           <div className={styles.row}>
             <div className={styles.col}>
               <div className={styles.formElement}>
-                <label className={cx(styles.label, styles.labelSmall)}>
+                <label
+                  htmlFor="inputDeviceSelector"
+                  className={cx(styles.label, styles.labelSmall)}
+                >
                   {intl.formatMessage(intlMessages.micSourceLabel)}
                   <DeviceSelector
+                    id="inputDeviceSelector"
                     value={this.state.inputDeviceId}
                     className={styles.select}
                     kind="audioinput"
@@ -119,9 +127,13 @@ class AudioSettings extends React.Component {
             </div>
             <div className={styles.col}>
               <div className={styles.formElement}>
-                <label className={cx(styles.label, styles.labelSmall)}>
+                <label
+                  htmlFor="outputDeviceSelector"
+                  className={cx(styles.label, styles.labelSmall)}
+                >
                   {intl.formatMessage(intlMessages.speakerSourceLabel)}
                   <DeviceSelector
+                    id="outputDeviceSelector"
                     value={this.state.outputDeviceId}
                     className={styles.select}
                     kind="audiooutput"
@@ -133,21 +145,13 @@ class AudioSettings extends React.Component {
           </div>
 
           <div className={styles.row}>
-            <div className={styles.col}>
-              <div className={styles.formElement}>
-                <label className={cx(styles.label, styles.labelSmall)}>
-                  {intl.formatMessage(intlMessages.streamVolumeLabel)}
-                  <AudioStreamVolume
-                    deviceId={this.state.inputDeviceId}
-                    className={styles.audioMeter}
-                  />
-                </label>
-              </div>
-            </div>
-            <div className={styles.col}>
-              <label className={styles.labelSmall}>
+            <div className={cx(styles.col, styles.spacedLeft)}>
+              <label
+                htmlFor="audioTest"
+                className={styles.labelSmall}
+              >
                 Test your speaker volume
-                <AudioTestContainer />
+                <AudioTestContainer id="audioTest" />
               </label>
             </div>
           </div>
@@ -177,4 +181,6 @@ class AudioSettings extends React.Component {
 
 }
 
+AudioSettings.propTypes = propTypes;
+
 export default withModalMounter(injectIntl(AudioSettings));
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss
index 5ea4ee9c9f..5ae2ec17f7 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss
@@ -19,6 +19,14 @@
   margin-bottom: 0.7rem;
 }
 
+// .spaced {
+//
+//   // >*{
+//   //   background-color: red;
+//   // }
+//
+// }
+
 .col {
   display: flex;
   flex-grow: 1;
@@ -30,6 +38,32 @@
     padding-right: 0.1rem;
     padding-left: 4rem;
   }
+
+  &.spacedLeft {
+    // @extend .spaced;
+
+    label {
+      flex-grow: 1;
+      flex-basis: 0;
+      margin-right: 0;
+      padding-right: 0.1rem;
+      padding-left: 4rem;
+    }
+
+    &:before {
+      content: "";
+      display: block;
+      flex-grow: 1;
+      flex-basis: 0;
+      margin-right: 1rem;
+    }
+
+    &:last-child {
+      margin-right: 0;
+      padding-right: 0;
+      padding-left: 0;
+    }
+  }
 }
 
 .labelSmall {
@@ -98,28 +132,3 @@
   left:50%;
   transform: translate(-50%, 0);
 }
-
-.calling:after {
-  overflow: hidden;
-  display: inline-block;
-  vertical-align: bottom;
-  -webkit-animation: ellipsis steps(4,end) 900ms infinite;
-  animation: ellipsis steps(4,end) 900ms infinite;
-  content: "\2026"; /* ascii code for the ellipsis character */
-  width: 0;
-  margin-right: 1.25em;
-}
-
-@keyframes ellipsis {
-  to {
-    width: 1.25em;
-    margin-right: 0;
-  }
-}
-
-@-webkit-keyframes ellipsis {
-  to {
-    width: 1.25em;
-    margin-right: 0;
-  }
-}
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-stream-volume/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-stream-volume/component.jsx
index bd73393123..3ce501571b 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-stream-volume/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-stream-volume/component.jsx
@@ -89,7 +89,7 @@ class AudioStreamVolume extends Component {
   handleConnectStreamToProcessor(stream) {
     this.source = this.audioContext.createMediaStreamSource(stream);
     this.source.connect(this.scriptProcessor);
-    // this.scriptProcessor.connect(this.audioContext.destination);
+    this.scriptProcessor.connect(this.audioContext.destination);
   }
 
   handleAudioProcess(event) {
@@ -99,7 +99,7 @@ class AudioStreamVolume extends Component {
 
     this.setState(prevState => ({
       instant,
-      slow: 0.75 * prevState.slow + 0.25 * instant,
+      slow: (0.75 * prevState.slow) + (0.25 * instant),
     }));
   }
 
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss
index d2800c86f1..9ffdec2685 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss
@@ -4,14 +4,18 @@
   background-color: transparent;
   color: $color-primary;
   font-weight: normal;
+  border: none;
+
   i {
     color: $color-primary;
     transition: all .2s ease-in-out;
   }
 
-  &:hover {
+  &:hover, &:focus {
+    background-color: transparent;
+    color: darken($color-primary, 17%);
     i {
-      color: $color-white;
+      color: darken($color-primary, 17%);
     }
   }
 }
diff --git a/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss
index a81d0b5761..d0d427c5c7 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss
@@ -1,5 +1,4 @@
 @import "/imports/ui/stylesheets/variables/_all";
-@import "/imports/ui/components/modal/simple/styles";
 
 .button {
   &:first-child {
diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js
index 0168de733c..80c9613afe 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/service.js
+++ b/bigbluebutton-html5/imports/ui/components/audio/service.js
@@ -23,7 +23,7 @@ const init = () => {
     microphoneLockEnforced,
   };
 
-  AudioManager.userData = userData;
+  AudioManager.init(userData);
 };
 
 export default {
@@ -33,8 +33,8 @@ export default {
   joinMicrophone: () => AudioManager.joinAudio(),
   joinEchoTest: () => AudioManager.joinAudio({ isEchoTest: true }),
   toggleMuteMicrophone: () => AudioManager.toggleMuteMicrophone(),
-  changeInputDevice: (inputDeviceId) => AudioManager.changeInputDevice(inputDeviceId),
-  changeOutputDevice: (outputDeviceId) => AudioManager.changeOutputDevice(outputDeviceId),
+  changeInputDevice: inputDeviceId => AudioManager.changeInputDevice(inputDeviceId),
+  changeOutputDevice: outputDeviceId => AudioManager.changeOutputDevice(outputDeviceId),
   isConnected: () => AudioManager.isConnected,
   isMuted: () => AudioManager.isMuted,
   isConnecting: () => AudioManager.isConnecting,
diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
index 55a7add00f..fdb49ca5fa 100644
--- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
+++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
@@ -28,18 +28,18 @@ const CALL_STATES = {
 class AudioManager {
   constructor() {
     this._inputDevice = {
-      tracker: new Tracker.Dependency,
+      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(device => device.label === deviceLabel);
+        navigator.mediaDevices.enumerateDevices().then((devices) => {
+          const device = devices.find(d => d.label === deviceLabel);
           this.changeInputDevice(device.deviceId);
-        })
-      });
+        });
+      }).catch(err => console.error(err));
 
 
     this.defineProperties({
@@ -58,7 +58,7 @@ class AudioManager {
       const privateKey = `_${key}`;
       this[privateKey] = {
         value: obj[key],
-        tracker: new Tracker.Dependency,
+        tracker: new Tracker.Dependency(),
       };
 
       Object.defineProperty(this, key, {
@@ -74,6 +74,12 @@ class AudioManager {
     });
   }
 
+  init(userData) {
+    console.log('init', userData);
+    this.bridge = USE_SIP ? new SIPBridge(userData) : new VertoBridge(userData);
+    this.userData = userData;
+  }
+
   joinAudio(options = {}, callbacks = {}) {
     const {
       isListenOnly,
@@ -184,9 +190,7 @@ class AudioManager {
   }
 
   set userData(value) {
-    console.log('set user data');
     this._userData = value;
-    this.bridge = USE_SIP ? new SIPBridge(value) : new VertoBridge(value);
   }
 
   get userData() {
@@ -208,6 +212,7 @@ class AudioManager {
   }
 
   changeInputDevice(value) {
+    console.log('changeInputDevice');
     if (this._inputDevice.audioContext) {
       this._inputDevice.audioContext.close().then(() => {
         this._inputDevice.audioContext = null;
@@ -254,11 +259,11 @@ class AudioManager {
     console.log('Change id');
   }
 
-  get inputStream () {
+  get inputStream() {
     return this._inputDevice.stream;
   }
 
-  get inputDeviceId () {
+  get inputDeviceId() {
     this._inputDevice.tracker.depend();
     return this._inputDevice.id;
   }
diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json
index d8fbe1df5a..f8eb170103 100755
--- a/bigbluebutton-html5/private/locales/en.json
+++ b/bigbluebutton-html5/private/locales/en.json
@@ -202,6 +202,10 @@
     "app.audioModal.closeLabel": "Close",
     "app.audioModal.yes": "Yes",
     "app.audioModal.no": "No",
+    "app.audioModal.echoTestTitle": "Speak a few words, Do you hear yourself?",
+    "app.audioModal.settingsTitle": "Change your audio settings",
+    "app.audioModal.connecting": "Connecting",
+    "app.audioModal.connectingEchoTest": "Connecting to echo test",
     "app.audio.joinAudio": "Join Audio",
     "app.audio.leaveAudio": "Leave Audio",
     "app.audio.enterSessionLabel": "Enter Session",
-- 
GitLab