From cbb897b23fc2fcd470c218d95dc0aaaf33ad4a62 Mon Sep 17 00:00:00 2001
From: gcampes <gabrieldecampes@gmail.com>
Date: Fri, 29 Sep 2017 10:38:10 -0300
Subject: [PATCH] audio connects. WIP

---
 .../api/2.0/audio/client/bridge/base.js       |   6 +-
 .../api/2.0/audio/client/bridge/sip.js        | 104 +++---------
 .../api/2.0/audio/client/bridge/verto.js      |   4 +-
 .../imports/api/2.0/bbb/index.js              |  34 ++--
 .../audio/audio-controls/container.jsx        |   8 +-
 .../audio/audio-modal/component.jsx           |  58 ++++---
 .../audio/audio-modal/container.jsx           |  47 +++++
 .../components/audio/audio-modal/styles.scss  |   2 +-
 .../audio/audio-settings/component.jsx        |  52 +++++-
 .../audio/audio-settings/styles.scss          |  29 +++-
 .../components/audio/audio-test/styles.scss   |   2 -
 .../imports/ui/components/audio/container.jsx |   4 +-
 .../imports/ui/components/audio/service.js    |   6 +-
 .../ui/components/modal/simple/styles.scss    |   1 +
 .../ui/services/audio-manager/index.js        | 160 +++++++++++++-----
 15 files changed, 339 insertions(+), 178 deletions(-)
 create mode 100644 bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx

diff --git a/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/base.js b/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/base.js
index 58037d1e53..62e4a6d311 100644
--- a/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/base.js
+++ b/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/base.js
@@ -5,10 +5,6 @@ export default class BaseAudioBridge {
   exitAudio() {
   }
 
-  joinListenOnly() {
+  joinAudio() {
   }
-
-  joinMicrophone() {
-  }
-
 }
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 3401d72dba..8067a3fc75 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,95 +1,47 @@
-import { makeCall } from '/imports/ui/services/api';
-
 import BaseAudioBridge from './base';
 
-const APP_CONFIG = Meteor.settings.public.app;
-const MEDIA_CONFIG = Meteor.settings.public.media;
-
-let triedHangup = false;
+const RETRY_INTERVAL = Meteor.settings.public.media.WebRTCHangupRetryInterval;
 
 export default class SIPBridge extends BaseAudioBridge {
   constructor(userData) {
     super();
     this.userData = userData;
-  }
-
-  joinListenOnly(stunServers, turnServers, callbackFromManager) {
-    console.log('Join listen only from sip');
-    makeCall('listenOnlyToggle', true);
-    return this._joinVoiceCallSIP({ isListenOnly: true }, stunServers, turnServers, callbackFromManager);
-  }
-
-  joinMicrophone(stunServers, turnServers, callbackFromManager) {
-    console.log('Join microphone from sip');
-    return this._joinVoiceCallSIP({ isListenOnly: false }, stunServers, turnServers, callbackFromManager);
-  }
-
-  // Periodically check the status of the WebRTC call, when a call has been established attempt to
-  // hangup, retry if a call is in progress, send the leave voice conference message to BBB
-  exitAudio(isListenOnly, afterExitCall = () => { }) {
-    // To be called when the hangup is confirmed
-    const hangupCallback = function () {
-      console.log(`Exited Voice Conference, listenOnly=${isListenOnly}`);
-
-      // notify BBB-apps we are leaving the call if we are in listen only mode
-      if (isListenOnly) {
-        makeCall('listenOnlyToggle', false);
-      }
+    this.callStates = {
+      callStarted: 'started',
+      callEnded: 'ended',
+      callDisconnected: 'failed',
     };
+  }
 
-    // Checks periodically until a call is established so we can successfully
-    // end the call clean state
-    triedHangup = false;
-
-    // function to initiate call
-    const checkToHangupCall = ((context, afterExitCall = () => { }) => {
-      // if an attempt to hang up the call is made when the current session is not yet finished,
-      // the request has no effect keep track in the session if we haven't tried a hangup
-      if (window.getCallStatus() != null && !triedHangup) {
-        console.log('Attempting to hangup on WebRTC call');
-        window.webrtc_hangup(hangupCallback);
-
-        // we have hung up, prevent retries
-        triedHangup = true;
-
-        if (afterExitCall) {
-          afterExitCall(this, APP_CONFIG.listenOnly);
-        }
-      } else {
-        console.log('RETRYING hangup on WebRTC call in ' +
-          `${MEDIA_CONFIG.WebRTCHangupRetryInterval} ms`);
+  joinAudio({ isListenOnly, dialplan }, stunTurnServers, managerCallback) {
+    return new Promise((resolve) => {
+      const extension = dialplan || this.userData.voiceBridge;
 
-        // try again periodically
-        setTimeout(checkToHangupCall, MEDIA_CONFIG.WebRTCHangupRetryInterval);
+      const callback = (message) => {
+        managerCallback(message).then(() => resolve());
       }
-    })(this, afterExitCall);
 
-    return false;
+      this.callIntoConference(extension, callback, isListenOnly, stunTurnServers);
+    })
   }
 
-  // join the conference. If listen only send the request to the server
-  _joinVoiceCallSIP(options, stunServers, turnServers, callbackFromManager) {
-    return new Promise((resolve, reject) => {
-      const extension = this.userData.voiceBridge;
-      console.log(options);
-
-      // create voice call params
-      const joinCallback = function (message) {
-        console.log('Beginning WebRTC Conference Call', this.userData);
-      };
-
-      const stunsAndTurns = {
-        stun: stunServers,
-        turn: turnServers,
+  exitAudio() {
+    return new Promise((resolve) => {
+      let didTryHangup = false;
+
+      const attemptHangup = () => {
+        if (window.getCallStatus()) {
+          console.log('Attempting to hangup on WebRTC call');
+          didTryHangup = true;
+          window.webrtc_hangup(() => resolve());
+        } else {
+          console.log('RETRYING hangup on WebRTC call in ' +
+            `${RETRY_INTERVAL} ms`);
+          setTimeout(attemptHangup, RETRY_INTERVAL);
+        }
       };
 
-      const callback = (message) => {
-        console.log('CALLBACK FROM SIP BRIDGE');
-        return callbackFromManager(message).then(response => resolve(response))
-                       .catch(reason => reject(reason));
-      }
-
-      callIntoConference(extension, callback, options.isListenOnly, stunsAndTurns);
+      return attemptHangup();
     })
   }
 }
diff --git a/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/verto.js b/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/verto.js
index df5a59fd37..b3637c7494 100644
--- a/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/verto.js
+++ b/bigbluebutton-html5/imports/api/2.0/audio/client/bridge/verto.js
@@ -17,8 +17,8 @@ export default class VertoBridge extends BaseAudioBridge {
     window.vertoExitAudio();
   }
 
-  joinListenOnly() {
-    window.vertoJoinListenOnly(
+  joinAudio() {
+    window.vertoJoinMicrophone(
       'remote-media',
       this.voiceBridge,
       this.vertoUsername,
diff --git a/bigbluebutton-html5/imports/api/2.0/bbb/index.js b/bigbluebutton-html5/imports/api/2.0/bbb/index.js
index d8092d2188..2240365b03 100644
--- a/bigbluebutton-html5/imports/api/2.0/bbb/index.js
+++ b/bigbluebutton-html5/imports/api/2.0/bbb/index.js
@@ -4,10 +4,8 @@ import Users from '/imports/api/2.0/users';
 import Meetings from '/imports/api/2.0/meetings';
 
 class BBB {
-
   getUserId() {
-    const userID = Auth.userID;
-    return userID;
+    return Auth.userID;
   }
 
   getUsername() {
@@ -15,22 +13,32 @@ class BBB {
   }
 
   getExtension() {
-    const extension = Meetings.findOne().voiceProp.voiceConf;
-    return extension;
+    return Meetings.findOne().voiceProp.voiceConf;
+  }
+
+  getMyRole() {
+    return Users.findOne({ userId: this.getUserId() }).role
+  }
+
+  amIPresenter() {
+    return Users.findOne({ userId: this.getUserId() }).presenter
+  }
+
+  getMyAvatarURL() {
+    return Users.findOne({ userId: this.getUserId() }).avatar;
   }
 
   getMyUserInfo(callback) {
-    const result = {
+    return callback({
       myUserID: this.getUserId(),
       myUsername: this.getUsername(),
-      myInternalUserID: this.getUserId(),
-      myAvatarURL: null,
-      myRole: 'getMyRole',
-      amIPresenter: 'false',
+      myExternalUserID: this.getUserId(),
+      myAvatarURL: this.getMyAvatarURL(),
+      myRole: this.getMyRole(),
+      amIPresenter: this.amIPresenter(),
       voiceBridge: this.getExtension(),
       dialNumber: null,
-    };
-    return callback(result);
+    });
   }
 
   webRTCCallFailed(inEchoTest, errorcode, cause) {
@@ -47,7 +55,7 @@ class BBB {
 }
 
 export const initBBB = () => {
-  if (window.BBB == undefined) {
+  if (!window.BBB) {
     window.BBB = new BBB();
   }
 };
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 8990df798b..e647adf894 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/container.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
 import { createContainer } from 'meteor/react-meteor-data';
-import PropTypes from 'prop-types';
+// import PropTypes from 'prop-types';
 import { withModalMounter } from '/imports/ui/components/modal/service';
 import AudioControls from './component';
-import AudioModal from '../audio-modal/component';
+import AudioModalContainer from '../audio-modal/container';
 import Service from '../service';
 
 // const propTypes = {
@@ -16,8 +16,6 @@ import Service from '../service';
 
 const AudioControlsContainer = props => <AudioControls {...props} />;
 
-let didMountAutoJoin = false;
-
 export default withModalMounter(createContainer(({ mountModal }) => {
   // const APP_CONFIG = Meteor.settings.public.app;
   //
@@ -33,7 +31,7 @@ export default withModalMounter(createContainer(({ mountModal }) => {
     join: Service.isConnected(),
 
     handleToggleMuteMicrophone: () => Service.toggleMuteMicrophone(),
-    handleJoinAudio: () => mountModal(<AudioModal handleJoinListenOnly={() => {}} />),
+    handleJoinAudio: () => mountModal(<AudioModalContainer />),
     handleLeaveAudio: () => Service.exitAudio(),
   };
 }, AudioControlsContainer));
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 c7c6d8d840..ddf71059fa 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/component.jsx
@@ -3,7 +3,6 @@ import ModalBase from '/imports/ui/components/modal/base/component';
 import Button from '/imports/ui/components/button/component';
 import { defineMessages, injectIntl } from 'react-intl';
 import styles from './styles';
-import JoinAudio from '../join-audio/component';
 import AudioSettings from '../audio-settings/component';
 
 const intlMessages = defineMessages({
@@ -59,8 +58,12 @@ class AudioModal extends Component {
     };
 
     this.handleBack = this.handleBack.bind(this);
-    this.handleMicrophone = this.handleMicrophone.bind(this);
-    this.handleClose = this.handleClose.bind(this);
+    this.handleGoToAudioSettings = this.handleGoToAudioSettings.bind(this);
+    this.handleClose = props.closeModal;
+    this.handleJoinMicrophone = props.joinMicrophone;
+    this.handleJoinListenOnly = props.joinListenOnly;
+    this.joinEchoTest = props.joinEchoTest;
+    this.exitAudio = props.exitAudio;
   }
 
   handleBack() {
@@ -69,16 +72,12 @@ class AudioModal extends Component {
     });
   }
 
-  handleMicrophone() {
+  handleGoToAudioSettings() {
     this.setState({
       settings: true,
     });
   }
 
-  handleClose() {
-    console.log('handleClose');
-  }
-
   renderAudioOptions() {
     const {
       intl,
@@ -92,7 +91,7 @@ class AudioModal extends Component {
           icon={'unmute'}
           circle
           size={'jumbo'}
-          onClick={this.handleMicrophone}
+          onClick={this.handleGoToAudioSettings}
         />
         <Button
           className={styles.audioBtn}
@@ -100,14 +99,30 @@ class AudioModal extends Component {
           icon={'listen'}
           circle
           size={'jumbo'}
-          onClick={() => console.log('click listen only')}
+          onClick={this.handleJoinListenOnly}
         />
       </div>
     );
   }
 
   renderAudioSettings() {
-    return <AudioSettings handleBack={this.handleBack} />;
+    const {
+      isConnecting,
+      isConnected,
+      isEchoTest,
+    } = this.props;
+
+    return (
+      <AudioSettings
+        handleBack={this.handleBack}
+        handleJoin={this.handleJoinMicrophone}
+        joinEchoTest={this.joinEchoTest}
+        exitAudio={this.exitAudio}
+        isConnecting={isConnecting}
+        isConnected={isConnected}
+        isEchoTest={isEchoTest}
+      />
+    );
   }
 
   render() {
@@ -117,23 +132,24 @@ class AudioModal extends Component {
 
     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}
-          />
+          { !settings ?
+            <Button
+              className={styles.closeBtn}
+              label={intl.formatMessage(intlMessages.closeLabel)}
+              icon={'close'}
+              size={'md'}
+              hideLabel
+              onClick={this.handleClose}
+            /> : null }
         </header>
-
-      { settings ? this.renderAudioSettings() : this.renderAudioOptions() }
+        { settings ? this.renderAudioSettings() : this.renderAudioOptions() }
       </ModalBase>
     );
   }
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
new file mode 100644
index 0000000000..8e3cc88df6
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
@@ -0,0 +1,47 @@
+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;
+
+  return {
+    closeModal: () => mountModal(null),
+    joinMicrophone: () => {
+      Service.exitAudio().then(() => Service.joinMicrophone())
+                         .then(() => mountModal(null));
+    },
+    joinListenOnly: () => {
+      Service.joinMicrophone().then(a => mountModal(null))
+    },
+    joinEchoTest: () => Service.joinEchoTest(),
+    exitAudio: () => Service.exitAudio(),
+    isConnecting: Service.isConnecting(),
+    isConnected: Service.isConnected(),
+    isEchoTest: Service.isEchoTest(),
+  };
+}, AudioModalContainer));
+
+// AudioControlsContainer.propTypes = propTypes;
+// AudioControlsContainer.defaultProps = defaultProps;
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 4c9734b572..041b138d98 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss
@@ -51,7 +51,7 @@
   &:hover{
     background-color: $color-white;
     i{
-      color: $color-danger;
+      color: $color-primary;
     }
   }
 }
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 39be540dca..9c80ab849e 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/component.jsx
@@ -34,6 +34,10 @@ const intlMessages = defineMessages({
     id: 'app.audio.audioSettings.microphoneStreamLabel',
     description: 'Label for stream volume',
   },
+  enterSessionLabel: {
+    id: 'app.audio.enterSessionLabel',
+    description: 'enter session button label',
+  },
 });
 
 class AudioSettings extends React.Component {
@@ -43,8 +47,10 @@ class AudioSettings extends React.Component {
     this.chooseAudio = this.chooseAudio.bind(this);
     this.handleInputChange = this.handleInputChange.bind(this);
     this.handleOutputChange = this.handleOutputChange.bind(this);
-    this.handleClose = this.handleClose.bind(this);
     this.handleBack = props.handleBack;
+    this.handleJoin = props.handleJoin;
+    this.joinEchoTest = props.joinEchoTest;
+    this.exitAudio = props.exitAudio;
 
     this.state = {
       inputDeviceId: undefined,
@@ -52,6 +58,20 @@ 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);
   }
@@ -63,23 +83,26 @@ class AudioSettings extends React.Component {
     });
   }
 
-  handleOutputChange(deviceId) {
+  handleOutputChange(deviceId, device) {
+    console.log(device);
     console.log(`OUTPUT DEVICE CHANGED: ${deviceId}`);
     this.setState({
       outputDeviceId: deviceId,
     });
   }
 
-  handleClose() {
-    this.setState({ isOpen: false });
-    this.props.mountModal(null);
-  }
+  // handleClose() {
+  //   this.setState({ isOpen: false });
+  //   this.props.mountModal(null);
+  // }
 
   render() {
     const {
+      isConnecting,
       intl,
     } = this.props;
 
+    // return this.renderCalling();
     return (
       <div>
         <div className={styles.form}>
@@ -143,15 +166,26 @@ class AudioSettings extends React.Component {
             className={styles.backBtn}
             label={intl.formatMessage(intlMessages.backLabel)}
             size={'md'}
-            ghost={true}
             color={'primary'}
             onClick={this.handleBack}
+            disabled={isConnecting}
+            ghost
           />
-          <EnterAudioContainer isFullAudio />
+          <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>
         </div>
       </div>
-    );
+    )
   }
+
 }
 
 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 1687effc2d..c223a09435 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-settings/styles.scss
@@ -34,9 +34,9 @@
 
 .labelSmall {
   color: black;
-  font-size: 0.7rem;
+  font-size: 0.85rem;
   font-weight: 600;
-  margin-bottom: 0.3rem;
+  margin-bottom: 0.5rem;
 }
 
 .formElement {
@@ -92,3 +92,28 @@
   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-test/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss
index 038a84ab9e..d2800c86f1 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-test/styles.scss
@@ -1,8 +1,6 @@
 @import "/imports/ui/stylesheets/variables/_all";
 
 .testAudioBtn {
-  border: none;
-  padding-left: 0;
   background-color: transparent;
   color: $color-primary;
   font-weight: normal;
diff --git a/bigbluebutton-html5/imports/ui/components/audio/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/container.jsx
index 845ac98ee0..e6fc03db8f 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/container.jsx
@@ -4,7 +4,7 @@ import { withModalMounter } from '/imports/ui/components/modal/service';
 import PropTypes from 'prop-types';
 import Service from './service';
 import Audio from './component';
-import AudioModal from './audio-modal/component';
+import AudioModalContainer from './audio-modal/container';
 
 const propTypes = {
   children: PropTypes.element,
@@ -31,7 +31,7 @@ export default withModalMounter(createContainer(({ mountModal }) => {
     init: () => {
       Service.init();
       if (!autoJoinAudio || didMountAutoJoin) return;
-      mountModal(<AudioModal handleJoinListenOnly={Service.joinListenOnly} />);
+      mountModal(<AudioModalContainer />);
       didMountAutoJoin = true;
     },
   };
diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js
index a4144bc7e7..e657d961ef 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/service.js
+++ b/bigbluebutton-html5/imports/ui/components/audio/service.js
@@ -6,6 +6,7 @@ import Meetings from '/imports/api/2.0/meetings';
 const init = () => {
   console.log('Running audio service init.');
   const userId = Auth.userID;
+  const sessionToken = Auth.sessionToken;
   const User = Users.findOne({ userId });
   const username = User.name;
   const Meeting = Meetings.findOne({ meetingId: User.meetingId });
@@ -16,6 +17,7 @@ const init = () => {
 
   const userData = {
     userId,
+    sessionToken,
     username,
     voiceBridge,
     microphoneLockEnforced,
@@ -27,8 +29,9 @@ const init = () => {
 export default {
   init,
   exitAudio: () => AudioManager.exitAudio(),
-  joinListenOnly: () => AudioManager.joinAudio(true),
+  joinListenOnly: () => AudioManager.joinAudio({ isListenOnly: true }),
   joinMicrophone: () => AudioManager.joinAudio(),
+  joinEchoTest: () => AudioManager.joinAudio({ isEchoTest: true }),
   toggleMuteMicrophone: () => AudioManager.toggleMuteMicrophone(),
   isConnected: () => AudioManager.isConnected,
   isMuted: () => AudioManager.isMuted,
@@ -36,4 +39,5 @@ export default {
   isListenOnly: () => AudioManager.isListenOnly,
   inputDeviceId: () => AudioManager.inputDeviceId,
   outputDeviceId: () => AudioManager.outputDeviceId,
+  isEchoTest: () => AudioManager.isEchoTest,
 }
diff --git a/bigbluebutton-html5/imports/ui/components/modal/simple/styles.scss b/bigbluebutton-html5/imports/ui/components/modal/simple/styles.scss
index baa112c889..b235b549c3 100644
--- a/bigbluebutton-html5/imports/ui/components/modal/simple/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/modal/simple/styles.scss
@@ -5,6 +5,7 @@
   display: flex;
   flex-direction: column;
   padding: ($line-height-computed / 2) $line-height-computed;
+  box-shadow : 0px 0px 15px rgba(0, 0, 0, 0.5);
 }
 
 .content {
diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
index 69eeb03288..1388fa582b 100644
--- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
+++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
@@ -1,22 +1,13 @@
 import { Tracker } from 'meteor/tracker';
+import { makeCall } from '/imports/ui/services/api';
+import VertoBridge from '/imports/api/2.0/audio/client/bridge/verto';
+import SIPBridge from '/imports/api/2.0/audio/client/bridge/sip';
 
-const joinAudioMicrophone = (cb) => {
-  this.setTimeout(() => {
-    console.log('joining microphone mock...');
-    cb();
-  }, 1000)
-}
-
-const exitAudio = (cb) => {
-  setTimeout(() => {
-    console.log('exit audio mock...');
-    cb();
-  }, 1000);
-}
+const USE_SIP = Meteor.settings.public.media.useSIPAudio;
 
 const toggleMuteMicrophone = (cb) => {
   cb();
-}
+};
 
 // const collection = new Mongo.Collection(null);
 
@@ -26,7 +17,8 @@ class AudioManager {
       isMuted: false,
       isConnected: false,
       isConnecting: false,
-      isListenOnly: null,
+      isListenOnly: false,
+      isEchoTest: false,
       error: null,
       inputDeviceId: null,
       outputDeviceId: null,
@@ -34,43 +26,61 @@ class AudioManager {
   }
 
   defineProperties(obj) {
-    Object.keys(obj).forEach(key => {
-      let originalKey = `_${key}`;
-      this[originalKey] = {
+    Object.keys(obj).forEach((key) => {
+      const privateKey = `_${key}`;
+      this[privateKey] = {
         value: obj[key],
-        tracker: new Tracker.Dependency
-      }
+        tracker: new Tracker.Dependency,
+      };
 
       Object.defineProperty(this, key, {
         set: (value) => {
-          this[originalKey].value = value;
-          this[originalKey].tracker.changed();
-          // console.log('set', originalKey, value);
-          // this.update(originalKey, value);
+          this[privateKey].value = value;
+          this[privateKey].tracker.changed();
+          // console.log('set', privateKey, value);
+          // this.update(privateKey, value);
         },
         get: () => {
-          this[originalKey].tracker.depend();
-          return this[originalKey].value;
-          // console.log('get', originalKey, collection.findOne({})[originalKey]);
-          // return collection.findOne({})[originalKey];
-        }
-      })
-    })
+          this[privateKey].tracker.depend();
+          return this[privateKey].value;
+          // console.log('get', privateKey, collection.findOne({})[privateKey]);
+          // return collection.findOne({})[privateKey];
+        },
+      });
+    });
 
     // return collection.insert(obj);
   }
 
-  joinAudio(isListenOnly) {
+  joinAudio(options = {}, callbacks = {}) {
+    const {
+      isListenOnly,
+      isEchoTest,
+    } = options;
+
     console.log('joinAudio', this, isListenOnly);
     this.isConnecting = true;
-    this.isListenOnly = isListenOnly
-
-    joinAudioMicrophone(this.onAudioJoin.bind(this));
+    this.isListenOnly = isListenOnly;
+    this.isEchoTest = isEchoTest;
+    this.callbacks = callbacks;
+
+    const callOptions = {
+      isListenOnly,
+      dialplan: isEchoTest ? '9196' : null,
+    }
+
+    return this.fetchStunTurn().then((stunTurnServers) =>
+      this.bridge.joinAudio(callOptions,
+                            stunTurnServers,
+                            this.callStateCallback.bind(this))
+    ).catch((error) => {
+      console.error('error', error)
+    })
   }
 
   exitAudio() {
     console.log('exitAudio', this);
-    exitAudio(this.onAudioExit.bind(this));
+    return this.bridge.exitAudio()
   }
 
   toggleMuteMicrophone() {
@@ -78,18 +88,34 @@ class AudioManager {
     toggleMuteMicrophone(this.onToggleMicrophoneMute.bind(this));
   }
 
+  callbackToAudioBridge(message) {
+    console.log('This is the Manager Callback', message);
+  }
+
   //----------------------------
 
   onAudioJoin() {
-    this.isConnected = true;
+    if (!this.isEchoTest) {
+      this.isConnected = true;
+    }
     this.isConnecting = false;
+
+    if (this.isListenOnly) {
+      makeCall('listenOnlyToggle', true);
+    }
+
     console.log('onAudioJoin', this);
   }
 
   onAudioExit() {
     this.isConnected = false;
-    this.isListenOnly = null;
-    this.isMuted = false;
+
+    if (this.isListenOnly) {
+      makeCall('listenOnlyToggle', false);
+    } else if (this.isEchoTest) {
+      this.isEchoTest = false;
+    }
+
     console.log('onAudioExit', this);
   }
 
@@ -104,6 +130,62 @@ class AudioManager {
   //   const modifier = { $set: { [key]: value }};
   //   collection.update(query, modifier);
   // }
+
+  callStateCallback({ status }) {
+    console.log('CALLSTATECALLBACK =====================', status);
+    return new Promise((resolve) => {
+      const {
+        callStarted,
+        callEnded,
+        callDisconnected,
+      } = this.bridge.callStates;
+
+      if (status === callStarted) {
+        this.onAudioJoin();
+        resolve(callStarted);
+      } else if (status === callEnded) {
+        this.onAudioExit();
+      } else if (status === callDisconnected) {
+        this.onAudioExit();
+      }
+    })
+  }
+
+  fetchStunTurn() {
+    return new Promise(async (resolve, reject) => {
+      const url = `/bigbluebutton/api/stuns?sessionToken=${this.userData.sessionToken}`;
+
+      let response = await fetch(url)
+        .then(response => response.json())
+        .then(({ response, stunServers, turnServers}) => {
+          console.log(response, stunServers, turnServers);
+          return new Promise((resolve) => {
+            if (response) {
+              resolve({ error: 404, stun: [], turn: [] });
+            }
+            console.log('krappa');
+            resolve({
+              stun: stunServers.map(server => server.url),
+              turn: turnServers.map(server => server.url),
+            });
+          });
+        });
+
+        console.log(response);
+      if(response.error) return reject(`Could not fetch the stuns/turns servers!`);
+      resolve(response);
+    })
+  }
+
+  set userData(value) {
+    console.log('set user data');
+    this._userData = value;
+    this.bridge = USE_SIP ? new SIPBridge(value) : new VertoBridge(value);
+  }
+
+  get userData() {
+    return this._userData;
+  }
 }
 
 const audioManager = new AudioManager();
-- 
GitLab