add audio logs (join/leave/mute/unmute) / fix lint issues

This commit is contained in:
KDSBrowne 2019-07-25 18:41:24 +00:00
parent fd7a5918c1
commit 4fba9978d3
7 changed files with 176 additions and 41 deletions

View File

@ -4,6 +4,7 @@ import { withModalMounter } from '/imports/ui/components/modal/service';
import AudioManager from '/imports/ui/services/audio-manager';
import { makeCall } from '/imports/ui/services/api';
import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
import logger from '/imports/startup/client/logger';
import AudioControls from './component';
import AudioModalContainer from '../audio-modal/container';
import Service from '../service';
@ -32,16 +33,37 @@ const processToggleMuteFromOutside = (e) => {
}
};
const handleLeaveAudio = () => {
Service.exitAudio();
logger.info({
logCode: 'audiocontrols_leave_audio',
extraInfo: { logType: 'user_action' },
}, 'audio connection closed by user');
};
const {
currentUser,
isConnected,
isListenOnly,
isEchoTest,
isMuted,
isConnecting,
isHangingUp,
isTalking,
toggleMuteMicrophone,
joinListenOnly,
} = Service;
export default lockContextContainer(withModalMounter(withTracker(({ mountModal, userLocks }) => ({
processToggleMuteFromOutside: arg => processToggleMuteFromOutside(arg),
showMute: Service.isConnected() && !Service.isListenOnly() && !Service.isEchoTest() && !userLocks.userMic,
muted: Service.isConnected() && !Service.isListenOnly() && Service.isMuted(),
inAudio: Service.isConnected() && !Service.isEchoTest(),
listenOnly: Service.isConnected() && Service.isListenOnly(),
disable: Service.isConnecting() || Service.isHangingUp() || !Meteor.status().connected,
talking: Service.isTalking() && !Service.isMuted(),
currentUser: Service.currentUser(),
handleToggleMuteMicrophone: () => Service.toggleMuteMicrophone(),
handleJoinAudio: () => (Service.isConnected() ? Service.joinListenOnly() : mountModal(<AudioModalContainer />)),
handleLeaveAudio: () => Service.exitAudio(),
showMute: isConnected() && !isListenOnly() && !isEchoTest() && !userLocks.userMic,
muted: isConnected() && !isListenOnly() && isMuted(),
inAudio: isConnected() && !isEchoTest(),
listenOnly: isConnected() && isListenOnly(),
disable: isConnecting() || isHangingUp() || !Meteor.status().connected,
talking: isTalking() && !isMuted(),
currentUser: currentUser(),
handleToggleMuteMicrophone: () => toggleMuteMicrophone(),
handleJoinAudio: () => (isConnected() ? joinListenOnly() : mountModal(<AudioModalContainer />)),
handleLeaveAudio,
}))(AudioControlsContainer)));

View File

