From 40795950c23b24d7cc5358ec27ff4efed7f23d4a Mon Sep 17 00:00:00 2001
From: gcampes <gabrieldecampes@gmail.com>
Date: Wed, 4 Oct 2017 17:49:11 -0300
Subject: [PATCH] updates to the audio manager

---
 .../api/2.0/audio/client/bridge/sip.js        |  91 +++++-------
 .../audio/audio-modal/component.jsx           | 139 +++++++++++++-----
 .../audio/audio-modal/container.jsx           |  19 ++-
 .../components/audio/audio-modal/styles.scss  |  50 ++++++-
 .../audio/audio-settings/component.jsx        |  33 +----
 .../components/audio/echo-test/component.jsx  |  17 ++-
 .../ui/components/audio/echo-test/styles.scss | 103 +------------
 .../imports/ui/components/button/styles.scss  |   7 +
 .../ui/services/audio-manager/index.js        |  76 ++++++----
 bigbluebutton-html5/private/locales/en.json   |   1 +
 10 files changed, 272 insertions(+), 264 deletions(-)

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 85910a922b..5c595cdf47 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
@@ -1,7 +1,5 @@
 import BaseAudioBridge from './base';
 
-const RETRY_INTERVAL = Meteor.settings.public.media.WebRTCHangupRetryInterval;
-
 export default class SIPBridge extends BaseAudioBridge {
   constructor(userData) {
     super();
@@ -9,43 +7,35 @@ export default class SIPBridge extends BaseAudioBridge {
 
     console.log('userdata', userData);
     this.isConnected = false;
-    this.callStates = {
-      callStarted: 'iceConnectionCompleted',
-      callEnded: 'bye',
-      callDisconnected: 'failed',
-    };
   }
 
   joinAudio({ isListenOnly, extension, inputStream }, managerCallback) {
-    return new Promise((resolve) => {
+    return new Promise((resolve, reject) => {
       extension = extension || this.userData.voiceBridge;
 
       const callback = (message) => {
         managerCallback(message).then(() => resolve());
       };
 
-      this.makeCall({ extension, isListenOnly, inputStream }, callback);
+      return this.doCall({ extension, isListenOnly, inputStream }, callback)
+                 .catch(reason => {
+                   callback({ status: 'failed', error: reason });
+                   reject(reason);
+                 });
     });
   }
 
   exitAudio() {
     return new Promise((resolve) => {
-      const attemptHangup = () => {
-        if (this.isConnected) {
-          console.log('Attempting to hangup on WebRTC call');
-          window.webrtc_hangup(() => resolve());
-        } else {
-          console.log('RETRYING hangup on WebRTC call in ' +
-            `${RETRY_INTERVAL} ms`);
-          setTimeout(attemptHangup, RETRY_INTERVAL);
-        }
-      };
-
-      return attemptHangup();
+      this.currentSession.on('bye', () => {
+        this.hangup = true;
+        resolve();
+      });
+      this.currentSession.bye();
     });
   }
 
-  makeCall({ isListenOnly, extension, inputStream }, callback) {
+  doCall({ isListenOnly, extension, inputStream }, callback) {
     const {
       userId,
       username,
@@ -102,8 +92,6 @@ export default class SIPBridge extends BaseAudioBridge {
         turnServers: turn,
       });
 
-      console.log(this.userAgent);
-
       this.userAgent.removeAllListeners('connected');
       this.userAgent.removeAllListeners('disconnected');
 
@@ -117,6 +105,7 @@ export default class SIPBridge extends BaseAudioBridge {
         this.userAgent.stop();
         this.userAgent = null;
         console.log('DISCONNECTED');
+        reject('CONNECTION_ERROR');
       })
 
       this.userAgent.start();
@@ -150,45 +139,33 @@ export default class SIPBridge extends BaseAudioBridge {
   setupEventHandlers(currentSession, callback) {
     return new Promise((resolve) => {
       console.log('SETUPEVENTHANDLERS');
-      currentSession.mediaHandler.on('iceGatheringComplete', function() {
-        callback({ status: 'iceGatheringComplete' });
-      });
-
-      currentSession.on('connecting', () => {
-        callback({})
-      	console.log('connecting');
-      });
-
-      currentSession.on('progress', (response) => {
-        console.log('progress');
-      });
-
-      currentSession.on('failed', (response, cause) => {
-        console.log('failed', cause);
-      });
-
-      currentSession.on('bye', (request) => {
-        console.log('bye');
-        callback({ status: 'bye' });
-      });
-
-      currentSession.on('accepted', (data) => {
-        console.log('accepted');
-      });
 
-      currentSession.mediaHandler.on('iceConnectionFailed', () => {
-        console.log('iceConnectionFailed');
-      });
-
-      currentSession.mediaHandler.on('iceConnectionConnected', () => {
-        console.log('iceConnectionConnected');
-      });
+      const {
+        causes,
+      } = window.SIP.C;
+
+      currentSession.on('terminated', (message, cause) => {
+        console.log('TERMINATED', message, cause);
+        if (!message && !cause) {
+          return callback({ status: 'ended'})
+        } else if(cause) {
+          if (cause === causes.REQUEST_TIMEOUT) {
+            return callback({ status: 'failed' , error: 'REQUEST_TIMEOUT'});
+          } else if (cause === causes.CONNECTION_ERROR) {
+            return callback({ status: 'failed' , error: 'CONNECTION_ERROR'});
+          }
+        }
+        return callback({ status: 'failed' , error: 'ERROR', message: cause});
+      })
 
       currentSession.mediaHandler.on('iceConnectionCompleted', () => {
         console.log('iceConnectionCompleted');
-        callback({ status: 'iceConnectionCompleted' });
+        this.hangup = false;
+        callback({ status: 'started' });
         resolve();
       });
+
+      this.currentSession = currentSession;
     })
   }
 }
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 7ed27c9329..5d235edac6 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
@@ -68,28 +68,17 @@ class AudioModal extends Component {
     this.handleGoToAudioOptions = this.handleGoToAudioOptions.bind(this);
     this.handleGoToAudioSettings = this.handleGoToAudioSettings.bind(this);
     this.handleGoToEchoTest = this.handleGoToEchoTest.bind(this);
-    this.handleClose = props.closeModal;
+    this.closeModal = props.closeModal.bind(this);
     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.contents = {
-      echoTest: <EchoTest
-        handleNo={this.handleGoToAudioOptions}
-        handleYes={this.handleJoinMicrophone}/>,
-      settings: <AudioSettings
-        handleBack={this.handleBack}
-        handleJoin={this.handleJoinMicrophone}
-        joinEchoTest={this.joinEchoTest}
-        exitAudio={this.exitAudio}
-        changeInputDevice={this.changeInputDevice}
-        isConnecting={isConnecting}
-        isConnected={isConnected}
-        isEchoTest={isEchoTest}
-        inputDeviceId={inputDeviceId}
-      />,
+      echoTest: () => this.renderEchoTest(),
+      settings: () => this.renderAudioSettings(),
     };
   }
 
@@ -100,15 +89,30 @@ class AudioModal extends Component {
   }
 
   handleGoToAudioSettings() {
-    this.setState({
-      content: 'settings',
-    });
+    this.leaveEchoTest().then(() => {
+      this.setState({
+        content: 'settings',
+      });
+    })
   }
 
   handleGoToEchoTest() {
-    this.setState({
-      content: 'echoTest',
-    });
+    this.joinEchoTest().then(() => {
+      this.setState({
+        content: 'echoTest',
+      });
+    })
+  }
+
+  componentWillUnmount() {
+    const {
+      isConnected,
+      isEchoTest,
+    } = this.props;
+
+    if (isEchoTest) {
+      this.exitAudio();
+    }
   }
 
   renderAudioOptions() {
@@ -117,7 +121,7 @@ class AudioModal extends Component {
     } = this.props;
 
     return (
-      <div className={styles.content}>
+      <span>
         <Button
           className={styles.audioBtn}
           label={intl.formatMessage(intlMessages.microphoneLabel)}
@@ -134,37 +138,96 @@ class AudioModal extends Component {
           size={'jumbo'}
           onClick={this.handleJoinListenOnly}
         />
-      </div>
+      </span>
     );
   }
 
   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,
+    } = this.props;
+
     const {
       content,
     } = this.state;
 
+    if (isConnecting) {
+      return (
+        <span className={styles.connecting}>Connecting</span>
+      )
+    }
+    return content ? this.contents[content]() : this.renderAudioOptions();
+  }
+
+  renderEchoTest() {
     const {
-      intl,
       isConnecting,
     } = this.props;
 
     return (
-      <ModalBase overlayClassName={styles.overlay} className={styles.modal}>
-        <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.handleClose}
-          />
-        </header>
-        { content ? this.contents[content] : this.renderAudioOptions() }
-      </ModalBase>
+      <EchoTest
+        isConnecting={isConnecting}
+        joinEchoTest={this.joinEchoTest}
+        leaveEchoTest={this.leaveEchoTest}
+        handleNo={this.handleGoToAudioSettings}
+        handleYes={this.handleJoinMicrophone}/>
     );
   }
+
+  renderAudioSettings () {
+    const {
+      isConnecting,
+      isConnected,
+      isEchoTest,
+      inputDeviceId
+    } = this.props;
+
+    return (
+      <AudioSettings
+        handleBack={this.handleGoToAudioOptions}
+        handleRetry={this.handleGoToEchoTest}
+        joinEchoTest={this.joinEchoTest}
+        exitAudio={this.exitAudio}
+        changeInputDevice={this.changeInputDevice}
+        isConnecting={isConnecting}
+        isConnected={isConnected}
+        isEchoTest={isEchoTest}
+        inputDeviceId={inputDeviceId}
+      />
+    )
+  }
 }
 
 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 190e5693b3..06e9b0e685 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
@@ -28,12 +28,21 @@ export default withModalMounter(createContainer(({ mountModal }) =>
 
    ({
      closeModal: () => mountModal(null),
-     joinMicrophone: () => Service.joinMicrophone().then(() => mountModal(null)),
-      //  Service.exitAudio().then(() => Service.joinMicrophone())
-      //                    .then(() => mountModal(null));
-    //  },
+     joinMicrophone: () => {
+       console.log('JOIN MIC FROM CONTAINER');
+       Service.exitAudio().then(() => Service.joinMicrophone())
+                          .then(() => mountModal(null));
+     },
      joinListenOnly: () => {
-       Service.joinMicrophone().then(a => mountModal(null));
+       Service.joinListenOnly().then(() => mountModal(null))
+                               .catch(reason => console.error(reason));
+     },
+
+     leaveEchoTest: () => {
+       if (!Service.isEchoTest()) {
+         return Promise.resolve();
+       }
+       return Service.exitAudio();
      },
      changeInputDevice: (inputDeviceId) => Service.changeInputDevice(inputDeviceId),
      joinEchoTest: () => Service.joinEchoTest(),
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss
index 041b138d98..f12aaccbf2 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss
@@ -12,7 +12,8 @@
   display: flex;
   justify-content: center;
   padding: 0;
-  margin-top: 1.5rem;
+  margin-top: auto;
+  margin-bottom: auto;
 
   .audioBtn:first-child {
     margin-right: 3rem;
@@ -32,6 +33,7 @@
 .modal {
   @extend .modal;
   padding: 1.5rem;
+  min-height: 35vh;
 }
 
 .closeBtn {
@@ -56,31 +58,36 @@
   }
 }
 
-Button.audioBtn {
+.audioBtn {
   i{
     color: #3c5764;
   }
 }
 
 // Modifies the audio button icon colour
-Button.audioBtn span:first-child {
+.audioBtn span:first-child {
   color: #1b3c4b;
   background-color: #f1f8ff;
   box-shadow: none;
   border: 5px solid  #f1f8ff;
+  font-size: 3.5rem;
+
+  @include mq($small-only) {
+    font-size: 2.5rem;
+  }
 }
 
 // When hovering over a button of class audioBtn, change the border colour of first span-child
-Button.audioBtn:hover span:first-child,
-Button.audioBtn:focus span:first-child {
+.audioBtn:hover span:first-child,
+.audioBtn:focus span:first-child {
   border: 5px solid $color-primary;
   background-color: #f1f8ff;
 }
 
 // Modifies the button label text
-Button.audioBtn span:last-child {
+.audioBtn span:last-child {
   color: black;
-  font-size: 0.8rem;
+  font-size: 1rem;
   font-weight: 600;
 }
 
@@ -104,6 +111,35 @@ Button.audioBtn span:last-child {
   }
 }
 
+.connecting {
+  font-size: 2rem;
+}
+
+.connecting: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;
+  }
+}
+
 .audioNote {
   color: $color-text;
   display: inline-block;
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 478cd34f6b..b92e680119 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
@@ -34,9 +34,9 @@ const intlMessages = defineMessages({
     id: 'app.audio.audioSettings.microphoneStreamLabel',
     description: 'Label for stream volume',
   },
-  enterSessionLabel: {
-    id: 'app.audio.enterSessionLabel',
-    description: 'enter session button label',
+  retryLabel: {
+    id: 'app.audio.audioSettings.retryLabel',
+    description: 'Retry button label',
   },
 });
 
@@ -48,8 +48,7 @@ class AudioSettings extends React.Component {
     this.handleInputChange = this.handleInputChange.bind(this);
     this.handleOutputChange = this.handleOutputChange.bind(this);
     this.handleBack = props.handleBack;
-    this.handleJoin = props.handleJoin;
-    this.joinEchoTest = props.joinEchoTest;
+    this.handleRetry = props.handleRetry;
     this.exitAudio = props.exitAudio;
     this.changeInputDevice = props.changeInputDevice;
 
@@ -61,20 +60,6 @@ class AudioSettings extends React.Component {
     };
   }
 
-  componentDidMount() {
-    // this.joinEchoTest();
-  }
-
-  componentWillUnmount() {
-    const {
-      isEchoTest,
-    } = this.props;
-
-    if (isEchoTest) {
-      this.exitAudio();
-    }
-  }
-
   chooseAudio() {
     this.props.changeMenu(this.props.JOIN_AUDIO);
   }
@@ -179,13 +164,9 @@ class AudioSettings extends React.Component {
           <Button
             size={'md'}
             color={'primary'}
-            onClick={this.handleJoin}
-            disabled={isConnecting}
-          >
-            <span className={isConnecting ? styles.calling : null}>
-              { isConnecting ? 'Calling echo test' : intl.formatMessage(intlMessages.enterSessionLabel)}
-            </span>
-          </Button>
+            label={intl.formatMessage(intlMessages.retryLabel)}
+            onClick={this.handleRetry}
+          />
         </div>
       </div>
     );
diff --git a/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx
index 521a9f32a1..5da0184955 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/echo-test/component.jsx
@@ -18,34 +18,39 @@ class EchoTest extends Component {
   constructor(props) {
     super(props);
 
-    this.handleYes = this.handleYes.bind(this);
-    this.handleNo = this.handleNo.bind(this);
+    this.handleYes = props.handleYes.bind(this);
+    this.handleNo = props.handleNo.bind(this);
+    this.joinEchoTest = props.joinEchoTest.bind(this);
+    this.leaveEchoTest = props.leaveEchoTest.bind(this);
   }
 
   render() {
     const {
       intl,
+      isConnecting,
     } = this.props;
 
     return (
-      <div className={styles.content}>
+      <span>
         <Button
-          className={styles.audioBtn}
+          className={styles.button}
           label={intl.formatMessage(intlMessages.no)}
           icon={'thumbs_down'}
           circle
+          color={'danger'}
           size={'jumbo'}
           onClick={this.handleNo}
         />
         <Button
-          className={styles.audioBtn}
+          className={styles.button}
           label={intl.formatMessage(intlMessages.yes)}
           icon={'thumbs_up'}
           circle
+          color={'success'}
           size={'jumbo'}
           onClick={this.handleYes}
         />
-      </div>
+      </span>
     );
   }
 }
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 041b138d98..a81d0b5761 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/echo-test/styles.scss
@@ -1,111 +1,18 @@
 @import "/imports/ui/stylesheets/variables/_all";
 @import "/imports/ui/components/modal/simple/styles";
 
-.header {
-  margin: 0;
-  padding: 0;
-  border: none;
-  line-height: 2rem;
-}
-
-.content {
-  display: flex;
-  justify-content: center;
-  padding: 0;
-  margin-top: 1.5rem;
-
-  .audioBtn:first-child {
+.button {
+  &:first-child {
     margin-right: 3rem;
 
     @include mq($small-only) {
       margin-right: 1rem;
     }
   }
-}
-
-//
-
-.overlay {
-  @extend .overlay;
-}
-
-.modal {
-  @extend .modal;
-  padding: 1.5rem;
-}
-
-.closeBtn {
-  right: 0;
-  top: 0;
-  position: absolute;
-  background-color: $color-white;
-  border: none;
-  padding: .75rem;
-
-
-  i {
-    color: $color-gray-light;
-  }
-
-  &:focus,
-  &:hover{
-    background-color: $color-white;
-    i{
-      color: $color-primary;
-    }
-  }
-}
-
-Button.audioBtn {
-  i{
-    color: #3c5764;
-  }
-}
-
-// Modifies the audio button icon colour
-Button.audioBtn span:first-child {
-  color: #1b3c4b;
-  background-color: #f1f8ff;
-  box-shadow: none;
-  border: 5px solid  #f1f8ff;
-}
-
-// When hovering over a button of class audioBtn, change the border colour of first span-child
-Button.audioBtn:hover span:first-child,
-Button.audioBtn:focus span:first-child {
-  border: 5px solid $color-primary;
-  background-color: #f1f8ff;
-}
 
-// Modifies the button label text
-Button.audioBtn span:last-child {
-  color: black;
-  font-size: 0.8rem;
-  font-weight: 600;
-}
-
-// Button.audioBtn:first-of-type {
-//   margin-right: 5%;
-// }
-//
-// Button.audioBtn:last-of-type {
-//   margin-left: 5%;
-// }
-
-.title {
-  text-align: center;
-  font-weight: 400;
-  font-size: 1.3rem;
-  white-space: normal;
-
-  @include mq($small-only) {
+  span:last-child {
+    color: black;
     font-size: 1rem;
-    padding: 0 1rem;
+    font-weight: 600;
   }
 }
-
-.audioNote {
-  color: $color-text;
-  display: inline-block;
-  font-size: 0.9rem;
-}
diff --git a/bigbluebutton-html5/imports/ui/components/button/styles.scss b/bigbluebutton-html5/imports/ui/components/button/styles.scss
index 31f7eb2152..e5d0bac3f9 100644
--- a/bigbluebutton-html5/imports/ui/components/button/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/button/styles.scss
@@ -96,6 +96,13 @@ $btn-jumbo-padding: $jumbo-padding-y $jumbo-padding-x;
       outline-offset: -2px;
     }
   }
+
+  &[aria-disabled="true"] {
+    cursor: not-allowed;
+    opacity: .65;
+    box-shadow: none;
+    pointer-events: none;
+  }
 }
 
 .label {
diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
index e9bc500d85..2b65fb14de 100644
--- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
+++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
@@ -5,17 +5,26 @@ import SIPBridge from '/imports/api/2.0/audio/client/bridge/sip';
 
 const USE_SIP = Meteor.settings.public.media.useSIPAudio;
 
-const toggleMuteMicrophone = (cb) => {
-  cb();
+const ERROR_CODES = {
+  REQUEST_TIMEOUT: {
+    message: 'Request Timeout',
+  },
+  CONNECTION_ERROR: {
+    message: 'Connection Error',
+  },
+  ERROR: {
+    message: 'An Error Occurred',
+  },
 };
 
-// const collection = new Mongo.Collection(null);
+CALL_STATES = {
+  STARTED: 'started',
+  ENDED: 'ended',
+  FAILED: 'failed',
+};
 
 class AudioManager {
   constructor() {
-
-
-    navigator.mediaDevices.enumerateDevices().then(kappa => console.log(kappa))
     this._inputDevice = {
       tracker: new Tracker.Dependency,
     };
@@ -23,7 +32,6 @@ class AudioManager {
     navigator.mediaDevices
       .getUserMedia({ audio: true })
       .then((stream) => {
-        console.log('kappa');
         const deviceLabel = stream.getAudioTracks()[0].label;
         navigator.mediaDevices.enumerateDevices().then(devices => {
           const device = devices.find(device => device.label === deviceLabel);
@@ -53,19 +61,13 @@ class AudioManager {
         set: (value) => {
           this[privateKey].value = value;
           this[privateKey].tracker.changed();
-          // console.log('set', privateKey, value);
-          // this.update(privateKey, value);
         },
         get: () => {
           this[privateKey].tracker.depend();
           return this[privateKey].value;
-          // console.log('get', privateKey, collection.findOne({})[privateKey]);
-          // return collection.findOne({})[privateKey];
         },
       });
     });
-
-    // return collection.insert(obj);
   }
 
   joinAudio(options = {}, callbacks = {}) {
@@ -83,10 +85,10 @@ class AudioManager {
     const callOptions = {
       isListenOnly,
       extension: isEchoTest ? '9196' : null,
-      inputStream: this.inputStream,
+      inputStream: isListenOnly ? this.createListenOnlyStream() : this.inputStream,
     }
 
-    console.log(this.inputStream);
+    console.log(callOptions.inputStream);
     console.log(this.inputDeviceId);
 
     return this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this));
@@ -99,7 +101,10 @@ class AudioManager {
 
   toggleMuteMicrophone() {
     console.log('toggleMuteMicrophone', this);
-    toggleMuteMicrophone(this.onToggleMicrophoneMute.bind(this));
+    makeCall('toggleSelfVoice').then((res) => {
+      console.log(res);
+      this.onToggleMicrophoneMute();
+    });
   }
 
   callbackToAudioBridge(message) {
@@ -123,6 +128,7 @@ class AudioManager {
 
   onAudioExit() {
     this.isConnected = false;
+    this.isConnecting = false;
 
     if (this.isListenOnly) {
       makeCall('listenOnlyToggle', false);
@@ -145,21 +151,29 @@ class AudioManager {
   //   collection.update(query, modifier);
   // }
 
-  callStateCallback({ status }) {
-    console.log('CALLSTATECALLBACK =====================', status);
+  callStateCallback(response) {
     return new Promise((resolve) => {
       const {
-        callStarted,
-        callEnded,
-        callDisconnected,
-      } = this.bridge.callStates;
+        STARTED,
+        ENDED,
+        FAILED,
+      } = CALL_STATES;
 
-      if (status === callStarted) {
+      const {
+        status,
+        error,
+      } = response;
+
+      console.log('CALLSTATECALLBACK =====================', response);
+
+      if (status === STARTED) {
         this.onAudioJoin();
-        resolve(callStarted);
-      } else if (status === callEnded) {
+        resolve(STARTED);
+      } else if (status === ENDED) {
+        console.log('ENDED');
         this.onAudioExit();
-      } else if (status === callDisconnected) {
+      } else if (status === FAILED) {
+        console.log('FAILED');
         this.onAudioExit();
       }
     })
@@ -175,6 +189,15 @@ class AudioManager {
     return this._userData;
   }
 
+  createListenOnlyStream() {
+    if (this.listenOnlyAudioContext) {
+      this.listenOnlyAudioContext.close();
+    }
+
+    this.listenOnlyAudioContext = new window.AudioContext;
+    return this.listenOnlyAudioContext.createMediaStreamDestination().stream;
+  }
+
   changeInputDevice(value) {
     if(this._inputDevice.audioContext) {
       this._inputDevice.audioContext.close().then(() => {
@@ -219,7 +242,6 @@ class AudioManager {
     this._inputDevice.tracker.depend();
     return this._inputDevice.id;
   }
-  // set outputDeviceId
 }
 
 const audioManager = new AudioManager();
diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json
index dd777910e3..d8fbe1df5a 100755
--- a/bigbluebutton-html5/private/locales/en.json
+++ b/bigbluebutton-html5/private/locales/en.json
@@ -212,6 +212,7 @@
     "app.audio.audioSettings.microphoneSourceLabel": "Microphone source",
     "app.audio.audioSettings.speakerSourceLabel": "Speaker source",
     "app.audio.audioSettings.microphoneStreamLabel": "Your audio stream volume",
+    "app.audio.audioSettings.retryLabel": "Retry",
     "app.audio.listenOnly.backLabel": "Back",
     "app.audio.listenOnly.closeLabel": "Close",
     "app.error.kicked": "You have been kicked out of the meeting",
-- 
GitLab