Merge branch 'develop' into feat-unsupportedbrowser

auto_join_3.3
Mészáros Mihály 2020-05-19 15:07:25 +02:00
commit ee1a691642
38 changed files with 621 additions and 333 deletions

2
app/.env 100644
View File

@ -0,0 +1,2 @@
REACT_APP_VERSION=$npm_package_version
REACT_APP_NAME=$npm_package_name

View File

@ -37,20 +37,22 @@ var config =
'opera'
],
// Socket.io request timeout
requestTimeout : 10000,
requestTimeout : 20000,
requestRetries : 3,
transportOptions :
{
tcp : true
},
defaultAudio :
{
sampleRate : 48000,
channelCount : 1,
volume : 1.0,
autoGainControl : true,
echoCancellation : true,
noiseSuppression : true,
sampleSize : 16
sampleRate : 48000,
channelCount : 1,
volume : 1.0,
autoGainControl : true,
echoCancellation : true,
noiseSuppression : true,
voiceActivityMute : false,
sampleSize : 16
},
/**

View File

@ -1,6 +1,7 @@
import Logger from './Logger';
import hark from 'hark';
import { getSignalingUrl } from './urlFactory';
import { SocketTimeoutError } from './utils';
import * as requestActions from './actions/requestActions';
import * as meActions from './actions/meActions';
import * as roomActions from './actions/roomActions';
@ -574,7 +575,7 @@ export default class RoomClient
if (called)
return;
called = true;
callback(new Error('Request timeout.'));
callback(new SocketTimeoutError('Request timed out'));
},
ROOM_OPTIONS.requestTimeout
);
@ -590,13 +591,13 @@ export default class RoomClient
};
}
sendRequest(method, data)
_sendRequest(method, data)
{
return new Promise((resolve, reject) =>
{
if (!this._signalingSocket)
{
reject('No socket connection.');
reject('No socket connection');
}
else
{
@ -606,19 +607,42 @@ export default class RoomClient
this.timeoutCallback((err, response) =>
{
if (err)
{
reject(err);
}
else
{
resolve(response);
}
})
);
}
});
}
async sendRequest(method, data)
{
logger.debug('sendRequest() [method:"%s", data:"%o"]', method, data);
const {
requestRetries = 3
} = window.config;
for (let tries = 0; tries < requestRetries; tries++)
{
try
{
return await this._sendRequest(method, data);
}
catch (error)
{
if (
error instanceof SocketTimeoutError &&
tries < requestRetries
)
logger.warn('sendRequest() | timeout, retrying [attempt:"%s"]', tries);
else
throw error;
}
}
}
async changeDisplayName(displayName)
{
logger.debug('changeDisplayName() [displayName:"%s"]', displayName);
@ -1003,37 +1027,50 @@ export default class RoomClient
if (!this._harkStream.getAudioTracks()[0])
throw new Error('getMicStream():something went wrong with hark');
this._hark = hark(this._harkStream, { play: false });
// eslint-disable-next-line no-unused-vars
this._hark.on('volume_change', (dBs, threshold) =>
{
// The exact formula to convert from dBs (-100..0) to linear (0..1) is:
// Math.pow(10, dBs / 20)
// However it does not produce a visually useful output, so let exagerate
// it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to
// minimize component renderings.
let volume = Math.round(Math.pow(10, dBs / 85) * 10);
if (volume === 1)
volume = 0;
volume = Math.round(volume);
if (this._micProducer && volume !== this._micProducer.volume)
this._hark = hark(this._harkStream,
{
this._micProducer.volume = volume;
play : false,
interval : 5,
threshold : store.getState().settings.noiseThreshold,
history : 300
});
this._hark.lastVolume = -100;
this._hark.on('volume_change', (volume) =>
{
volume = Math.round(volume);
if (this._micProducer && volume !== Math.round(this._hark.lastVolume))
{
if (volume < this._hark.lastVolume * 1.02)
{
volume = this._hark.lastVolume * 1.02;
}
this._hark.lastVolume = volume;
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
}
});
this._hark.on('speaking', function()
this._hark.on('speaking', () =>
{
store.dispatch(meActions.setIsSpeaking(true));
if ((store.getState().settings.voiceActivatedUnmute ||
store.getState().me.isAutoMuted) &&
this._micProducer &&
this._micProducer.paused)
{
this._micProducer.resume();
}
store.dispatch(meActions.setAutoMuted(false)); // sanity action
});
this._hark.on('stopped_speaking', function()
this._hark.on('stopped_speaking', () =>
{
store.dispatch(meActions.setIsSpeaking(false));
if (store.getState().settings.voiceActivatedUnmute &&
this._micProducer &&
!this._micProducer.paused)
{
this._micProducer.pause();
store.dispatch(meActions.setAutoMuted(true));
}
});
}
@ -1962,23 +1999,12 @@ export default class RoomClient
producerPaused
} = request.data;
let codecOptions;
if (kind === 'audio')
{
codecOptions =
{
opusStereo : 1
};
}
const consumer = await this._recvTransport.consume(
{
id,
producerId,
kind,
rtpParameters,
codecOptions,
appData : { ...appData, peerId } // Trick.
});
@ -2031,19 +2057,8 @@ export default class RoomClient
consumer.hark = hark(stream, { play: false });
// eslint-disable-next-line no-unused-vars
consumer.hark.on('volume_change', (dBs, threshold) =>
consumer.hark.on('volume_change', (volume) =>
{
// The exact formula to convert from dBs (-100..0) to linear (0..1) is:
// Math.pow(10, dBs / 20)
// However it does not produce a visually useful output, so let exaggerate
// it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to
// minimize component renderings.
let volume = Math.round(Math.pow(10, dBs / 85) * 10);
if (volume === 1)
volume = 0;
volume = Math.round(volume);
if (consumer && volume !== consumer.volume)
@ -2667,7 +2682,7 @@ export default class RoomClient
store.dispatch(requestActions.notify(
{
text : intl.formatMessage({
id : 'moderator.muteScreenSharingModerator',
id : 'moderator.stopScreenSharing',
defaultMessage : 'Moderator stopped your screen sharing'
})
}));
@ -3888,6 +3903,14 @@ export default class RoomClient
store.dispatch(meActions.setWebcamInProgress(false));
}
async _setNoiseThreshold(threshold)
{
logger.debug('_setNoiseThreshold:%s', threshold);
this._hark.setThreshold(threshold);
store.dispatch(
settingsActions.setNoiseThreshold(threshold));
}
async _updateAudioDevices()
{
logger.debug('_updateAudioDevices()');

View File

@ -110,3 +110,9 @@ export const setIsSpeaking = (flag) =>
type : 'SET_IS_SPEAKING',
payload : { flag }
});
export const setAutoMuted = (flag) =>
({
type : 'SET_AUTO_MUTED',
payload : { flag }
});

View File

@ -71,6 +71,18 @@ export const setNoiseSuppression = (noiseSuppression) =>
payload : { noiseSuppression }
});
export const setVoiceActivatedUnmute = (voiceActivatedUnmute) =>
({
type: 'SET_VOICE_ACTIVATED_UNMUTE',
payload: { voiceActivatedUnmute }
});
export const setNoiseThreshold = (noiseThreshold) =>
({
type: 'SET_NOISE_THRESHOLD',
payload: { noiseThreshold }
});
export const setDefaultAudio = (audio) =>
({
type : 'SET_DEFAULT_AUDIO',

View File

@ -137,10 +137,10 @@ const styles = (theme) =>
transform : 'translate(-50%, 0%)',
color : 'rgba(255, 255, 255, 0.7)',
fontSize : '1.3em',
backgroundColor : 'rgba(255, 0, 0, 0.9)',
backgroundColor : 'rgba(245, 0, 87, 0.70)',
margin : '4px',
padding : theme.spacing(2),
zIndex : 31,
zIndex : 1200,
borderRadius : '20px',
textAlign : 'center',
opacity : 0,
@ -176,6 +176,7 @@ const Me = (props) =>
screenProducer,
extraVideoProducers,
canShareScreen,
noiseVolume,
classes
} = props;
@ -440,7 +441,12 @@ const Me = (props) =>
})}
className={classes.smallContainer}
disabled={!me.canSendMic || me.audioInProgress}
color={micState === 'on' ? 'primary' : 'secondary'}
color={
micState === 'on' ?
settings.voiceActivatedUnmute && !me.isAutoMuted ?
'primary'
: 'default'
: 'secondary'}
size='small'
onClick={() =>
{
@ -453,7 +459,10 @@ const Me = (props) =>
}}
>
{ micState === 'on' ?
<MicIcon />
<MicIcon
color={me.isAutoMuted ? 'secondary' : 'primary'}
style={{ opacity: noiseVolume }}
/>
:
<MicOffIcon />
}
@ -468,7 +477,10 @@ const Me = (props) =>
})}
className={classes.fab}
disabled={!me.canSendMic || me.audioInProgress}
color={micState === 'on' ? 'default' : 'secondary'}
color={micState === 'on' ?
settings.voiceActivatedUnmute && !me.isAutoMuted? 'primary'
: 'default'
: 'secondary'}
size='large'
onClick={() =>
{
@ -481,7 +493,11 @@ const Me = (props) =>
}}
>
{ micState === 'on' ?
<MicIcon />
<MicIcon
color={me.isAutoMuted && settings.voiceActivatedUnmute ? 'secondary' : 'primary'}
style={me.isAutoMuted && settings.voiceActivatedUnmute ? { opacity: noiseVolume }
: { opacity: 1 }}
/>
:
<MicOffIcon />
}
@ -868,6 +884,7 @@ Me.propTypes =
style : PropTypes.object,
smallContainer : PropTypes.bool,
canShareScreen : PropTypes.bool.isRequired,
noiseVolume : PropTypes.number,
classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired
};
@ -878,12 +895,26 @@ const makeMapStateToProps = () =>
const mapStateToProps = (state) =>
{
let volume;
// noiseVolume under threshold
if (state.peerVolumes[state.me.id] < state.settings.noiseThreshold)
{
// noiseVolume mapped to range 0.5 ... 1 (threshold switch)
volume = 1 + ((Math.abs(state.peerVolumes[state.me.id] -
state.settings.noiseThreshold) / (-120 -
state.settings.noiseThreshold)));
}
// noiseVolume over threshold: no noise but voice
else { volume = 0; }
return {
me : state.me,
...meProducersSelector(state),
settings : state.settings,
activeSpeaker : state.me.id === state.room.activeSpeakerId,
canShareScreen : hasPermission(state)
canShareScreen : hasPermission(state),
noiseVolume : volume
};
};
@ -900,6 +931,8 @@ export default withRoomContext(connect(
return (
prev.room === next.room &&
prev.me === next.me &&
Math.round(prev.peerVolumes[prev.me.id]) ===
Math.round(next.peerVolumes[next.me.id]) &&
prev.peers === next.peers &&
prev.producers === next.producers &&
prev.settings === next.settings

View File

@ -94,17 +94,17 @@ const styles = () =>
smallBar :
{
flex : '0 0 auto',
margin : '0.3rem',
backgroundSize : '75%',
backgroundRepeat : 'no-repeat',
backgroundColor : 'rgba(0, 0, 0, 1)',
cursor : 'pointer',
transitionProperty : 'opacity, background-color',
width : 3,
borderRadius : 6,
borderRadius : 2,
transitionDuration : '0.25s',
position : 'absolute',
bottom : 0,
top : '50%',
transform : 'translateY(-50%)',
'&.level0' : { height: 0 },
'&.level1' : { height: '0.2vh' },
'&.level2' : { height: '0.4vh' },
@ -149,9 +149,16 @@ const makeMapStateToProps = (initialState, props) =>
{
const mapStateToProps = (state) =>
{
return {
volume : state.peerVolumes[props.id]
};
if (state.peerVolumes[props.id]>state.settings.noiseThreshold)
{
return {
volume : Math.round((state.peerVolumes[props.id]+100) / 10)
};
}
else
{
return { volume: 0 };
}
};
return mapStateToProps;

View File

@ -42,8 +42,9 @@ const styles = (theme) =>
},
link :
{
display : 'block',
textAlign : 'center'
display : 'block',
textAlign : 'center',
marginBottom : theme.spacing(1)
}
});
@ -68,15 +69,16 @@ const About = ({
/>
</DialogTitle>
<DialogContent dividers='true'>
<DialogContentText>
<DialogContentText paragraph>
Contributions to this work were made on behalf of the GÉANT
project, a project that has received funding from the
European Unions Horizon 2020 research and innovation
programme under Grant Agreement No. 731122 (GN4-2).
On behalf of GÉANT project, GÉANT Association is the sole
owner of the copyright in all material which was developed
by a member of the GÉANT project.<br />
<br />
by a member of the GÉANT project.
</DialogContentText>
<DialogContentText paragraph>
GÉANT Vereniging (Association) is registered with the
Chamber of Commerce in Amsterdam with registration number
40535155 and operates in the UK as a branch of GÉANT
@ -87,6 +89,13 @@ const About = ({
<Link href='https://edumeet.org' target='_blank' rel='noreferrer' color='secondary' variant='h6' className={classes.link}>
https://edumeet.org
</Link>
<DialogContentText align='center' variant='h7'>
<FormattedMessage
id='label.version'
defaultMessage='Version'
/>
:{` ${process.env.REACT_APP_VERSION}`}
</DialogContentText>
</DialogContent>
<DialogActions>
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }

View File

@ -472,6 +472,34 @@ const TopBar = (props) =>
}
</div>
<div className={classes.sectionMobile}>
{ lobbyPeers.length > 0 &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
>
<span className={classes.disabledButton}>
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
className={classes.actionButton}
color='inherit'
disabled={!canPromote}
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
>
<PulsingBadge
color='secondary'
badgeContent={lobbyPeers.length}
>
<SecurityIcon />
</PulsingBadge>
</IconButton>
</span>
</Tooltip>
}
<IconButton
aria-haspopup='true'
onClick={handleMobileMenuOpen}
@ -480,34 +508,6 @@ const TopBar = (props) =>
<MoreIcon />
</IconButton>
</div>
{ lobbyPeers.length > 0 &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
>
<span className={classes.disabledButton}>
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
className={classes.actionButton}
color='inherit'
disabled={!canPromote}
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
>
<PulsingBadge
color='secondary'
badgeContent={lobbyPeers.length}
>
<SecurityIcon />
</PulsingBadge>
</IconButton>
</span>
</Tooltip>
}
<div className={classes.divider} />
<Button
aria-label={intl.formatMessage({
@ -697,33 +697,6 @@ const TopBar = (props) =>
/>
</p>
</MenuItem>
{ lobbyPeers.length > 0 &&
<MenuItem
aria-label={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
disabled={!canPromote}
onClick={() =>
{
handleMenuClose();
setLockDialogOpen(!room.lockDialogOpen);
}}
>
<PulsingBadge
color='secondary'
badgeContent={lobbyPeers.length}
>
<SecurityIcon />
</PulsingBadge>
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.lobby'
defaultMessage='Show lobby'
/>
</p>
</MenuItem>
}
<MenuItem
aria-label={intl.formatMessage({
id : 'tooltip.participants',

View File

@ -6,31 +6,66 @@ import { withRoomContext } from '../../RoomContext';
import * as settingsActions from '../../actions/settingsActions';
import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl';
import classnames from 'classnames';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select';
import Checkbox from '@material-ui/core/Checkbox';
import Slider from '@material-ui/core/Slider';
import Typography from '@material-ui/core/Typography';
const styles = (theme) =>
({
setting :
const NoiseSlider = withStyles(
{
root :
{
padding : theme.spacing(2)
color : '#3880ff',
height : 2,
padding : '15px 0'
},
formControl :
{
display : 'flex'
track : {
height : 2
},
rail : {
height : 2,
opacity : 0.2
},
mark : {
backgroundColor : '#bfbfbf',
height : 10,
width : 3,
marginTop : -3
},
markActive : {
opacity : 1,
backgroundColor : 'currentColor'
}
});
})(Slider);
const styles = (theme) => ({
setting :
{
padding : theme.spacing(2)
},
margin :
{
height : theme.spacing(3)
},
formControl :
{
display : 'flex'
}
});
const MediaSettings = ({
setEchoCancellation,
setAutoGainControl,
setNoiseSuppression,
setVoiceActivatedUnmute,
roomClient,
me,
volume,
settings,
classes
}) =>
@ -135,6 +170,34 @@ const MediaSettings = ({
}
</FormHelperText>
</FormControl>
<FormControl className={classes.formControl}>
<Select
value={settings.resolution || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.changeVideoResolution(event.target.value);
}}
name='Video resolution'
autoWidth
className={classes.selectEmpty}
>
{resolutions.map((resolution, index) =>
{
return (
<MenuItem key={index} value={resolution.value}>
{resolution.label}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.resolution'
defaultMessage='Select your video resolution'
/>
</FormHelperText>
</FormControl>
</form>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
@ -148,7 +211,7 @@ const MediaSettings = ({
displayEmpty
name={intl.formatMessage({
id : 'settings.audio',
defaultMessage : 'Audio device'
defaultMessage : 'Audio input device'
})}
autoWidth
className={classes.selectEmpty}
@ -165,12 +228,12 @@ const MediaSettings = ({
{ audioDevices.length > 0 ?
intl.formatMessage({
id : 'settings.selectAudio',
defaultMessage : 'Select audio device'
defaultMessage : 'Select audio input device'
})
:
intl.formatMessage({
id : 'settings.cantSelectAudio',
defaultMessage : 'Unable to select audio device'
defaultMessage : 'Unable to select audio input device'
})
}
</FormHelperText>
@ -225,34 +288,6 @@ const MediaSettings = ({
</form>
}
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={settings.resolution || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.changeVideoResolution(event.target.value);
}}
name='Video resolution'
autoWidth
className={classes.selectEmpty}
>
{ resolutions.map((resolution, index) =>
{
return (
<MenuItem key={index} value={resolution.value}>
{resolution.label}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.resolution'
defaultMessage='Select your video resolution'
/>
</FormHelperText>
</FormControl>
<FormControlLabel
className={classes.setting}
control={
@ -298,6 +333,43 @@ const MediaSettings = ({
defaultMessage : 'Noise suppression'
})}
/>
<FormControlLabel
className={classes.setting}
control={
<Checkbox checked={settings.voiceActivatedUnmute} onChange={
(event) =>
{
setVoiceActivatedUnmute(event.target.checked);
}}
/>}
label={intl.formatMessage({
id : 'settings.voiceActivatedUnmute',
defaultMessage : 'Voice activated unmute'
})}
/>
<div className={classes.margin} />
<Typography gutterBottom>
{
intl.formatMessage({
id : 'settings.noiseThreshold',
defaultMessage : 'Noise threshold:'
})
}
</Typography>
<NoiseSlider className={classnames(classes.slider, classnames.setting)}
key={'noise-threshold-slider'}
min={-100}
value={settings.noiseThreshold}
max={0}
valueLabelDisplay={'off'}
onChange={
(event, value) =>
{
roomClient._setNoiseThreshold(value);
}}
marks={[ { value: volume, label: 'level' } ]}
/>
<div className={classes.margin} />
</form>
</React.Fragment>
);
@ -305,27 +377,31 @@ const MediaSettings = ({
MediaSettings.propTypes =
{
roomClient : PropTypes.any.isRequired,
setEchoCancellation : PropTypes.func.isRequired,
setAutoGainControl : PropTypes.func.isRequired,
setNoiseSuppression : PropTypes.func.isRequired,
me : appPropTypes.Me.isRequired,
settings : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired
roomClient : PropTypes.any.isRequired,
setEchoCancellation : PropTypes.func.isRequired,
setAutoGainControl : PropTypes.func.isRequired,
setNoiseSuppression : PropTypes.func.isRequired,
setVoiceActivatedUnmute : PropTypes.func.isRequired,
me : appPropTypes.Me.isRequired,
volume : PropTypes.number,
settings : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
{
return {
me : state.me,
volume : state.peerVolumes[state.me.id],
settings : state.settings
};
};
const mapDispatchToProps = {
setEchoCancellation : settingsActions.setEchoCancellation,
setAutoGainControl : settingsActions.toggleAutoGainControl,
setNoiseSuppression : settingsActions.toggleNoiseSuppression
setEchoCancellation : settingsActions.setEchoCancellation,
setAutoGainControl : settingsActions.toggleAutoGainControl,
setNoiseSuppression : settingsActions.toggleNoiseSuppression,
setVoiceActivatedUnmute : settingsActions.setVoiceActivatedUnmute
};
export default withRoomContext(connect(
@ -337,7 +413,8 @@ export default withRoomContext(connect(
{
return (
prev.me === next.me &&
prev.settings === next.settings
prev.settings === next.settings &&
prev.peerVolumes[prev.me.id] === next[next.me.id]
);
}
}

View File

@ -18,7 +18,8 @@ const initialState =
raisedHand : false,
raisedHandInProgress : false,
loggedIn : false,
isSpeaking : false
isSpeaking : false,
isAutoMuted : true
};
const me = (state = initialState, action) =>
@ -162,6 +163,13 @@ const me = (state = initialState, action) =>
return { ...state, isSpeaking: flag };
}
case 'SET_AUTO_MUTED':
{
const { flag } = action.payload;
return { ...state, isAutoMuted: flag };
}
default:
return state;
}

View File

@ -31,9 +31,10 @@ const peerVolumes = (state = initialState, action) =>
case 'SET_PEER_VOLUME':
{
const { peerId, volume } = action.payload;
const { peerId } = action.payload;
const dBs = action.payload.volume < -100 ? -100 : action.payload.volume;
return { ...state, [peerId]: volume };
return { ...state, [peerId]: Math.round(dBs) };
}
default:

View File

@ -1,26 +1,28 @@
const initialState =
{
displayName : 'Guest',
selectedWebcam : null,
selectedAudioDevice : null,
advancedMode : false,
sampleRate : 48000,
channelCount : 1,
volume : 1.0,
autoGainControl : true,
echoCancellation : true,
noiseSuppression : true,
sampleSize : 16,
displayName : 'Guest',
selectedWebcam : null,
selectedAudioDevice : null,
advancedMode : false,
sampleRate : 48000,
channelCount : 1,
volume : 1.0,
autoGainControl : false,
echoCancellation : true,
noiseSuppression : true,
voiceActivatedUnmute : false,
noiseThreshold : -50,
sampleSize : 16,
// low, medium, high, veryhigh, ultra
resolution : window.config.defaultResolution || 'medium',
lastN : 4,
permanentTopBar : true,
hiddenControls : false,
showNotifications : true,
notificationSounds : true,
buttonControlBar : window.config.buttonControlBar || false,
drawerOverlayed : window.config.drawerOverlayed || true,
autoMuteThreshold : window.config.autoMuteThreshold || 4,
resolution : window.config.defaultResolution || 'medium',
lastN : 4,
permanentTopBar : true,
hiddenControls : false,
showNotifications : true,
notificationSounds : true,
buttonControlBar : window.config.buttonControlBar || false,
drawerOverlayed : window.config.drawerOverlayed || true,
autoMuteThreshold : window.config.autoMuteThreshold || 4,
...window.config.defaultAudio
};
@ -99,6 +101,20 @@ const settings = (state = initialState, action) =>
return { ...state, noiseSuppression };
}
case 'SET_VOICE_ACTIVATED_UNMUTE':
{
const { voiceActivatedUnmute } = action.payload;
return { ...state, voiceActivatedUnmute };
}
case 'SET_NOISE_THRESHOLD':
{
const { noiseThreshold } = action.payload;
return { ...state, noiseThreshold };
}
case 'SET_DEFAULT_AUDIO':
{
const { audio } = action.payload;

View File

@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "设置",
"settings.camera": "视频设备",
@ -190,6 +191,7 @@
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -118,6 +118,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Nastavení",
"settings.camera": "Kamera",
@ -189,6 +190,7 @@
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": "Video hinzufügen",
"label.promoteAllPeers": "Alle Teilnehmer reinlassen",
"label.moreActions": "Weitere Aktionen",
"label.version": null,
"settings.settings": "Einstellungen",
"settings.camera": "Kamera",
@ -190,6 +191,7 @@
"moderator.muteAudio": "Moderator hat dich stummgeschaltet",
"moderator.muteVideo": "Moderator hat dein Video gestoppt",
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Indstillinger",
"settings.camera": "Kamera",
@ -190,6 +191,7 @@
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Ρυθμίσεις",
"settings.camera": "Κάμερα",
@ -190,6 +191,7 @@
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": "Add video",
"label.promoteAllPeers": "Promote all",
"label.moreActions": "More actions",
"label.version": "Version",
"settings.settings": "Settings",
"settings.camera": "Camera",
@ -190,6 +191,7 @@
"moderator.muteAudio": "Moderator muted your audio",
"moderator.muteVideo": "Moderator muted your video",
"moderator.muteScreenSharing": "Moderator muted your screen sharing",
"moderator.stopScreenSharing": "Moderator stopped your screen sharing",
"unsupportedBrowser.titleUsnsupportedBrowser": "Detected unsupported browser!",
"unsupportedBrowser.titlewebrtcUnavailable": "Required functionality not available in your browser!",

View File

@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Ajustes",
"settings.camera": "Cámara",
@ -190,6 +191,7 @@
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Paramètres",
"settings.camera": "Caméra",
@ -189,6 +190,7 @@
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": "Dodaj video",
"label.promoteAllPeers": "Promoviraj sve",
"label.moreActions": "Više akcija",
"label.version": null,
"settings.settings": "Postavke",
"settings.camera": "Kamera",
@ -190,6 +191,7 @@
"moderator.muteAudio": "Moderator je utišao tvoj zvuk",
"moderator.muteVideo": "Moderator je zaustavio tvoj video",
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -66,7 +66,7 @@
"room.browsePeersSpotlight": "Résztvevők böngészése",
"room.stopAllScreenSharing": "Összes képernyőmegosztás leállítása",
"me.mutedPTT": "Némítva vagy, ha beszélnél nyomd le a szóköz billentyűt",
"me.mutedPTT": "Némítva vagy, ha beszélnél nyomd le a SZÓKÖZ billentyűt",
"roles.gotRole": "{role} szerepet kaptál",
"roles.lostRole": "Elvesztetted a {role} szerepet",
@ -119,6 +119,7 @@
"label.addVideo": "Videó hozzáadása",
"label.promoteAllPeers": "Mindenkit beengedek",
"label.moreActions": "További műveletek",
"label.version": "Verzió",
"settings.settings": "Beállítások",
"settings.camera": "Kamera",
@ -189,10 +190,10 @@
"moderator.clearFiles": "A moderátor kiürítette a file megosztás történelmet",
"moderator.muteAudio": "A moderátor elnémította a hangod",
"moderator.muteVideo": "A moderátor elnémította a videód",
"moderator.muteScreenSharing": "A moderátor leállította képernyőmegosztásod",
"moderator.muteScreenSharing": "A moderátor szünetelteti képernyőmegosztásod",
"moderator.stopScreenSharing": "A moderátor leállította a képernyőmegosztásod",
"unsupportedBrowser.titleUnsupportedBrowser": "A bőngésző verziód sajnos nem támogatott! :-(",
"unsupportedBrowser.titlewebrtcUnavailable": "A böngésződ egy szükséges funkciója nem elérhető!",
"unsupportedBrowser.bodyText": "Kérlek frissítsd a böngésződ, válts másik böngészőre, vagy ellenőrizd a böngésződ beállításait! Támogatott böngészők:"
}

View File

@ -119,6 +119,7 @@
"label.addVideo": "Aggiungi video",
"label.promoteAllPeers": "Promuovi tutti",
"label.moreActions": "Altre azioni",
"label.version": null,
"settings.settings": "Impostazioni",
"settings.camera": "Videocamera",
@ -190,9 +191,9 @@
"moderator.muteAudio": "Il moderatore ha mutato il tuo audio",
"moderator.muteVideo": "Il moderatore ha fermato il tuo video",
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
}

View File

@ -116,6 +116,7 @@
"label.advanced": "Advancēts",
"label.addVideo": "Pievienot video",
"label.moreActions": null,
"label.version": null,
"settings.settings": "Iestatījumi",
"settings.camera": "Kamera",
@ -184,6 +185,7 @@
"moderator.muteAudio": "Moderators noklusināja jūsu mikrofonu",
"moderator.muteVideo": "Moderators atslēdza jūsu kameru",
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": "Legg til video",
"label.promoteAllPeers": "Slipp inn alle",
"label.moreActions": "Flere handlinger",
"label.version": null,
"settings.settings": "Innstillinger",
"settings.camera": "Kamera",
@ -190,6 +191,7 @@
"moderator.muteAudio": "Moderator mutet lyden din",
"moderator.muteVideo": "Moderator mutet videoen din",
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": "Dodaj wideo",
"label.promoteAllPeers": "Wpuść wszystkich",
"label.moreActions": "Więcej akcji",
"label.version": null,
"settings.settings": "Ustawienia",
"settings.camera": "Kamera",
@ -190,6 +191,7 @@
"moderator.muteAudio": "Moderator wyciszył audio",
"moderator.muteVideo": "Moderator wyciszył twoje video",
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Definições",
"settings.camera": "Camera",
@ -190,6 +191,7 @@
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Setări",
"settings.camera": "Cameră video",
@ -190,6 +191,7 @@
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Ayarlar",
"settings.camera": "Kamera",
@ -181,7 +182,13 @@
"devices.cameraDisconnected": "Kamera bağlı değil",
"devices.cameraError": "Kameranıza erişilirken bir hata oluştu",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -118,6 +118,7 @@
"label.addVideo": "新增視訊",
"label.promoteAllPeers": "提升全部",
"label.moreActions": "更多",
"label.version": null,
"settings.settings": "設置",
"settings.camera": "視訊來源",
@ -189,6 +190,7 @@
"moderator.muteAudio": "您已被管理員靜音",
"moderator.muteVideo": "您的視訊已被管理員關閉",
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Налаштування",
"settings.camera": "Камера",
@ -190,6 +191,7 @@
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.muteScreenSharing": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,

View File

@ -17,3 +17,21 @@ export const idle = (callback, delay) =>
handle = setTimeout(callback, delay);
};
};
/**
* Error produced when a socket request has a timeout.
*/
export class SocketTimeoutError extends Error
{
constructor(message)
{
super(message);
this.name = 'SocketTimeoutError';
if (Error.hasOwnProperty('captureStackTrace')) // Just in V8.
Error.captureStackTrace(this, SocketTimeoutError);
else
this.stack = (new Error(message)).stack;
}
}