@ -3,6 +3,9 @@ import Auth from '/imports/ui/services/auth';
import AudioManager from '/imports/ui/services/audio-manager';
import Meetings from '/imports/api/meetings';
import mapUser from '/imports/ui/services/user/mapUser';
import { makeCall } from '/imports/ui/services/api';
import VoiceUsers from '/imports/api/voice-users';
import logger from '/imports/startup/client/logger';
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
@ -34,6 +37,24 @@ const init = (messages, intl) => {
const currentUser = () => mapUser(Users.findOne({ intId: Auth.userID }));
const toggleMuteMicrophone = () => {
makeCall('toggleSelfVoice');
const cvu = VoiceUsers.findOne({ meetingId: Auth.meetingID, intId: Auth.userID });
if (cvu) {
if (cvu.muted) {
logger.info({
logCode: 'audiomanager_unmute_audio',
extraInfo: { logType: 'user_action' },
}, 'microphone unmuted by user');
} else {
logger.info({
logCode: 'audiomanager_mute_audio',
extraInfo: { logType: 'user_action' },
}, 'microphone muted by user');
}
}
};
export default {
init,
exitAudio: () => AudioManager.exitAudio(),
@ -41,7 +62,7 @@ export default {
joinListenOnly: () => AudioManager.joinListenOnly(),
joinMicrophone: () => AudioManager.joinMicrophone(),
joinEchoTest: () => AudioManager.joinEchoTest(),
toggleMuteMicrophone: () => AudioManager.toggleMuteMicrophone(),
toggleMuteMicrophone,
changeInputDevice: inputDeviceId => AudioManager.changeInputDevice(inputDeviceId),
changeOutputDevice: outputDeviceId => AudioManager.changeOutputDevice(outputDeviceId),
isConnected: () => AudioManager.isConnected,

View File

@ -1,7 +1,9 @@
import React, { Component } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl, intlShape } from 'react-intl';
import { withModalMounter } from '/imports/ui/components/modal/service';
import Modal from '/imports/ui/components/modal/fullscreen/component';
import logger from '/imports/startup/client/logger';
import PropTypes from 'prop-types';
import AudioService from '../audio/service';
import VideoService from '../video-provider/service';
import { styles } from './styles';
@ -37,6 +39,19 @@ const intlMessages = defineMessages({
},
});
const propTypes = {
intl: intlShape.isRequired,
breakout: PropTypes.objectOf(Object).isRequired,
getURL: PropTypes.func.isRequired,
mountModal: PropTypes.func.isRequired,
breakoutURL: PropTypes.string.isRequired,
isFreeJoin: PropTypes.bool.isRequired,
currentVoiceUser: PropTypes.objectOf(Object).isRequired,
requestJoinURL: PropTypes.func.isRequired,
breakouts: PropTypes.arrayOf(Object).isRequired,
breakoutName: PropTypes.string.isRequired,
};
class BreakoutJoinConfirmation extends Component {
constructor(props) {
super(props);
@ -56,10 +71,21 @@ class BreakoutJoinConfirmation extends Component {
mountModal,
breakoutURL,
isFreeJoin,
currentVoiceUser,
} = this.props;
const url = isFreeJoin ? getURL(this.state.selectValue) : breakoutURL;
// leave main room's audio when joining a breakout room
AudioService.exitAudio();
const { selectValue } = this.state;
const url = isFreeJoin ? getURL(selectValue) : breakoutURL;
if (currentVoiceUser && currentVoiceUser.joined) {
// leave main room's audio when joining a breakout room
AudioService.exitAudio();
logger.info({
logCode: 'breakoutjoinconfirmation_ended_audio',
extraInfo: { logType: 'user_action' },
}, 'joining breakout room closed audio in the main room');
}
VideoService.exitVideo();
window.open(url);
mountModal(null);
@ -67,21 +93,32 @@ class BreakoutJoinConfirmation extends Component {
handleSelectChange(e) {
const { value } = e.target;
const { requestJoinURL } = this.props;
this.setState({ selectValue: value });
this.props.requestJoinURL(value);
requestJoinURL(value);
}
renderSelectMeeting() {
const { breakouts, intl } = this.props;
const { selectValue } = this.state;
return (
<div className={styles.selectParent}>
{`${intl.formatMessage(intlMessages.freeJoinMessage)}`}
<select
className={styles.select}
value={this.state.selectValue}
value={selectValue}
onChange={this.handleSelectChange}
>
{breakouts.map(({ name, breakoutId }) => (<option key={breakoutId} value={breakoutId}>{name}</option>))}
{
breakouts.map(({ name, breakoutId }) => (
<option
key={breakoutId}
value={breakoutId}
>
{name}
</option>
))
}
</select>
</div>
);
@ -89,6 +126,7 @@ class BreakoutJoinConfirmation extends Component {
render() {
const { intl, breakoutName, isFreeJoin } = this.props;
return (
<Modal
title={intl.formatMessage(intlMessages.title)}
@ -110,3 +148,5 @@ class BreakoutJoinConfirmation extends Component {
}
export default withModalMounter(injectIntl(BreakoutJoinConfirmation));
BreakoutJoinConfirmation.propTypes = propTypes;

View File

@ -2,12 +2,16 @@ import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Breakouts from '/imports/api/breakouts';
import Auth from '/imports/ui/services/auth';
import VoiceUsers from '/imports/api/voice-users/';
import { makeCall } from '/imports/ui/services/api';
import breakoutService from '/imports/ui/components/breakout-room/service';
import BreakoutJoinConfirmationComponent from './component';
const BreakoutJoinConfirmationContrainer = props =>
(<BreakoutJoinConfirmationComponent {...props} />);
const BreakoutJoinConfirmationContrainer = props => (
<BreakoutJoinConfirmationComponent
{...props}
/>
);
const getURL = (breakoutId) => {
const currentUserId = Auth.userID;
@ -31,6 +35,8 @@ export default withTracker(({ breakout, mountModal, breakoutName }) => {
requestJoinURL(breakoutId);
}
const currentVoiceUser = VoiceUsers.findOne({ meetingId: Auth.meetingID, intId: Auth.userID });
return {
isFreeJoin,
mountModal,
@ -39,5 +45,6 @@ export default withTracker(({ breakout, mountModal, breakoutName }) => {
breakouts: breakoutService.getBreakouts(),
requestJoinURL,
getURL,
currentVoiceUser,
};
})(BreakoutJoinConfirmationContrainer);

View File

@ -11,6 +11,8 @@ import { EMOJI_STATUSES } from '/imports/utils/statuses';
import { makeCall } from '/imports/ui/services/api';
import _ from 'lodash';
import KEY_CODES from '/imports/utils/keyCodes';
import AudioService from '/imports/ui/components/audio/service';
import logger from '/imports/startup/client/logger';
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
@ -382,9 +384,13 @@ const removeUser = (userId) => {
const toggleVoice = (userId) => {
if (userId === Auth.userID) {
makeCall('toggleSelfVoice');
AudioService.toggleMuteMicrophone();
} else {
makeCall('toggleVoice', userId);
logger.info({
logCode: 'usermenu_option_mute_audio',
extraInfo: { logType: 'moderator_action' },
}, 'moderator muted user microphone');
}
};

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import Auth from '/imports/ui/services/auth';
import Service from '/imports/ui/components/actions-bar/service';
import userListService from '/imports/ui/components/user-list/service';
import logger from '/imports/startup/client/logger';
import { defineMessages, injectIntl, intlShape } from 'react-intl';
import { notify } from '/imports/ui/services/notification';
import UserOptions from './component';
@ -43,9 +44,38 @@ const UserOptionsContainer = withTracker((props) => {
);
};
const isMeetingMuteOnStart = () => {
const { voiceProp } = meeting;
const { muteOnStart } = voiceProp;
return muteOnStart;
};
const meetingMuteDisabledLog = () => logger.info({
logCode: 'useroptions_unmute_all',
extraInfo: { logType: 'moderator_action' },
}, 'moderator disabled meeting mute');
return {
toggleMuteAllUsers: () => muteAllUsers(Auth.userID),
toggleMuteAllUsersExceptPresenter: () => muteAllExceptPresenter(Auth.userID),
toggleMuteAllUsers: () => {
muteAllUsers(Auth.userID);
if (isMeetingMuteOnStart()) {
return meetingMuteDisabledLog();
}
return logger.info({
logCode: 'useroptions_mute_all',
extraInfo: { logType: 'moderator_action' },
}, 'moderator enabled meeting mute, all users muted');
},
toggleMuteAllUsersExceptPresenter: () => {
muteAllExceptPresenter(Auth.userID);
if (isMeetingMuteOnStart()) {
return meetingMuteDisabledLog();
}
return logger.info({
logCode: 'useroptions_mute_all_except_presenter',
extraInfo: { logType: 'moderator_action' },
}, 'moderator enabled meeting mute, all users muted except presenter');
},
toggleStatus,
isMeetingMuted: meeting.voiceProp.muteOnStart,
isUserPresenter: Service.isUserPresenter(),

View File

@ -1,5 +1,4 @@
import { Tracker } from 'meteor/tracker';
import { makeCall } from '/imports/ui/services/api';
import KurentoBridge from '/imports/api/audio/client/bridge/kurento';
import Auth from '/imports/ui/services/auth';
import VoiceUsers from '/imports/api/voice-users';
@ -138,11 +137,13 @@ class AudioManager {
extension: ECHO_TEST_NUMBER,
inputStream: this.inputStream,
};
logger.info({ logCode: 'audiomanager_join_echotest', extraInfo: { logType: 'user_action' } }, 'User requested to join audio conference with mic');
return this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this));
});
}
async joinListenOnly(retries = 0) {
async joinListenOnly(r = 0) {
let retries = r;
this.isListenOnly = true;
this.isEchoTest = false;
@ -180,6 +181,11 @@ class AudioManager {
});
const handleListenOnlyError = async (err) => {
const error = {
type: 'MEDIA_ERROR',
message: this.messages.error.MEDIA_ERROR,
};
if (iceGatheringTimeout) {
clearTimeout(iceGatheringTimeout);
}
@ -191,12 +197,12 @@ class AudioManager {
retries,
},
}, 'Listen only error');
throw {
type: 'MEDIA_ERROR',
message: this.messages.error.MEDIA_ERROR,
};
throw error;
};
logger.info({ logCode: 'audiomanager_join_listenonly', extraInfo: { logType: 'user_action' } }, 'user requested to connect to audio conference as listen only');
return this.onAudioJoining()
.then(() => Promise.race([
bridge.joinAudio(callOptions, this.callStateCallback.bind(this)),
@ -214,13 +220,16 @@ class AudioManager {
}
try {
await this.joinListenOnly(++retries);
retries += 1;
await this.joinListenOnly(retries);
} catch (error) {
return handleListenOnlyError(error);
}
} else {
handleListenOnlyError(err);
return handleListenOnlyError(err);
}
return null;
});
}
@ -247,10 +256,6 @@ class AudioManager {
return this.bridge.transferCall(this.onAudioJoin.bind(this));
}
toggleMuteMicrophone() {
makeCall('toggleSelfVoice');
}
onAudioJoin() {
this.isConnecting = false;
this.isConnected = true;
@ -362,9 +367,11 @@ class AudioManager {
this.listenOnlyAudioContext.close();
}
this.listenOnlyAudioContext = window.AudioContext
? new window.AudioContext()
: new window.webkitAudioContext();
const { AudioContext, WebkitAudioContext } = window;
this.listenOnlyAudioContext = AudioContext
? new AudioContext()
: new WebkitAudioContext();
const dest = this.listenOnlyAudioContext.createMediaStreamDestination();
@ -402,9 +409,11 @@ class AudioManager {
return Promise.resolve(inputDevice);
};
const handleChangeInputDeviceError = () => Promise.reject({
type: 'MEDIA_ERROR',
message: this.messages.error.MEDIA_ERROR,
const handleChangeInputDeviceError = () => new Promise((reject) => {
reject({
type: 'MEDIA_ERROR',
message: this.messages.error.MEDIA_ERROR,
});
});
if (!deviceId) {