Merge pull request #3834 from antobinary/audio-restructuring
[HTML5] Audio restructuring part 1
This commit is contained in:
commit
a9f2a18475
14
bigbluebutton-html5/imports/api/audio/client/bridge/base.js
Normal file
14
bigbluebutton-html5/imports/api/audio/client/bridge/base.js
Normal file
@ -0,0 +1,14 @@
|
||||
export default class BaseAudioBridge {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
exitAudio () {
|
||||
}
|
||||
|
||||
joinListenOnly() {
|
||||
}
|
||||
|
||||
joinMicrophone() {
|
||||
}
|
||||
|
||||
}
|
@ -1,32 +1,30 @@
|
||||
// TODO: This file should be a `service.js` somewhere in the /ui folder
|
||||
import Users from '/imports/api/users';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import {callServer} from '/imports/ui/services/api';
|
||||
import {vertoExitAudio, vertoJoinListenOnly, vertoJoinMicrophone} from '/imports/api/verto';
|
||||
import BaseAudioBridge from './base';
|
||||
|
||||
import { callServer } from '/imports/ui/services/api';
|
||||
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const MEDIA_CONFIG = Meteor.settings.public.media;
|
||||
|
||||
let triedHangup = false;
|
||||
|
||||
function getVoiceBridge() {
|
||||
return Meetings.findOne({}).voiceConf;
|
||||
}
|
||||
export default class SIPBridge extends BaseAudioBridge {
|
||||
constructor(userData) {
|
||||
super();
|
||||
this.userData = userData;
|
||||
}
|
||||
|
||||
function amIListenOnly() {
|
||||
const uid = Auth.userID;
|
||||
return Users.findOne({ userId: uid }).user.listenOnly;
|
||||
}
|
||||
joinListenOnly() {
|
||||
callServer('listenOnlyToggle', true);
|
||||
this._joinVoiceCallSIP({ isListenOnly: true });
|
||||
}
|
||||
|
||||
// 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
|
||||
joinMicrophone() {
|
||||
this._joinVoiceCallSIP({ isListenOnly: false });
|
||||
}
|
||||
|
||||
function exitAudio(afterExitCall = () => {}) {
|
||||
if (!MEDIA_CONFIG.useSIPAudio) {
|
||||
vertoExitAudio();
|
||||
return;
|
||||
} else {
|
||||
// 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(afterExitCall = () => {}) {
|
||||
// To be called when the hangup is initiated
|
||||
const hangupCallback = function () {
|
||||
console.log('Exiting Voice Conference');
|
||||
@ -46,7 +44,7 @@ function exitAudio(afterExitCall = () => {}) {
|
||||
console.log('Attempting to hangup on WebRTC call');
|
||||
|
||||
// notify BBB-apps we are leaving the call call if we are listen only
|
||||
if (amIListenOnly()) {
|
||||
if (userData.listenOnly) {
|
||||
callServer('listenOnlyToggle', false);
|
||||
}
|
||||
|
||||
@ -71,27 +69,29 @@ function exitAudio(afterExitCall = () => {}) {
|
||||
(this, afterExitCall);
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// join the conference. If listen only send the request to the server
|
||||
function joinVoiceCallSIP(options) {
|
||||
const extension = getVoiceBridge();
|
||||
console.log(options);
|
||||
if (MEDIA_CONFIG.useSIPAudio) {
|
||||
// join the conference. If listen only send the request to the server
|
||||
_joinVoiceCallSIP(options) {
|
||||
const extension = this.userData.voiceBridge;
|
||||
console.log(options);
|
||||
|
||||
// create voice call params
|
||||
const joinCallback = function (message) {
|
||||
console.log('Beginning WebRTC Conference Call');
|
||||
};
|
||||
|
||||
const {
|
||||
userId,
|
||||
username,
|
||||
} = this.userData;
|
||||
|
||||
window.BBB = {};
|
||||
window.BBB.getMyUserInfo = function (callback) {
|
||||
const uid = Auth.userID;
|
||||
const result = {
|
||||
myUserID: uid,
|
||||
myUsername: Users.findOne({ userId: uid }).user.name,
|
||||
myInternalUserID: uid,
|
||||
myUserID: userId,
|
||||
myUsername: username,
|
||||
myInternalUserID: userId,
|
||||
myAvatarURL: null,
|
||||
myRole: 'getMyRole',
|
||||
amIPresenter: 'false',
|
||||
@ -101,51 +101,30 @@ function joinVoiceCallSIP(options) {
|
||||
return callback(result);
|
||||
};
|
||||
|
||||
const m = Meetings.findOne();
|
||||
const st = {
|
||||
stun: m.stuns,
|
||||
turn: m.turns,
|
||||
const stunsAndTurns = {
|
||||
stun: this.userData.stuns,
|
||||
turn: this.userData.turns,
|
||||
};
|
||||
|
||||
callIntoConference(extension, function (audio) {
|
||||
switch (audio.status) {
|
||||
case 'failed':
|
||||
let audioFailed = new CustomEvent('bbb.webrtc.failed', {
|
||||
status: 'Failed' });
|
||||
status: 'Failed', });
|
||||
window.dispatchEvent(audioFailed);
|
||||
break;
|
||||
case 'mediafail':
|
||||
let mediaFailed = new CustomEvent('bbb.webrtc.mediaFailed', {
|
||||
status: 'MediaFailed' });
|
||||
status: 'MediaFailed', });
|
||||
window.dispatchEvent(mediaFailed);
|
||||
break;
|
||||
case 'mediasuccess':
|
||||
case 'started':
|
||||
let connected = new CustomEvent('bbb.webrtc.connected', {
|
||||
status: 'started' });
|
||||
status: 'started', });
|
||||
window.dispatchEvent(connected);
|
||||
break;
|
||||
}
|
||||
}, options.isListenOnly, st);
|
||||
return;
|
||||
}, options.isListenOnly, stunsAndTurns);
|
||||
}
|
||||
}
|
||||
|
||||
function joinListenOnly() {
|
||||
callServer('listenOnlyToggle', true);
|
||||
if (MEDIA_CONFIG.useSIPAudio) {
|
||||
joinVoiceCallSIP({ isListenOnly: true });
|
||||
} else {
|
||||
vertoJoinListenOnly();
|
||||
}
|
||||
}
|
||||
|
||||
function joinMicrophone() {
|
||||
if (MEDIA_CONFIG.useSIPAudio) {
|
||||
joinVoiceCallSIP({ isListenOnly: false });
|
||||
} else {
|
||||
vertoJoinMicrophone();
|
||||
}
|
||||
}
|
||||
|
||||
export { joinListenOnly, joinMicrophone, exitAudio, getVoiceBridge, };
|
38
bigbluebutton-html5/imports/api/audio/client/bridge/verto.js
Normal file
38
bigbluebutton-html5/imports/api/audio/client/bridge/verto.js
Normal file
@ -0,0 +1,38 @@
|
||||
import { getVoiceBridge } from '/imports/ui/components/audio/service';
|
||||
import BaseAudioBridge from './base';
|
||||
|
||||
export default class VertoBridge extends BaseAudioBridge {
|
||||
constructor(userData) {
|
||||
super();
|
||||
const {
|
||||
username,
|
||||
voiceBridge,
|
||||
} = userData;
|
||||
|
||||
this.voiceBridge = voiceBridge;
|
||||
this.vertoUsername = 'FreeSWITCH User - ' + encodeURIComponent(username);
|
||||
}
|
||||
|
||||
exitAudio() {
|
||||
window.vertoExitAudio();
|
||||
}
|
||||
|
||||
joinListenOnly() {
|
||||
window.vertoJoinListenOnly(
|
||||
'remote-media',
|
||||
this.voiceBridge,
|
||||
this.vertoUsername,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
joinMicrophone() {
|
||||
window.vertoJoinMicrophone(
|
||||
'remote-media',
|
||||
this.voiceBridge,
|
||||
this.vertoUsername,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import { callServer } from '/imports/ui/services/api';
|
||||
import BaseAudioBridge from '../bridge/base';
|
||||
import VertoBridge from '../bridge/verto';
|
||||
import SIPBridge from '../bridge/sip';
|
||||
|
||||
// manages audio calls and audio bridges
|
||||
export default class AudioManager {
|
||||
constructor(userData) {
|
||||
const MEDIA_CONFIG = Meteor.settings.public.media;
|
||||
const audioBridge = MEDIA_CONFIG.useSIPAudio ? new SIPBridge(userData) : new VertoBridge(userData);
|
||||
if (!(audioBridge instanceof BaseAudioBridge)) {
|
||||
throw 'Audio Bridge not compatible';
|
||||
}
|
||||
|
||||
this.bridge = audioBridge;
|
||||
}
|
||||
|
||||
exitAudio () {
|
||||
this.bridge.exitAudio();
|
||||
}
|
||||
|
||||
joinAudio(listenOnly) {
|
||||
if (listenOnly) {
|
||||
callServer('listenOnlyToggle', true);
|
||||
this.bridge.joinListenOnly();
|
||||
} else {
|
||||
this.bridge.joinMicrophone();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import VertoBridge from './verto';
|
||||
|
||||
const deskshareBridge = new VertoBridge();
|
||||
|
||||
export default deskshareBridge;
|
@ -0,0 +1,28 @@
|
||||
import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { getVoiceBridge } from '/imports/ui/components/audio/service';
|
||||
|
||||
// TODO pass info in constructor instead of importing ^^
|
||||
const createVertoUserName = () => {
|
||||
const userId = Auth.userID;
|
||||
const uName = Users.findOne({ userId }).user.name;
|
||||
return 'FreeSWITCH User - ' + encodeURIComponent(uName);
|
||||
};
|
||||
|
||||
export default class VertoDeskshareBridge {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
vertoWatchVideo() {
|
||||
window.vertoWatchVideo(
|
||||
'deskshareVideo',
|
||||
getVoiceBridge(),
|
||||
createVertoUserName(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO add vertoExitVideo
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import {getInStorage} from '/imports/ui/components/app/service';
|
||||
import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { getVoiceBridge } from '/imports/api/phone';
|
||||
|
||||
function createVertoUserName() {
|
||||
const uid = Auth.userID;
|
||||
const uName = Users.findOne({ userId: uid }).user.name;
|
||||
const conferenceUsername = encodeURIComponent(uName);
|
||||
return conferenceUsername;
|
||||
}
|
||||
|
||||
function vertoExitAudio() {
|
||||
window.vertoExitAudio();
|
||||
}
|
||||
|
||||
function vertoJoinListenOnly() {
|
||||
window.vertoJoinListenOnly(
|
||||
'remote-media',
|
||||
getVoiceBridge(),
|
||||
createVertoUserName(),
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
function vertoJoinMicrophone() {
|
||||
window.vertoJoinMicrophone(
|
||||
'remote-media',
|
||||
getVoiceBridge(),
|
||||
createVertoUserName(),
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
function vertoWatchVideo() {
|
||||
window.vertoWatchVideo(
|
||||
'deskshareVideo',
|
||||
getVoiceBridge(),
|
||||
createVertoUserName(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
vertoJoinListenOnly,
|
||||
vertoJoinMicrophone,
|
||||
vertoWatchVideo,
|
||||
vertoExitAudio,
|
||||
};
|
||||
|
@ -1,11 +1,9 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import React, { Component } from 'react';
|
||||
import styles from './styles.scss';
|
||||
import EmojiContainer from './emoji-menu/container';
|
||||
import ActionsDropdown from './actions-dropdown/component';
|
||||
import JoinAudioOptionsContainer from './audio-menu/container';
|
||||
import JoinAudioOptionsContainer from '../audio/audio-menu/container';
|
||||
import MuteAudioContainer from './mute-button/container';
|
||||
import JoinVideo from './video-button/component';
|
||||
|
||||
export default class ActionsBar extends Component {
|
||||
constructor(props) {
|
||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import ActionsBar from './component';
|
||||
import Service from './service';
|
||||
import { exitAudio, handleJoinAudio } from '../audio/service';
|
||||
|
||||
class ActionsBarContainer extends Component {
|
||||
constructor(props) {
|
||||
@ -20,8 +21,8 @@ class ActionsBarContainer extends Component {
|
||||
|
||||
export default createContainer(() => {
|
||||
const isPresenter = Service.isUserPresenter();
|
||||
const handleExitAudio = () => Service.handleExitAudio();
|
||||
const handleOpenJoinAudio = () => Service.handleJoinAudio();
|
||||
const handleExitAudio = () => exitAudio();
|
||||
const handleOpenJoinAudio = () => handleJoinAudio();
|
||||
|
||||
return {
|
||||
isUserPresenter: isPresenter,
|
||||
|
@ -1,10 +1,6 @@
|
||||
import React from 'react';
|
||||
import AuthSingleton from '/imports/ui/services/auth/index.js';
|
||||
import Users from '/imports/api/users';
|
||||
import { joinListenOnly } from '/imports/api/phone';
|
||||
import { showModal } from '/imports/ui/components/app/service';
|
||||
import { exitAudio } from '/imports/api/phone';
|
||||
import Audio from '/imports/ui/components/audio-modal/component';
|
||||
|
||||
let isUserPresenter = () => {
|
||||
|
||||
@ -14,17 +10,6 @@ let isUserPresenter = () => {
|
||||
}).user.presenter;
|
||||
};
|
||||
|
||||
const handleExitAudio = () => {
|
||||
return exitAudio();
|
||||
}
|
||||
|
||||
const handleJoinAudio = () => {
|
||||
const handleJoinListenOnly = () => joinListenOnly();
|
||||
return showModal(<Audio handleJoinListenOnly={handleJoinListenOnly} />);
|
||||
}
|
||||
|
||||
export default {
|
||||
isUserPresenter,
|
||||
handleJoinAudio,
|
||||
handleExitAudio,
|
||||
};
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import NotificationsBarContainer from '../notifications-bar/container';
|
||||
import AudioNotificationContainer from '../audio-notification/container';
|
||||
import AudioNotificationContainer from '../audio/audio-notification/container';
|
||||
import AudioContainer from '../audio/container';
|
||||
import ChatNotificationContainer from '../chat/notification/container';
|
||||
|
||||
import Button from '../button/component';
|
||||
import styles from './styles';
|
||||
import cx from 'classnames';
|
||||
|
||||
@ -16,15 +16,15 @@ const intlMessages = defineMessages({
|
||||
},
|
||||
chatLabel: {
|
||||
id: 'app.chat.Label',
|
||||
description: 'Aria-label for Chat Section'
|
||||
description: 'Aria-label for Chat Section',
|
||||
},
|
||||
mediaLabel: {
|
||||
id: 'app.media.Label',
|
||||
description: 'Aria-label for Media Section'
|
||||
description: 'Aria-label for Media Section',
|
||||
},
|
||||
actionsbarLabel: {
|
||||
id: 'app.actionsBar.Label',
|
||||
description: 'Aria-label for ActionsBar Section'
|
||||
description: 'Aria-label for ActionsBar Section',
|
||||
},
|
||||
});
|
||||
|
||||
@ -165,7 +165,7 @@ class App extends Component {
|
||||
{this.renderSidebar()}
|
||||
</section>
|
||||
{modal}
|
||||
<audio id="remote-media" autoPlay="autoplay"></audio>
|
||||
<AudioContainer />
|
||||
<ChatNotificationContainer currentChatID={params.chatID} />
|
||||
</main>
|
||||
);
|
||||
|
@ -5,7 +5,6 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import {
|
||||
getModal,
|
||||
showModal,
|
||||
getFontSize,
|
||||
getCaptionsStatus,
|
||||
} from './service';
|
||||
@ -18,7 +17,6 @@ import App from './component';
|
||||
import NavBarContainer from '../nav-bar/container';
|
||||
import ActionsBarContainer from '../actions-bar/container';
|
||||
import MediaContainer from '../media/container';
|
||||
import AudioModalContainer from '../audio-modal/container';
|
||||
import ClosedCaptionsContainer from '/imports/ui/components/closed-captions/container';
|
||||
|
||||
const defaultProps = {
|
||||
@ -48,12 +46,8 @@ class AppContainer extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
|
||||
const init = () => {
|
||||
if (APP_CONFIG.autoJoinAudio) {
|
||||
showModal(<AudioModalContainer />);
|
||||
}
|
||||
// TODO
|
||||
};
|
||||
|
||||
export default withRouter(injectIntl(createContainer(({ router, intl, baseControls }) => {
|
||||
|
@ -1,91 +0,0 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import styles from '../styles.scss';
|
||||
|
||||
import DeviceSelector from '/imports/ui/components/audio/device-selector/component';
|
||||
import AudioTestContainer from '/imports/ui/components/audio-test/container';
|
||||
import EnterAudioContainer from '/imports/ui/components/enter-audio/container';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
backLabel: {
|
||||
id: 'app.audio.listenOnly.backLabel',
|
||||
},
|
||||
closeLabel: {
|
||||
id: 'app.audio.listenOnly.closeLabel',
|
||||
},
|
||||
});
|
||||
|
||||
class ListenOnly extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.chooseAudio = this.chooseAudio.bind(this);
|
||||
this.handleOutputChange = this.handleOutputChange.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
|
||||
this.state = {
|
||||
inputDeviceId: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
chooseAudio() {
|
||||
this.props.changeMenu(this.props.JOIN_AUDIO);
|
||||
}
|
||||
|
||||
handleOutputChange(deviceId) {
|
||||
console.log(`OUTPUT DEVICE CHANGED: ${deviceId}`);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.setState({ isOpen: false });
|
||||
clearModal();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.center}>
|
||||
<Button className={styles.backBtn}
|
||||
label={intl.formatMessage(intlMessages.backLabel)}
|
||||
icon={'left_arrow'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
ghost={true}
|
||||
onClick={this.chooseAudio}
|
||||
/>
|
||||
<Button className={styles.closeBtn}
|
||||
label={intl.formatMessage(intlMessages.closeLabel)}
|
||||
icon={'close'}
|
||||
size={'lg'}
|
||||
circle={true}
|
||||
hideLabel={true}
|
||||
onClick={this.handleClose}
|
||||
/>
|
||||
<div>
|
||||
Choose your listen only settings
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.containerLeftHalfContent}>
|
||||
<DeviceSelector
|
||||
className={styles.item}
|
||||
kind="audiooutput"
|
||||
onChange={this.handleOutputChange} />
|
||||
<AudioTestContainer />
|
||||
</div>
|
||||
<div className={styles.containerRightHalfContent}>
|
||||
<EnterAudioContainer isFullAudio={false}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default injectIntl(ListenOnly);
|
@ -7,7 +7,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
const intlMessages = defineMessages({
|
||||
joinAudio: {
|
||||
id: 'app.audio.joinAudio',
|
||||
description: 'Join audio button label'
|
||||
description: 'Join audio button label',
|
||||
},
|
||||
leaveAudio: {
|
||||
id: 'app.audio.leaveAudio',
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import Users from '/imports/api/users/index';
|
||||
import Auth from '/imports/ui/services/auth/index';
|
||||
import JoinAudioOptions from './component';
|
@ -1,14 +1,8 @@
|
||||
import React from 'react';
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import ModalBase from '../modal/base/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import classNames from 'classnames';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ModalBase from '../../modal/base/component';
|
||||
import styles from './styles.scss';
|
||||
import JoinAudio from './join-audio/component';
|
||||
import ListenOnly from './listen-only/component';
|
||||
import AudioSettings from './audio-settings/component';
|
||||
import JoinAudio from '../join-audio/component';
|
||||
import AudioSettings from '../audio-settings/component';
|
||||
|
||||
export default class Audio extends React.Component {
|
||||
constructor(props) {
|
||||
@ -16,7 +10,6 @@ export default class Audio extends React.Component {
|
||||
|
||||
this.JOIN_AUDIO = 0;
|
||||
this.AUDIO_SETTINGS = 1;
|
||||
this.LISTEN_ONLY = 2;
|
||||
|
||||
this.submenus = [];
|
||||
}
|
||||
@ -28,7 +21,6 @@ export default class Audio extends React.Component {
|
||||
this.setState({ activeSubmenu: 0 });
|
||||
this.submenus.push({ componentName: JoinAudio, });
|
||||
this.submenus.push({ componentName: AudioSettings, });
|
||||
this.submenus.push({ componentName: ListenOnly, });
|
||||
}
|
||||
|
||||
changeMenu(i) {
|
@ -1,7 +1,7 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import Audio from './component';
|
||||
import { joinListenOnly } from '/imports/api/phone';
|
||||
import { joinListenOnly } from '../service';
|
||||
|
||||
export default class AudioModalContainer extends Component {
|
||||
constructor(props) {
|
@ -1,4 +1,4 @@
|
||||
@import "../../stylesheets/variables/_all";
|
||||
@import "../../../stylesheets/variables/_all";
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
@ -41,7 +41,7 @@ Button.audioBtn:hover span:first-child {
|
||||
Button.audioBtn span:last-child {
|
||||
color: black;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
Button.audioBtn:first-of-type {
|
@ -42,7 +42,7 @@ class AudioNotification extends Component {
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
if (!color || !message ){
|
||||
if (!color || !message) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
@ -1,10 +1,7 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import AudioNotification from './component';
|
||||
import styles from './styles.scss';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
audioFailed: {
|
||||
@ -26,7 +23,7 @@ class AudioNotificationContainer extends Component {
|
||||
|
||||
this.state = {
|
||||
status: null,
|
||||
}
|
||||
};
|
||||
|
||||
this.handleAudioFailure = this.handleAudioFailure.bind(this);
|
||||
this.handleMediaFailure = this.handleMediaFailure.bind(this);
|
||||
@ -34,42 +31,42 @@ class AudioNotificationContainer extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("bbb.webrtc.failed", this.handleAudioFailure);
|
||||
window.addEventListener("bbb.webrtc.mediaFailed", this.handleMediaFailure);
|
||||
window.addEventListener("bbb.webrtc.connected", this.handleClose);
|
||||
window.addEventListener('bbb.webrtc.failed', this.handleAudioFailure);
|
||||
window.addEventListener('bbb.webrtc.mediaFailed', this.handleMediaFailure);
|
||||
window.addEventListener('bbb.webrtc.connected', this.handleClose);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("bbb.webrtc.failed", this.handleAudioFailure);
|
||||
window.removeEventListener("bbb.webrtc.mediaFailed", this.handleMediaFailure);
|
||||
window.removeEventListener("bbb.webrtc.connected", this.handleClose);
|
||||
window.removeEventListener('bbb.webrtc.failed', this.handleAudioFailure);
|
||||
window.removeEventListener('bbb.webrtc.mediaFailed', this.handleMediaFailure);
|
||||
window.removeEventListener('bbb.webrtc.connected', this.handleClose);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.color = null;
|
||||
this.message = null;
|
||||
this.setState({status: null});
|
||||
this.setState({ status: null });
|
||||
}
|
||||
|
||||
handleAudioFailure() {
|
||||
this.message = this.props.audioFailure;
|
||||
this.setState({status: 'failed'});
|
||||
this.setState({ status: 'failed' });
|
||||
}
|
||||
|
||||
handleMediaFailure() {
|
||||
this.message = this.props.mediaFailure;
|
||||
this.setState({status: 'failed'});
|
||||
this.setState({ status: 'failed' });
|
||||
}
|
||||
|
||||
render() {
|
||||
const handleClose = this.handleClose;
|
||||
this.color = 'danger';
|
||||
|
||||
return(
|
||||
return (
|
||||
<AudioNotification
|
||||
color={this.color}
|
||||
message={this.message}
|
||||
handleClose={this.handleClose}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
@import "../../stylesheets/variables/_all";
|
||||
@import "../../../stylesheets/variables/_all";
|
||||
|
||||
$nb-default-color: $color-gray;
|
||||
$nb-default-bg: $color-white;
|
@ -2,12 +2,12 @@ import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import styles from '../styles.scss';
|
||||
import styles from '../audio-modal/styles.scss';
|
||||
|
||||
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/enter-audio/container';
|
||||
import AudioTestContainer from '/imports/ui/components/audio-test/container';
|
||||
import EnterAudioContainer from '/imports/ui/components/audio/enter-audio/container';
|
||||
import AudioTestContainer from '/imports/ui/components/audio/audio-test/container';
|
||||
import cx from 'classnames';
|
||||
|
||||
class AudioSettings extends React.Component {
|
||||
@ -112,7 +112,7 @@ class AudioSettings extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<label className={styles.label}> </label>
|
||||
<label className={styles.label}> </label>
|
||||
<AudioTestContainer/>
|
||||
</div>
|
||||
</div>
|
||||
@ -150,7 +150,7 @@ const intlMessages = defineMessages({
|
||||
streamVolumeLabel: {
|
||||
id: 'app.audio.audioSettings.microphoneStreamLabel',
|
||||
description: 'Label for stream volume',
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default injectIntl(AudioSettings);
|
@ -9,8 +9,8 @@ const propTypes = {
|
||||
|
||||
const defaultProps = {
|
||||
low: 0,
|
||||
optimum: .05,
|
||||
high: .3,
|
||||
optimum: 0.05,
|
||||
high: 0.3,
|
||||
deviceId: undefined,
|
||||
};
|
||||
|
||||
|
@ -1,35 +1,35 @@
|
||||
import React from 'react';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import styles from '../settings/styles.scss';
|
||||
import styles from './styles.scss';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
class AudioTest extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Button className={styles.testAudioBtn}
|
||||
label={intl.formatMessage(intlMessages.playSoundLabel)}
|
||||
icon={'unmute'}
|
||||
size={'md'}
|
||||
color={'primary'}
|
||||
onClick={this.props.handlePlayAudioSample}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
playSoundLabel: {
|
||||
id: 'app.audio.playSoundLabel',
|
||||
description: 'Play sound button label',
|
||||
},
|
||||
});
|
||||
|
||||
export default injectIntl(AudioTest);
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Button className={styles.testAudioBtn}
|
||||
label={intl.formatMessage(intlMessages.playSoundLabel)}
|
||||
icon={'unmute'}
|
||||
size={'sm'}
|
||||
color={'primary'}
|
||||
onClick={this.props.handlePlayAudioSample}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
playSoundLabel: {
|
||||
id: 'app.audio.playSoundLabel',
|
||||
description: 'Play sound button label',
|
||||
},
|
||||
});
|
||||
|
||||
export default injectIntl(AudioTest);
|
@ -1,27 +1,26 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import AudioTest from './component';
|
||||
|
||||
class AudioTestContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AudioTest {...this.props}>
|
||||
{this.props.children}
|
||||
</AudioTest>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default createContainer(() => {
|
||||
const data = {
|
||||
handlePlayAudioSample: () => {
|
||||
const snd = new Audio('resources/sounds/audioSample.mp3');
|
||||
snd.play();
|
||||
},
|
||||
};
|
||||
return data;
|
||||
}, AudioTestContainer);
|
||||
import React, { Component } from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import AudioTest from './component';
|
||||
|
||||
class AudioTestContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AudioTest {...this.props}>
|
||||
{this.props.children}
|
||||
</AudioTest>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default createContainer(() => {
|
||||
return {
|
||||
handlePlayAudioSample: () => {
|
||||
const snd = new Audio('resources/sounds/audioSample.mp3');
|
||||
snd.play();
|
||||
},
|
||||
};
|
||||
}, AudioTestContainer);
|
@ -0,0 +1,12 @@
|
||||
@import "../../../stylesheets/variables/all";
|
||||
|
||||
.testAudioBtn {
|
||||
border: none;
|
||||
padding-left: 0;
|
||||
background-color: transparent;
|
||||
color: $color-primary;
|
||||
font-weight: normal;
|
||||
i {
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import React, { Component } from 'react';
|
||||
import { init } from './service';
|
||||
import { showModal } from '../app/service';
|
||||
import AudioModalContainer from './audio-modal/container';
|
||||
|
||||
export default class Audio extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
if (this.props.showJoinAudio) {
|
||||
showModal(<AudioModalContainer />);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
init();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<audio id="remote-media" autoPlay="autoplay">
|
||||
</audio>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import Audio from './component';
|
||||
|
||||
class AudioContainer extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Audio {...this.props}>
|
||||
<Audio
|
||||
{...this.props}>
|
||||
{this.props.children}
|
||||
</Audio>
|
||||
);
|
||||
@ -13,5 +14,9 @@ class AudioContainer extends Component {
|
||||
}
|
||||
|
||||
export default createContainer(() => {
|
||||
return {};
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
|
||||
return {
|
||||
showJoinAudio: APP_CONFIG.autoJoinAudio,
|
||||
};
|
||||
}, AudioContainer);
|
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import styles from '../settings/styles.scss';
|
||||
|
||||
class EnterAudio extends React.Component {
|
||||
constructor(props) {
|
||||
@ -14,8 +13,8 @@ class EnterAudio extends React.Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={styles.half}>
|
||||
<Button className={styles.enterBtn}
|
||||
<div>
|
||||
<Button
|
||||
label={intl.formatMessage(intlMessages.enterSessionLabel)}
|
||||
size={'md'}
|
||||
color={'primary'}
|
@ -1,6 +1,6 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import {joinListenOnly, joinMicrophone} from '/imports/api/phone';
|
||||
import { joinListenOnly, joinMicrophone } from '../service';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import EnterAudio from './component';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import styles from '../styles.scss';
|
||||
import styles from '../audio-modal/styles.scss';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
49
bigbluebutton-html5/imports/ui/components/audio/service.js
Normal file
49
bigbluebutton-html5/imports/ui/components/audio/service.js
Normal file
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import AudioModal from './audio-modal/component';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
|
||||
import { showModal } from '/imports/ui/components/app/service';
|
||||
import AudioManager from '/imports/api/audio/client/manager';
|
||||
|
||||
const handleJoinAudio = () => {
|
||||
const handleJoinListenOnly = () => joinListenOnly();
|
||||
return showModal(<AudioModal handleJoinListenOnly={handleJoinListenOnly} />);
|
||||
};
|
||||
|
||||
let audioManager = undefined;
|
||||
const init = () => {
|
||||
const userId = Auth.userID;
|
||||
const User = Users.findOne({ userId });
|
||||
const username = User.user.name;
|
||||
const listenOnly = User.user.listenOnly;
|
||||
|
||||
const Meeting = Meetings.findOne(); //TODO test this with Breakouts
|
||||
const turns = Meeting.turns;
|
||||
const stuns = Meeting.stuns;
|
||||
const voiceBridge = Meeting.voiceBridge;
|
||||
|
||||
const userData = {
|
||||
userId,
|
||||
username,
|
||||
listenOnly,
|
||||
turns,
|
||||
stuns,
|
||||
voiceBridge,
|
||||
};
|
||||
|
||||
audioManager = new AudioManager(userData);
|
||||
};
|
||||
|
||||
let exitAudio = () => audioManager.exitAudio();
|
||||
let joinListenOnly = () => audioManager.joinAudio(true);
|
||||
let joinMicrophone = () => audioManager.joinAudio(false);
|
||||
|
||||
export {
|
||||
init,
|
||||
handleJoinAudio,
|
||||
exitAudio,
|
||||
joinListenOnly,
|
||||
joinMicrophone,
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { clearModal } from '/imports/ui/components/app/service';
|
||||
import { exitAudio } from '/imports/api/phone'
|
||||
import { exitAudio } from '../audio/service'
|
||||
import Modal from '/imports/ui/components/modal/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
|
@ -1,7 +1,9 @@
|
||||
import Deskshare from '/imports/api/deskshare';
|
||||
import {vertoWatchVideo} from '/imports/api/verto';
|
||||
import VertoBridge from '/imports/api/deskshare/client/bridge/verto';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
|
||||
const vertoBridge = new VertoBridge();
|
||||
|
||||
// when the meeting information has been updated check to see if it was
|
||||
// desksharing. If it has changed either trigger a call to receive video
|
||||
// and display it, or end the call and hide the video
|
||||
@ -18,14 +20,14 @@ function isVideoBroadcasting() {
|
||||
function presenterDeskshareHasEnded() {
|
||||
// references a functiion in the global namespace inside verto_extension.js
|
||||
// that we load dynamically
|
||||
vertoExitVideo();
|
||||
vertoBridge.vertoExitVideo();
|
||||
};
|
||||
|
||||
// if remote deskshare has been started connect and display the video stream
|
||||
function presenterDeskshareHasStarted() {
|
||||
// references a functiion in the global namespace inside verto_extension.js
|
||||
// that we load dynamically
|
||||
vertoWatchVideo();
|
||||
vertoBridge.vertoWatchVideo();
|
||||
};
|
||||
|
||||
export {
|
||||
|
@ -53,6 +53,7 @@ export default class ModalBase extends Component {
|
||||
overlayClassName={styleOverlay}
|
||||
portalClassName={styles.portal}
|
||||
isOpen={isOpen}
|
||||
contentLabel="modal"
|
||||
onAfterOpen={this.handleAfterOpen}
|
||||
onRequestClose={this.handleRequestClose}>
|
||||
{this.props.children}
|
||||
|
@ -3,7 +3,6 @@ import Modal from '/imports/ui/components/modal/component';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ClosedCaptions from '/imports/ui/components/settings/submenus/closed-captions/component';
|
||||
import Audio from '/imports/ui/components/settings/submenus/audio/component';
|
||||
import Application from '/imports/ui/components/settings/submenus/application/container';
|
||||
import Participants from '/imports/ui/components/settings/submenus/participants/component';
|
||||
import Video from '/imports/ui/components/settings/submenus/video/component';
|
||||
@ -42,7 +41,6 @@ class Settings extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const audio = props.audio;
|
||||
const video = props.video;
|
||||
const application = props.application;
|
||||
const cc = props.cc;
|
||||
@ -50,14 +48,12 @@ class Settings extends Component {
|
||||
|
||||
this.state = {
|
||||
current: {
|
||||
audio: _.clone(audio),
|
||||
video: _.clone(video),
|
||||
application: _.clone(application),
|
||||
cc: _.clone(cc),
|
||||
participants: _.clone(participants),
|
||||
},
|
||||
saved: {
|
||||
audio: _.clone(audio),
|
||||
video: _.clone(video),
|
||||
application: _.clone(application),
|
||||
cc: _.clone(cc),
|
||||
@ -135,10 +131,6 @@ class Settings extends Component {
|
||||
<Icon iconName='application' className={styles.icon}/>
|
||||
<span id="appTab">{intl.formatMessage(intlMessages.appTabLabel)}</span>
|
||||
</Tab>
|
||||
<Tab className={styles.tabSelector} aria-labelledby="audioTab">
|
||||
<Icon iconName='unmute' className={styles.icon}/>
|
||||
<span id="audioTab">{intl.formatMessage(intlMessages.audioTabLabel)}</span>
|
||||
</Tab>
|
||||
<Tab className={styles.tabSelector} aria-labelledby="videoTab">
|
||||
<Icon iconName='video' className={styles.icon}/>
|
||||
<span id="videoTab">{intl.formatMessage(intlMessages.videoTabLabel)}</span>
|
||||
@ -161,11 +153,6 @@ class Settings extends Component {
|
||||
settings={this.state.current.application}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel className={styles.tabPanel}>
|
||||
<Audio
|
||||
settings={this.state.current.audio}
|
||||
handleUpdateSettings={this.handleUpdateSettings}/>
|
||||
</TabPanel>
|
||||
<TabPanel className={styles.tabPanel}>
|
||||
<Video
|
||||
handleUpdateSettings={this.handleUpdateSettings}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import _ from 'lodash';
|
||||
import Settings from './component';
|
||||
import SettingsService from '/imports/ui/services/settings';
|
||||
|
||||
|
@ -56,13 +56,3 @@
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.testAudioBtn {
|
||||
border: none;
|
||||
padding-left: 0;
|
||||
i {
|
||||
color: $color-primary;
|
||||
}
|
||||
background-color: transparent;
|
||||
color: $color-primary;
|
||||
}
|
||||
|
@ -1,120 +0,0 @@
|
||||
import React from 'react';
|
||||
import BaseMenu from '../base/component';
|
||||
import styles from '../styles.scss';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import DeviceSelector from '/imports/ui/components/audio/device-selector/component';
|
||||
import AudioStreamVolume from '/imports/ui/components/audio/audio-stream-volume/component';
|
||||
import AudioTestContainer from '/imports/ui/components/audio-test/container';
|
||||
import cx from 'classnames';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
micSource: {
|
||||
id: 'app.submenu.audio.micSourceLabel',
|
||||
description: 'Label for microphone source',
|
||||
},
|
||||
speakerSource: {
|
||||
id: 'app.submenu.audio.speakerSourceLabel',
|
||||
description: 'Label for speaker source',
|
||||
},
|
||||
streamVolume: {
|
||||
id: 'app.submenu.audio.streamVolumeLabel',
|
||||
description: 'Label for stream volume',
|
||||
},
|
||||
});
|
||||
|
||||
class AudioMenu extends BaseMenu {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleOutputChange = this.handleOutputChange.bind(this);
|
||||
|
||||
this.state = {
|
||||
settingsName: 'audio',
|
||||
settings: props.settings,
|
||||
};
|
||||
}
|
||||
|
||||
chooseAudio() {
|
||||
this.props.changeMenu(this.props.JOIN_AUDIO);
|
||||
}
|
||||
|
||||
handleSelectChange(fieldname, options, e) {
|
||||
let obj = this.state;
|
||||
obj.settings[fieldname] = options[e.target.value];
|
||||
this.setState(obj);
|
||||
this.handleUpdateSettings('audio', obj);
|
||||
}
|
||||
|
||||
handleInputChange(deviceId) {
|
||||
let obj = this.state;
|
||||
obj.settings.inputDeviceId = deviceId;
|
||||
this.setState(obj);
|
||||
this.handleUpdateSettings('audio', obj);
|
||||
}
|
||||
|
||||
handleOutputChange(deviceId) {
|
||||
let obj = this.state;
|
||||
obj.settings.outputDeviceId = deviceId;
|
||||
this.setState(obj);
|
||||
this.handleUpdateSettings('audio', obj);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.header}>
|
||||
<h3 className={styles.title}>Audio</h3>
|
||||
</div>
|
||||
|
||||
<div className={styles.form}>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={cx(styles.label, styles.labelSmall)}>
|
||||
{intl.formatMessage(intlMessages.micSource)}
|
||||
</label>
|
||||
<DeviceSelector
|
||||
value={this.state.inputDeviceId}
|
||||
className={styles.select}
|
||||
kind="audioinput"
|
||||
onChange={this.handleInputChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={cx(styles.label, styles.labelSmall)}>
|
||||
{intl.formatMessage(intlMessages.streamVolume)}
|
||||
</label>
|
||||
<AudioStreamVolume
|
||||
deviceId={this.state.inputDeviceId}
|
||||
className={styles.audioMeter} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label className={cx(styles.label, styles.labelSmall)}>
|
||||
{intl.formatMessage(intlMessages.speakerSource)}
|
||||
</label>
|
||||
<DeviceSelector
|
||||
value={this.state.outputDeviceId}
|
||||
className={styles.select}
|
||||
kind="audiooutput"
|
||||
onChange={this.handleOutputChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<label className={styles.label}> </label>
|
||||
<AudioTestContainer/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default injectIntl(AudioMenu);
|
@ -127,7 +127,7 @@ class ClosedCaptionsMenu extends BaseMenu {
|
||||
render() {
|
||||
const {
|
||||
locales,
|
||||
intl
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -18,7 +18,7 @@ getClosedCaptionSettings = () => {
|
||||
ccSettings.locales = locales;
|
||||
|
||||
return ccSettings;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
getClosedCaptionSettings,
|
||||
|
@ -62,10 +62,6 @@
|
||||
height: 1.75rem;
|
||||
}
|
||||
|
||||
.audioMeter {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pullContentRight {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
@ -32,7 +32,7 @@ const intlMessages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
class AudioMenu extends BaseMenu {
|
||||
class VideoMenu extends BaseMenu {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@ -101,4 +101,4 @@ class AudioMenu extends BaseMenu {
|
||||
}
|
||||
};
|
||||
|
||||
export default injectIntl(AudioMenu);
|
||||
export default injectIntl(VideoMenu);
|
||||
|
@ -38,7 +38,8 @@ export default class Switch extends Toggle {
|
||||
|
||||
<input
|
||||
{...inputProps}
|
||||
ref={ref => { this.input = ref }}
|
||||
ref={ref => { this.input = ref; }}
|
||||
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
className='react-toggle-screenreader-only'
|
||||
|
Loading…
Reference in New Issue
Block a user