View File

@ -106,6 +106,12 @@
"no-invalid-regexp": 2,
"no-invalid-this": 2,
"no-irregular-whitespace": 2,
"no-trailing-spaces": [
"error",
{
"ignoreComments": true
}
],
"no-lonely-if": 2,
"no-mixed-operators": 2,
"no-mixed-spaces-and-tabs": 2,

View File

@ -276,6 +276,10 @@ module.exports =
// maxUsersPerRoom : 20,
// Room size before spreading to new router
routerScaleSize : 40,
// Socket timout value
requestTimeout : 20000,
// Socket retries when timeout
requestRetries : 3,
// Mediasoup settings
mediasoup :
{

View File

@ -3,6 +3,7 @@ const AwaitQueue = require('awaitqueue');
const axios = require('axios');
const Logger = require('./Logger');
const Lobby = require('./Lobby');
const { SocketTimeoutError } = require('./errors');
const { v4: uuidv4 } = require('uuid');
const jwt = require('jsonwebtoken');
const userRoles = require('../userRoles');
@ -1759,9 +1760,9 @@ class Room extends EventEmitter
if (called)
return;
called = true;
callback(new Error('Request timeout.'));
callback(new SocketTimeoutError('Request timed out'));
},
10000
config.requestTimeout || 20000
);
return (...args) =>
@ -1775,7 +1776,7 @@ class Room extends EventEmitter
};
}
_request(socket, method, data = {})
_sendRequest(socket, method, data = {})
{
return new Promise((resolve, reject) =>
{
@ -1797,6 +1798,33 @@ class Room extends EventEmitter
});
}
async _request(socket, method, data)
{
logger.debug('_request() [method:"%s", data:"%o"]', method, data);
const {
requestRetries = 3
} = config;
for (let tries = 0; tries < requestRetries; tries++)
{
try
{
return await this._sendRequest(socket, method, data);
}
catch (error)
{
if (
error instanceof SocketTimeoutError &&
tries < requestRetries
)
logger.warn('_request() | timeout, retrying [attempt:"%s"]', tries);
else
throw error;
}
}
}
_notification(socket, method, data = {}, broadcast = false, includeSender = false)
{
if (broadcast)

View File

@ -0,0 +1,22 @@
/**
* Error produced when a socket request has a timeout.
*/
class SocketTimeoutError extends Error
{
constructor(message)
{
super(message);
this.name = 'SocketTimeoutError';
if (Error.hasOwnProperty('captureStackTrace')) // Just in V8.
Error.captureStackTrace(this, SocketTimeoutError);
else
this.stack = (new Error(message)).stack;
}
}
module.exports =
{
SocketTimeoutError
};