Merge branch 'develop' into feat-network-indicator

auto_join_3.3
Roman Drozd 2020-05-20 16:47:02 +02:00
commit ddd8c36c67
62 changed files with 1100 additions and 483 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

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

View File

@ -5,6 +5,26 @@ var config =
developmentPort : 3443, developmentPort : 3443,
productionPort : 443, productionPort : 443,
/**
* Supported browsers version
* in bowser satisfy format.
* See more:
* https://www.npmjs.com/package/bowser#filtering-browsers
* Otherwise you got a unsupported browser page
*/
supportedBrowsers :
{
'windows' : {
'internet explorer' : '>12',
'microsoft edge' : '>18'
},
'safari' : '>12',
'firefox' : '>=60',
'chrome' : '>=74',
'opera' : '>=62',
'samsung internet for android' : '>=11.1.1.52'
},
/** /**
* If defaultResolution is set, it will override user settings when joining: * If defaultResolution is set, it will override user settings when joining:
* low ~ 320x240 * low ~ 320x240
@ -26,6 +46,15 @@ var config =
{ scaleResolutionDownBy: 1 } { scaleResolutionDownBy: 1 }
], ],
/**
* Alternative simulcast setting:
* [
* { maxBitRate: 50000 },
* { maxBitRate: 1000000 },
* { maxBitRate: 4800000 }
*],
**/
/** /**
* White listing browsers that support audio output device selection. * White listing browsers that support audio output device selection.
* It is not yet fully implemented in Firefox. * It is not yet fully implemented in Firefox.
@ -37,12 +66,13 @@ var config =
'opera' 'opera'
], ],
// Socket.io request timeout // Socket.io request timeout
requestTimeout : 10000, requestTimeout : 20000,
requestRetries : 3,
transportOptions : transportOptions :
{ {
tcp : true tcp : true
}, },
defaultAudio : defaultAudio :
{ {
sampleRate : 48000, sampleRate : 48000,
channelCount : 1, channelCount : 1,
@ -55,14 +85,16 @@ var config =
}, },
/** /**
* Set the auto mute / Push To Talk threshold * Set max number participants in one room that join
* default value is 4 * unmuted. Next participant will join automatically muted
* Default value is 4
* *
* Set it to 0 to disable auto mute functionality, * Set it to 0 to auto mute all,
* Set it to negative (-1) to never automatically auto mute
* but use it with caution * but use it with caution
* full mesh audio strongly decrease room capacity! * full mesh audio strongly decrease room capacity!
*/ */
autoMuteThreshold : 4, autoMuteThreshold : 4,
background : 'images/background.jpg', background : 'images/background.jpg',
defaultLayout : 'democratic', // democratic, filmstrip defaultLayout : 'democratic', // democratic, filmstrip
// If true, will show media control buttons in separate // If true, will show media control buttons in separate
@ -75,9 +107,12 @@ var config =
notificationPosition : 'right', notificationPosition : 'right',
// Timeout for autohiding topbar and button control bar // Timeout for autohiding topbar and button control bar
hideTimeout : 3000, hideTimeout : 3000,
// max number of participant that will be visible in
// as speaker
lastN : 4, lastN : 4,
mobileLastN : 1, mobileLastN : 1,
// Highest number of speakers user can select // Highest number of lastN the user can select manually in
// userinteface
maxLastN : 5, maxLastN : 5,
// If truthy, users can NOT change number of speakers visible // If truthy, users can NOT change number of speakers visible
lockLastN : false, lockLastN : false,

View File

@ -16,6 +16,44 @@
<title>Multiparty Meeting</title> <title>Multiparty Meeting</title>
<script src='%PUBLIC_URL%/config/config.js' type='text/javascript'></script> <script src='%PUBLIC_URL%/config/config.js' type='text/javascript'></script>
<!-- Show an error page to IE browsers -->
<script type='text/javascript'>
var fallback = '<style type="text/css">body{margin:40px auto;max-width:650px;line-height:1.6;font-size:18px;color:#444;padding:0 10px}h1,h2,h3{line-height:1.2}</style><header><h1>Your browser is not supported</h1><aside>You need to change to a different browser.</aside></header><h3>Supported browsers</h3><ul><li>Google Chrome/Chromium 55 +</li><li>Microsoft Edge 18 +</li><li>Mozilla Firefox 60 +</li><li>Apple Safari 12 +</li><li>Opera 62 +</li><li>Samsung Internet 11.1.1.52 +</li></ul>';
var fallbackCall = function() {
document.body.innerHTML = fallback;
};
if(navigator.userAgent.indexOf('MSIE') !== -1)
{
document.attachEvent('onreadystatechange', function() {
if (document.readyState === 'complete')
{
document.detachEvent('onreadystatechange', arguments.callee);
fallbackCall();
}
});
}
if (navigator.appVersion.indexOf('Trident/') > -1)
{
if (
document.readyState === 'complete' ||
(document.readyState !== 'loading' && !document.documentElement.doScroll)
)
{
document.removeEventListener('DOMContentLoaded', fallbackCall);
fallbackCall();
}
else
{
document.addEventListener('DOMContentLoaded', fallbackCall);
}
}
</script>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -1,6 +1,7 @@
import Logger from './Logger'; import Logger from './Logger';
import hark from 'hark'; import hark from 'hark';
import { getSignalingUrl } from './urlFactory'; import { getSignalingUrl } from './urlFactory';
import { SocketTimeoutError } from './utils';
import * as requestActions from './actions/requestActions'; import * as requestActions from './actions/requestActions';
import * as meActions from './actions/meActions'; import * as meActions from './actions/meActions';
import * as roomActions from './actions/roomActions'; import * as roomActions from './actions/roomActions';
@ -315,7 +316,7 @@ export default class RoomClient
{ {
const newPeerId = this._spotlights.getNextAsSelected( const newPeerId = this._spotlights.getNextAsSelected(
store.getState().room.selectedPeerId); store.getState().room.selectedPeerId);
if (newPeerId) this.setSelectedPeer(newPeerId); if (newPeerId) this.setSelectedPeer(newPeerId);
break; break;
} }
@ -575,7 +576,7 @@ export default class RoomClient
if (called) if (called)
return; return;
called = true; called = true;
callback(new Error('Request timeout.')); callback(new SocketTimeoutError('Request timed out'));
}, },
ROOM_OPTIONS.requestTimeout ROOM_OPTIONS.requestTimeout
); );
@ -591,13 +592,13 @@ export default class RoomClient
}; };
} }
sendRequest(method, data) _sendRequest(method, data)
{ {
return new Promise((resolve, reject) => return new Promise((resolve, reject) =>
{ {
if (!this._signalingSocket) if (!this._signalingSocket)
{ {
reject('No socket connection.'); reject('No socket connection');
} }
else else
{ {
@ -607,13 +608,9 @@ export default class RoomClient
this.timeoutCallback((err, response) => this.timeoutCallback((err, response) =>
{ {
if (err) if (err)
{
reject(err); reject(err);
}
else else
{
resolve(response); resolve(response);
}
}) })
); );
} }
@ -650,6 +647,33 @@ export default class RoomClient
} }
} }
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) async changeDisplayName(displayName)
{ {
logger.debug('changeDisplayName() [displayName:"%s"]', displayName); logger.debug('changeDisplayName() [displayName:"%s"]', displayName);
@ -805,7 +829,7 @@ export default class RoomClient
} }
}); });
torrent.on('done', () => torrent.on('done', () =>
{ {
store.dispatch( store.dispatch(
fileActions.setFileDone( fileActions.setFileDone(
@ -955,7 +979,7 @@ export default class RoomClient
{ {
await this.sendRequest( await this.sendRequest(
'resumeProducer', { producerId: this._micProducer.id }); 'resumeProducer', { producerId: this._micProducer.id });
store.dispatch( store.dispatch(
producerActions.setProducerResumed(this._micProducer.id)); producerActions.setProducerResumed(this._micProducer.id));
} }
@ -1007,23 +1031,23 @@ export default class RoomClient
} }
} }
disconnectLocalHark() disconnectLocalHark()
{ {
logger.debug('disconnectLocalHark() | Stopping harkStream.'); logger.debug('disconnectLocalHark() | Stopping harkStream.');
if (this._harkStream != null) if (this._harkStream != null)
{ {
this._harkStream.getAudioTracks()[0].stop(); this._harkStream.getAudioTracks()[0].stop();
this._harkStream = null; this._harkStream = null;
} }
if (this._hark != null) if (this._hark != null)
{ {
logger.debug('disconnectLocalHark() Stopping hark.'); logger.debug('disconnectLocalHark() Stopping hark.');
this._hark.stop(); this._hark.stop();
} }
} }
connectLocalHark(track) connectLocalHark(track)
{ {
logger.debug('connectLocalHark() | Track:%o', track); logger.debug('connectLocalHark() | Track:%o', track);
this._harkStream = new MediaStream(); this._harkStream = new MediaStream();
@ -1034,23 +1058,23 @@ export default class RoomClient
if (!this._harkStream.getAudioTracks()[0]) if (!this._harkStream.getAudioTracks()[0])
throw new Error('getMicStream():something went wrong with hark'); throw new Error('getMicStream():something went wrong with hark');
this._hark = hark(this._harkStream, this._hark = hark(this._harkStream,
{ {
play : false, play : false,
interval : 5, interval : 10,
threshold : store.getState().settings.noiseThreshold, threshold : store.getState().settings.noiseThreshold,
history : 30 history : 100
}); });
this._hark.lastVolume = -100; this._hark.lastVolume = -100;
this._hark.on('volume_change', (volume) => this._hark.on('volume_change', (volume) =>
{ {
volume = Math.round(volume); volume = Math.round(volume);
if (this._micProducer && volume !== Math.round(this._hark.lastVolume)) if (this._micProducer && (volume !== Math.round(this._hark.lastVolume)))
{ {
if (volume < this._hark.lastVolume * 1.02) if (volume < this._hark.lastVolume)
{ {
volume = this._hark.lastVolume * 1.02; volume = this._hark.lastVolume - Math.pow((volume - this._hark.lastVolume)/(100 + this._hark.lastVolume),4)*2;
} }
this._hark.lastVolume = volume; this._hark.lastVolume = volume;
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume)); store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
@ -1059,7 +1083,7 @@ export default class RoomClient
this._hark.on('speaking', () => this._hark.on('speaking', () =>
{ {
store.dispatch(meActions.setIsSpeaking(true)); store.dispatch(meActions.setIsSpeaking(true));
if ((store.getState().settings.voiceActivatedUnmute || if ((store.getState().settings.voiceActivatedUnmute ||
store.getState().me.isAutoMuted) && store.getState().me.isAutoMuted) &&
this._micProducer && this._micProducer &&
this._micProducer.paused) this._micProducer.paused)
@ -1071,9 +1095,9 @@ export default class RoomClient
this._hark.on('stopped_speaking', () => this._hark.on('stopped_speaking', () =>
{ {
store.dispatch(meActions.setIsSpeaking(false)); store.dispatch(meActions.setIsSpeaking(false));
if (store.getState().settings.voiceActivatedUnmute && if (store.getState().settings.voiceActivatedUnmute &&
this._micProducer && this._micProducer &&
!this._micProducer.paused) !this._micProducer.paused)
{ {
this._micProducer.pause(); this._micProducer.pause();
store.dispatch(meActions.setAutoMuted(true)); store.dispatch(meActions.setAutoMuted(true));
@ -1089,7 +1113,7 @@ export default class RoomClient
meActions.setAudioInProgress(true)); meActions.setAudioInProgress(true));
try try
{ {
const device = this._audioDevices[deviceId]; const device = this._audioDevices[deviceId];
if (!device) if (!device)
@ -1208,7 +1232,7 @@ export default class RoomClient
...VIDEO_CONSTRAINS[resolution] ...VIDEO_CONSTRAINS[resolution]
} }
}); });
if (stream) if (stream)
{ {
const track = stream.getVideoTracks()[0]; const track = stream.getVideoTracks()[0];
@ -1219,15 +1243,15 @@ export default class RoomClient
{ {
await this._webcamProducer.replaceTrack({ track }); await this._webcamProducer.replaceTrack({ track });
} }
else else
{ {
this._webcamProducer = await this._sendTransport.produce({ this._webcamProducer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'webcam' source : 'webcam'
} }
}); });
} }
store.dispatch( store.dispatch(
@ -1237,7 +1261,7 @@ export default class RoomClient
{ {
logger.warn('getVideoTracks Error: First Video Track is null'); logger.warn('getVideoTracks Error: First Video Track is null');
} }
} }
else else
{ {
@ -1271,7 +1295,7 @@ export default class RoomClient
if (!device) if (!device)
throw new Error('no webcam devices'); throw new Error('no webcam devices');
logger.debug( logger.debug(
'changeWebcam() | new selected webcam [device:%o]', 'changeWebcam() | new selected webcam [device:%o]',
device); device);
@ -1299,17 +1323,17 @@ export default class RoomClient
{ {
await this._webcamProducer.replaceTrack({ track }); await this._webcamProducer.replaceTrack({ track });
} }
else else
{ {
this._webcamProducer = await this._sendTransport.produce({ this._webcamProducer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'webcam' source : 'webcam'
} }
}); });
} }
store.dispatch( store.dispatch(
producerActions.setProducerTrack(this._webcamProducer.id, track)); producerActions.setProducerTrack(this._webcamProducer.id, track));
@ -1318,7 +1342,7 @@ export default class RoomClient
{ {
logger.warn('getVideoTracks Error: First Video Track is null'); logger.warn('getVideoTracks Error: First Video Track is null');
} }
} }
else else
{ {
@ -2106,7 +2130,7 @@ export default class RoomClient
const { displayName } = store.getState().settings; const { displayName } = store.getState().settings;
const { picture } = store.getState().me; const { picture } = store.getState().me;
await this.sendRequest('changeDisplayName', { displayName }); await this.sendRequest('changeDisplayName', { displayName });
await this.sendRequest('changePicture', { picture }); await this.sendRequest('changePicture', { picture });
break; break;
@ -2115,10 +2139,10 @@ export default class RoomClient
case 'signInRequired': case 'signInRequired':
{ {
store.dispatch(roomActions.setSignInRequired(true)); store.dispatch(roomActions.setSignInRequired(true));
break; break;
} }
case 'overRoomLimit': case 'overRoomLimit':
{ {
store.dispatch(roomActions.setOverRoomLimit(true)); store.dispatch(roomActions.setOverRoomLimit(true));
@ -2134,24 +2158,24 @@ export default class RoomClient
store.dispatch(roomActions.toggleJoined()); store.dispatch(roomActions.toggleJoined());
store.dispatch(roomActions.setInLobby(false)); store.dispatch(roomActions.setInLobby(false));
await this._joinRoom({ joinVideo }); await this._joinRoom({ joinVideo });
break; break;
} }
case 'roomBack': case 'roomBack':
{ {
await this._joinRoom({ joinVideo }); await this._joinRoom({ joinVideo });
break; break;
} }
case 'lockRoom': case 'lockRoom':
{ {
store.dispatch( store.dispatch(
roomActions.setRoomLocked()); roomActions.setRoomLocked());
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
@ -2159,15 +2183,15 @@ export default class RoomClient
defaultMessage : 'Room is now locked' defaultMessage : 'Room is now locked'
}) })
})); }));
break; break;
} }
case 'unlockRoom': case 'unlockRoom':
{ {
store.dispatch( store.dispatch(
roomActions.setRoomUnLocked()); roomActions.setRoomUnLocked());
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
@ -2175,21 +2199,21 @@ export default class RoomClient
defaultMessage : 'Room is now unlocked' defaultMessage : 'Room is now unlocked'
}) })
})); }));
break; break;
} }
case 'parkedPeer': case 'parkedPeer':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;
store.dispatch( store.dispatch(
lobbyPeerActions.addLobbyPeer(peerId)); lobbyPeerActions.addLobbyPeer(peerId));
store.dispatch( store.dispatch(
roomActions.setToolbarsVisible(true)); roomActions.setToolbarsVisible(true));
this._soundNotification(); this._soundNotification();
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
@ -2197,7 +2221,7 @@ export default class RoomClient
defaultMessage : 'New participant entered the lobby' defaultMessage : 'New participant entered the lobby'
}) })
})); }));
break; break;
} }
@ -2226,7 +2250,7 @@ export default class RoomClient
) )
); );
}); });
store.dispatch( store.dispatch(
roomActions.setToolbarsVisible(true)); roomActions.setToolbarsVisible(true));
@ -2243,14 +2267,14 @@ export default class RoomClient
break; break;
} }
case 'lobby:peerClosed': case 'lobby:peerClosed':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;
store.dispatch( store.dispatch(
lobbyPeerActions.removeLobbyPeer(peerId)); lobbyPeerActions.removeLobbyPeer(peerId));
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
@ -2258,10 +2282,10 @@ export default class RoomClient
defaultMessage : 'Participant in lobby left' defaultMessage : 'Participant in lobby left'
}) })
})); }));
break; break;
} }
case 'lobby:promotedPeer': case 'lobby:promotedPeer':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;
@ -2271,7 +2295,7 @@ export default class RoomClient
break; break;
} }
case 'lobby:changeDisplayName': case 'lobby:changeDisplayName':
{ {
const { peerId, displayName } = notification.data; const { peerId, displayName } = notification.data;
@ -2291,11 +2315,11 @@ export default class RoomClient
break; break;
} }
case 'lobby:changePicture': case 'lobby:changePicture':
{ {
const { peerId, picture } = notification.data; const { peerId, picture } = notification.data;
store.dispatch( store.dispatch(
lobbyPeerActions.setLobbyPeerPicture(picture, peerId)); lobbyPeerActions.setLobbyPeerPicture(picture, peerId));
@ -2313,7 +2337,7 @@ export default class RoomClient
case 'setAccessCode': case 'setAccessCode':
{ {
const { accessCode } = notification.data; const { accessCode } = notification.data;
store.dispatch( store.dispatch(
roomActions.setAccessCode(accessCode)); roomActions.setAccessCode(accessCode));
@ -2327,14 +2351,14 @@ export default class RoomClient
break; break;
} }
case 'setJoinByAccessCode': case 'setJoinByAccessCode':
{ {
const { joinByAccessCode } = notification.data; const { joinByAccessCode } = notification.data;
store.dispatch( store.dispatch(
roomActions.setJoinByAccessCode(joinByAccessCode)); roomActions.setJoinByAccessCode(joinByAccessCode));
if (joinByAccessCode) if (joinByAccessCode)
{ {
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
@ -2345,7 +2369,7 @@ export default class RoomClient
}) })
})); }));
} }
else else
{ {
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
@ -2358,20 +2382,20 @@ export default class RoomClient
break; break;
} }
case 'activeSpeaker': case 'activeSpeaker':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;
store.dispatch( store.dispatch(
roomActions.setRoomActiveSpeaker(peerId)); roomActions.setRoomActiveSpeaker(peerId));
if (peerId && peerId !== this._peerId) if (peerId && peerId !== this._peerId)
this._spotlights.handleActiveSpeaker(peerId); this._spotlights.handleActiveSpeaker(peerId);
break; break;
} }
case 'changeDisplayName': case 'changeDisplayName':
{ {
const { peerId, displayName, oldDisplayName } = notification.data; const { peerId, displayName, oldDisplayName } = notification.data;
@ -2579,74 +2603,74 @@ export default class RoomClient
{ {
const { consumerId } = notification.data; const { consumerId } = notification.data;
const consumer = this._consumers.get(consumerId); const consumer = this._consumers.get(consumerId);
if (!consumer) if (!consumer)
break; break;
consumer.close(); consumer.close();
if (consumer.hark != null) if (consumer.hark != null)
consumer.hark.stop(); consumer.hark.stop();
this._consumers.delete(consumerId); this._consumers.delete(consumerId);
const { peerId } = consumer.appData; const { peerId } = consumer.appData;
store.dispatch( store.dispatch(
consumerActions.removeConsumer(consumerId, peerId)); consumerActions.removeConsumer(consumerId, peerId));
break; break;
} }
case 'consumerPaused': case 'consumerPaused':
{ {
const { consumerId } = notification.data; const { consumerId } = notification.data;
const consumer = this._consumers.get(consumerId); const consumer = this._consumers.get(consumerId);
if (!consumer) if (!consumer)
break; break;
store.dispatch( store.dispatch(
consumerActions.setConsumerPaused(consumerId, 'remote')); consumerActions.setConsumerPaused(consumerId, 'remote'));
break; break;
} }
case 'consumerResumed': case 'consumerResumed':
{ {
const { consumerId } = notification.data; const { consumerId } = notification.data;
const consumer = this._consumers.get(consumerId); const consumer = this._consumers.get(consumerId);
if (!consumer) if (!consumer)
break; break;
store.dispatch( store.dispatch(
consumerActions.setConsumerResumed(consumerId, 'remote')); consumerActions.setConsumerResumed(consumerId, 'remote'));
break; break;
} }
case 'consumerLayersChanged': case 'consumerLayersChanged':
{ {
const { consumerId, spatialLayer, temporalLayer } = notification.data; const { consumerId, spatialLayer, temporalLayer } = notification.data;
const consumer = this._consumers.get(consumerId); const consumer = this._consumers.get(consumerId);
if (!consumer) if (!consumer)
break; break;
store.dispatch(consumerActions.setConsumerCurrentLayers( store.dispatch(consumerActions.setConsumerCurrentLayers(
consumerId, spatialLayer, temporalLayer)); consumerId, spatialLayer, temporalLayer));
break; break;
} }
case 'consumerScore': case 'consumerScore':
{ {
const { consumerId, score } = notification.data; const { consumerId, score } = notification.data;
store.dispatch( store.dispatch(
consumerActions.setConsumerScore(consumerId, score)); consumerActions.setConsumerScore(consumerId, score));
break; break;
} }
@ -2690,7 +2714,7 @@ export default class RoomClient
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
id : 'moderator.muteScreenSharingModerator', id : 'moderator.stopScreenSharing',
defaultMessage : 'Moderator stopped your screen sharing' defaultMessage : 'Moderator stopped your screen sharing'
}) })
})); }));
@ -2760,7 +2784,7 @@ export default class RoomClient
break; break;
} }
default: default:
{ {
logger.error( logger.error(
@ -2807,7 +2831,7 @@ export default class RoomClient
this._webTorrent.on('error', (error) => this._webTorrent.on('error', (error) =>
{ {
logger.error('Filesharing [error:"%o"]', error); logger.error('Filesharing [error:"%o"]', error);
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
@ -3029,7 +3053,7 @@ export default class RoomClient
); );
} }
locked ? locked ?
store.dispatch(roomActions.setRoomLocked()) : store.dispatch(roomActions.setRoomLocked()) :
store.dispatch(roomActions.setRoomUnLocked()); store.dispatch(roomActions.setRoomUnLocked());
@ -3053,16 +3077,20 @@ export default class RoomClient
if (!this._muted) if (!this._muted)
{ {
await this.enableMic(); await this.enableMic();
const { autoMuteThreshold } = store.getState().settings; let autoMuteThreshold = 4;
if (autoMuteThreshold && peers.length > autoMuteThreshold) if ('autoMuteThreshold' in window.config)
{
autoMuteThreshold = window.config.autoMuteThreshold;
}
if (autoMuteThreshold && peers.length >= autoMuteThreshold)
this.muteMic(); this.muteMic();
} }
if (joinVideo && this._mediasoupDevice.canProduce('video')) if (joinVideo && this._mediasoupDevice.canProduce('video'))
this.enableWebcam(); this.enableWebcam();
} }
await this._updateAudioOutputDevices(); await this._updateAudioOutputDevices();
const { selectedAudioOutputDevice } = store.getState().settings; const { selectedAudioOutputDevice } = store.getState().settings;
@ -3075,7 +3103,7 @@ export default class RoomClient
) )
); );
} }
store.dispatch(roomActions.setRoomState('connected')); store.dispatch(roomActions.setRoomState('connected'));
// Clean all the existing notifications. // Clean all the existing notifications.
@ -3259,7 +3287,7 @@ export default class RoomClient
if (!device) if (!device)
throw new Error('no webcam devices'); throw new Error('no webcam devices');
logger.debug( logger.debug(
'addExtraVideo() | new selected webcam [device:%o]', 'addExtraVideo() | new selected webcam [device:%o]',
device); device);
@ -3304,7 +3332,7 @@ export default class RoomClient
{ {
videoGoogleStartBitrate : 1000 videoGoogleStartBitrate : 1000
}, },
appData : appData :
{ {
source : 'extravideo' source : 'extravideo'
} }
@ -3314,7 +3342,7 @@ export default class RoomClient
{ {
producer = await this._sendTransport.produce({ producer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'extravideo' source : 'extravideo'
} }
@ -3408,7 +3436,7 @@ export default class RoomClient
if (!device) if (!device)
throw new Error('no audio devices'); throw new Error('no audio devices');
logger.debug( logger.debug(
'enableMic() | new selected audio device [device:%o]', 'enableMic() | new selected audio device [device:%o]',
device); device);
@ -3445,7 +3473,7 @@ export default class RoomClient
opusPtime : '3', opusPtime : '3',
opusMaxPlaybackRate : 48000 opusMaxPlaybackRate : 48000
}, },
appData : appData :
{ source: 'mic' } { source: 'mic' }
}); });
@ -3605,7 +3633,7 @@ export default class RoomClient
{ {
videoGoogleStartBitrate : 1000 videoGoogleStartBitrate : 1000
}, },
appData : appData :
{ {
source : 'screen' source : 'screen'
} }
@ -3615,7 +3643,7 @@ export default class RoomClient
{ {
this._screenSharingProducer = await this._sendTransport.produce({ this._screenSharingProducer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'screen' source : 'screen'
} }
@ -3733,7 +3761,7 @@ export default class RoomClient
if (!device) if (!device)
throw new Error('no webcam devices'); throw new Error('no webcam devices');
logger.debug( logger.debug(
'_setWebcamProducer() | new selected webcam [device:%o]', '_setWebcamProducer() | new selected webcam [device:%o]',
device); device);
@ -3776,7 +3804,7 @@ export default class RoomClient
{ {
videoGoogleStartBitrate : 1000 videoGoogleStartBitrate : 1000
}, },
appData : appData :
{ {
source : 'webcam' source : 'webcam'
} }
@ -3786,7 +3814,7 @@ export default class RoomClient
{ {
this._webcamProducer = await this._sendTransport.produce({ this._webcamProducer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'webcam' source : 'webcam'
} }

View File

@ -60,7 +60,7 @@ export default class Spotlights extends EventEmitter
const oldIndex = this._unmutablePeerList.indexOf(peerId); const oldIndex = this._unmutablePeerList.indexOf(peerId);
let index = oldIndex; let index = oldIndex;
index++; index++;
for (let i = 0; i < this._unmutablePeerList.length; i++) for (let i = 0; i < this._unmutablePeerList.length; i++)
{ {
@ -94,7 +94,7 @@ export default class Spotlights extends EventEmitter
const oldIndex = this._unmutablePeerList.indexOf(peerId); const oldIndex = this._unmutablePeerList.indexOf(peerId);
let index = oldIndex; let index = oldIndex;
index--; index--;
for (let i = 0; i < this._unmutablePeerList.length; i++) for (let i = 0; i < this._unmutablePeerList.length; i++)
{ {
@ -119,7 +119,7 @@ export default class Spotlights extends EventEmitter
logger.debug('setPeerSpotlight() [peerId:"%s"]', peerId); logger.debug('setPeerSpotlight() [peerId:"%s"]', peerId);
const index = this._selectedSpotlights.indexOf(peerId); const index = this._selectedSpotlights.indexOf(peerId);
if (index !== -1) if (index !== -1)
{ {
this._selectedSpotlights = []; this._selectedSpotlights = [];
@ -177,7 +177,7 @@ export default class Spotlights extends EventEmitter
{ {
logger.debug( logger.debug(
'room "newpeer" event [id: "%s"]', id); 'room "newpeer" event [id: "%s"]', id);
if (this._peerList.indexOf(id) === -1) // We don't have this peer in the list if (this._peerList.indexOf(id) === -1) // We don't have this peer in the list
{ {
logger.debug('_handlePeer() | adding peer [peerId: "%s"]', id); logger.debug('_handlePeer() | adding peer [peerId: "%s"]', id);

View File

@ -9,7 +9,7 @@ export const addResponseMessage = (message) =>
type : 'ADD_NEW_RESPONSE_MESSAGE', type : 'ADD_NEW_RESPONSE_MESSAGE',
payload : { message } payload : { message }
}); });
export const addChatHistory = (chatHistory) => export const addChatHistory = (chatHistory) =>
({ ({
type : 'ADD_CHAT_HISTORY', type : 'ADD_CHAT_HISTORY',

View File

@ -80,13 +80,13 @@ export const setAudioOutputInProgress = (flag) =>
type : 'SET_AUDIO_OUTPUT_IN_PROGRESS', type : 'SET_AUDIO_OUTPUT_IN_PROGRESS',
payload : { flag } payload : { flag }
}); });
export const setWebcamInProgress = (flag) => export const setWebcamInProgress = (flag) =>
({ ({
type : 'SET_WEBCAM_IN_PROGRESS', type : 'SET_WEBCAM_IN_PROGRESS',
payload : { flag } payload : { flag }
}); });
export const setScreenShareInProgress = (flag) => export const setScreenShareInProgress = (flag) =>
({ ({
type : 'SET_SCREEN_SHARE_IN_PROGRESS', type : 'SET_SCREEN_SHARE_IN_PROGRESS',

View File

@ -26,13 +26,13 @@ export const setPeerVideoInProgress = (peerId, flag) =>
type : 'SET_PEER_VIDEO_IN_PROGRESS', type : 'SET_PEER_VIDEO_IN_PROGRESS',
payload : { peerId, flag } payload : { peerId, flag }
}); });
export const setPeerAudioInProgress = (peerId, flag) => export const setPeerAudioInProgress = (peerId, flag) =>
({ ({
type : 'SET_PEER_AUDIO_IN_PROGRESS', type : 'SET_PEER_AUDIO_IN_PROGRESS',
payload : { peerId, flag } payload : { peerId, flag }
}); });
export const setPeerScreenInProgress = (peerId, flag) => export const setPeerScreenInProgress = (peerId, flag) =>
({ ({
type : 'SET_PEER_SCREEN_IN_PROGRESS', type : 'SET_PEER_SCREEN_IN_PROGRESS',

View File

@ -73,14 +73,14 @@ export const setNoiseSuppression = (noiseSuppression) =>
export const setVoiceActivatedUnmute = (voiceActivatedUnmute) => export const setVoiceActivatedUnmute = (voiceActivatedUnmute) =>
({ ({
type: 'SET_VOICE_ACTIVATED_UNMUTE', type : 'SET_VOICE_ACTIVATED_UNMUTE',
payload: { voiceActivatedUnmute } payload : { voiceActivatedUnmute }
}); });
export const setNoiseThreshold = (noiseThreshold) => export const setNoiseThreshold = (noiseThreshold) =>
({ ({
type: 'SET_NOISE_THRESHOLD', type : 'SET_NOISE_THRESHOLD',
payload: { noiseThreshold } payload : { noiseThreshold }
}); });
export const setDefaultAudio = (audio) => export const setDefaultAudio = (audio) =>
@ -89,21 +89,6 @@ export const setDefaultAudio = (audio) =>
payload : { audio } payload : { audio }
}); });
export const toggleEchoCancellation = () =>
({
type : 'TOGGLE_ECHO_CANCELLATION'
});
export const toggleAutoGainControl = () =>
({
type : 'TOGGLE_AUTO_GAIN_CONTROL'
});
export const toggleNoiseSuppression = () =>
({
type : 'TOGGLE_NOISE_SUPPRESSION'
});
export const toggleHiddenControls = () => export const toggleHiddenControls = () =>
({ ({
type : 'TOGGLE_HIDDEN_CONTROLS' type : 'TOGGLE_HIDDEN_CONTROLS'

View File

@ -39,7 +39,7 @@ const ListLobbyPeer = (props) =>
const picture = peer.picture || EmptyAvatar; const picture = peer.picture || EmptyAvatar;
return ( return (
<ListItem <ListItem
className={classnames(classes.root)} className={classnames(classes.root)}
key={peer.peerId} key={peer.peerId}
button button

View File

@ -77,7 +77,7 @@ const LockDialog = ({
/> />
</DialogTitle> </DialogTitle>
{ lobbyPeers.length > 0 ? { lobbyPeers.length > 0 ?
<List <List
dense dense
subheader={ subheader={
<ListSubheader component='div'> <ListSubheader component='div'>

View File

@ -287,9 +287,9 @@ const Me = (props) =>
defaultMessage : 'Start screen sharing' defaultMessage : 'Start screen sharing'
}); });
} }
const [ const [
screenShareTooltipOpen, screenShareTooltipOpen,
screenShareTooltipSetOpen screenShareTooltipSetOpen
] = React.useState(false); ] = React.useState(false);
const screenShareTooltipHandleClose = () => const screenShareTooltipHandleClose = () =>
@ -380,7 +380,7 @@ const Me = (props) =>
}} }}
style={spacingStyle} style={spacingStyle}
> >
{ me.browser.platform !== 'mobile' && smallContainer && { me.browser.platform !== 'mobile' && smallContainer &&
<div className={classnames( <div className={classnames(
classes.ptt, classes.ptt,
@ -457,8 +457,8 @@ const Me = (props) =>
className={classes.smallContainer} className={classes.smallContainer}
disabled={!me.canSendMic || me.audioInProgress} disabled={!me.canSendMic || me.audioInProgress}
color={ color={
micState === 'on' ? micState === 'on' ?
settings.voiceActivatedUnmute && !me.isAutoMuted ? settings.voiceActivatedUnmute && !me.isAutoMuted ?
'primary' 'primary'
: 'default' : 'default'
: 'secondary'} : 'secondary'}
@ -474,7 +474,7 @@ const Me = (props) =>
}} }}
> >
{ micState === 'on' ? { micState === 'on' ?
<MicIcon <MicIcon
color={me.isAutoMuted ? 'secondary' : 'primary'} color={me.isAutoMuted ? 'secondary' : 'primary'}
style={{ opacity: noiseVolume }} style={{ opacity: noiseVolume }}
/> />
@ -492,9 +492,9 @@ const Me = (props) =>
})} })}
className={classes.fab} className={classes.fab}
disabled={!me.canSendMic || me.audioInProgress} disabled={!me.canSendMic || me.audioInProgress}
color={micState === 'on' ? color={micState === 'on' ?
settings.voiceActivatedUnmute && !me.isAutoMuted? 'primary' settings.voiceActivatedUnmute && !me.isAutoMuted? 'primary'
: 'default' : 'default'
: 'secondary'} : 'secondary'}
size='large' size='large'
onClick={() => onClick={() =>
@ -509,8 +509,10 @@ const Me = (props) =>
> >
{ micState === 'on' ? { micState === 'on' ?
<MicIcon <MicIcon
color={me.isAutoMuted ? 'secondary' : 'primary'} color={me.isAutoMuted && settings.voiceActivatedUnmute ?
style={me.isAutoMuted ? { opacity: noiseVolume } 'secondary' : 'primary'}
style={me.isAutoMuted && settings.voiceActivatedUnmute ?
{ opacity: noiseVolume }
: { opacity: 1 }} : { opacity: 1 }}
/> />
: :
@ -574,9 +576,9 @@ const Me = (props) =>
} }
</Tooltip> </Tooltip>
{ me.browser.platform !== 'mobile' && { me.browser.platform !== 'mobile' &&
<Tooltip open={screenShareTooltipOpen} <Tooltip open={screenShareTooltipOpen}
onClose={screenShareTooltipHandleClose} onClose={screenShareTooltipHandleClose}
onOpen={screenShareTooltipHandleOpen} onOpen={screenShareTooltipHandleOpen}
title={screenTip} placement='left' title={screenTip} placement='left'
> >
{ smallContainer ? { smallContainer ?
@ -868,7 +870,7 @@ const Me = (props) =>
defaultMessage='ME' defaultMessage='ME'
/> />
</p> </p>
<VideoView <VideoView
isMe isMe
isScreen isScreen
@ -913,15 +915,15 @@ const makeMapStateToProps = () =>
const mapStateToProps = (state) => const mapStateToProps = (state) =>
{ {
let volume; let volume;
// noiseVolume under threshold // noiseVolume under threshold
if (state.peerVolumes[state.me.id] < state.settings.noiseThreshold) if (state.peerVolumes[state.me.id] < state.settings.noiseThreshold)
{ {
// noiseVolume mapped to range 0.5 ... 1 (threshold switch) // noiseVolume mapped to range 0.5 ... 1 (threshold switch)
volume = 1 + ((Math.abs(state.peerVolumes[state.me.id] - volume = 1 + ((Math.abs(state.peerVolumes[state.me.id] -
state.settings.noiseThreshold) / (-120 - state.settings.noiseThreshold) / (-120 -
state.settings.noiseThreshold))); state.settings.noiseThreshold)));
} }
// noiseVolume over threshold: no noise but voice // noiseVolume over threshold: no noise but voice
else { volume = 0; } else { volume = 0; }
@ -949,7 +951,7 @@ export default withRoomContext(connect(
return ( return (
prev.room === next.room && prev.room === next.room &&
prev.me === next.me && prev.me === next.me &&
Math.round(prev.peerVolumes[prev.me.id]) === Math.round(prev.peerVolumes[prev.me.id]) ===
Math.round(next.peerVolumes[next.me.id]) && Math.round(next.peerVolumes[next.me.id]) &&
prev.peers === next.peers && prev.peers === next.peers &&
prev.producers === next.producers && prev.producers === next.producers &&

View File

@ -228,14 +228,14 @@ const Peer = (props) =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
setHover(true); setHover(true);
}} }}
onTouchEnd={() => onTouchEnd={() =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
touchTimeout = setTimeout(() => touchTimeout = setTimeout(() =>
{ {
setHover(false); setHover(false);
@ -445,14 +445,14 @@ const Peer = (props) =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
setHover(true); setHover(true);
}} }}
onTouchEnd={() => onTouchEnd={() =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
touchTimeout = setTimeout(() => touchTimeout = setTimeout(() =>
{ {
setHover(false); setHover(false);
@ -471,7 +471,7 @@ const Peer = (props) =>
</p> </p>
</div> </div>
} }
<div <div
className={classnames(classes.controls, hover ? 'hover' : null)} className={classnames(classes.controls, hover ? 'hover' : null)}
onMouseOver={() => setHover(true)} onMouseOver={() => setHover(true)}
@ -480,14 +480,14 @@ const Peer = (props) =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
setHover(true); setHover(true);
}} }}
onTouchEnd={() => onTouchEnd={() =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
touchTimeout = setTimeout(() => touchTimeout = setTimeout(() =>
{ {
setHover(false); setHover(false);
@ -544,7 +544,7 @@ const Peer = (props) =>
} }
</Tooltip> </Tooltip>
} }
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'label.fullscreen', id : 'label.fullscreen',
@ -588,7 +588,7 @@ const Peer = (props) =>
} }
</Tooltip> </Tooltip>
</div> </div>
<VideoView <VideoView
showQuality showQuality
advancedMode={advancedMode} advancedMode={advancedMode}
@ -629,14 +629,14 @@ const Peer = (props) =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
setHover(true); setHover(true);
}} }}
onTouchEnd={() => onTouchEnd={() =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
touchTimeout = setTimeout(() => touchTimeout = setTimeout(() =>
{ {
setHover(false); setHover(false);
@ -663,7 +663,7 @@ const Peer = (props) =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
setHover(true); setHover(true);
}} }}
onTouchEnd={() => onTouchEnd={() =>
@ -671,7 +671,7 @@ const Peer = (props) =>
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
touchTimeout = setTimeout(() => touchTimeout = setTimeout(() =>
{ {
setHover(false); setHover(false);

View File

@ -58,27 +58,27 @@ const styles = () =>
'&.level6' : '&.level6' :
{ {
height : '60%', height : '60%',
backgroundColor : 'rgba(255, 0, 0, 0.65)' backgroundColor : 'rgba(255, 165, 0, 0.65)'
}, },
'&.level7' : '&.level7' :
{ {
height : '70%', height : '70%',
backgroundColor : 'rgba(255, 0, 0, 0.65)' backgroundColor : 'rgba(255, 100, 0, 0.65)'
}, },
'&.level8' : '&.level8' :
{ {
height : '80%', height : '80%',
backgroundColor : 'rgba(0, 0, 0, 0.65)' backgroundColor : 'rgba(255, 60, 0, 0.65)'
}, },
'&.level9' : '&.level9' :
{ {
height : '90%', height : '90%',
backgroundColor : 'rgba(0, 0, 0, 0.65)' backgroundColor : 'rgba(255, 30, 0, 0.65)'
}, },
'&.level10' : '&.level10' :
{ {
height : '100%', height : '100%',
backgroundColor : 'rgba(0, 0, 0, 0.65)' backgroundColor : 'rgba(255, 0, 0, 0.65)'
} }
}, },
volumeSmall : volumeSmall :

View File

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

View File

@ -107,13 +107,13 @@ const Help = ({
} }
/> />
</Tabs> </Tabs>
<DialogContent dividers='true'> <DialogContent dividers='true'>
<DialogContentText> <DialogContentText>
{shortcuts.map((value, index) => {shortcuts.map((value, index) =>
{ {
return ( return (
<div key={index} className={classes.shortcuts}> <div key={index} className={classes.shortcuts}>
<Paper className={classes.paper}> <Paper className={classes.paper}>
{value.key} {value.key}
</Paper> </Paper>
<FormattedMessage <FormattedMessage
@ -134,7 +134,7 @@ const Help = ({
/> />
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );
}; };
@ -142,7 +142,7 @@ Help.propTypes =
{ {
roomClient : PropTypes.object.isRequired, roomClient : PropTypes.object.isRequired,
helpOpen : PropTypes.bool.isRequired, helpOpen : PropTypes.bool.isRequired,
handleCloseHelp : PropTypes.func.isRequired, handleCloseHelp : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };

View File

@ -312,7 +312,7 @@ const TopBar = (props) =>
</Typography> </Typography>
<div className={classes.grow} /> <div className={classes.grow} />
<div className={classes.sectionDesktop}> <div className={classes.sectionDesktop}>
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'label.moreActions', id : 'label.moreActions',
defaultMessage : 'More actions' defaultMessage : 'More actions'
@ -350,7 +350,7 @@ const TopBar = (props) =>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
} }
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'tooltip.participants', id : 'tooltip.participants',
defaultMessage : 'Show participants' defaultMessage : 'Show participants'
@ -421,7 +421,7 @@ const TopBar = (props) =>
</span> </span>
</Tooltip> </Tooltip>
{ lobbyPeers.length > 0 && { lobbyPeers.length > 0 &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'tooltip.lobby', id : 'tooltip.lobby',
defaultMessage : 'Show lobby' defaultMessage : 'Show lobby'
@ -457,7 +457,7 @@ const TopBar = (props) =>
})} })}
className={classes.actionButton} className={classes.actionButton}
color='inherit' color='inherit'
onClick={() => onClick={() =>
{ {
loggedIn ? roomClient.logout() : roomClient.login(); loggedIn ? roomClient.logout() : roomClient.login();
}} }}
@ -472,6 +472,34 @@ const TopBar = (props) =>
} }
</div> </div>
<div className={classes.sectionMobile}> <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 <IconButton
aria-haspopup='true' aria-haspopup='true'
onClick={handleMobileMenuOpen} onClick={handleMobileMenuOpen}
@ -480,34 +508,6 @@ const TopBar = (props) =>
<MoreIcon /> <MoreIcon />
</IconButton> </IconButton>
</div> </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} /> <div className={classes.divider} />
<Button <Button
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
@ -558,8 +558,8 @@ const TopBar = (props) =>
/> />
</p> </p>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => onClick={() =>
{ {
handleMenuClose(); handleMenuClose();
setHelpOpen(!room.helpOpen); setHelpOpen(!room.helpOpen);
@ -578,8 +578,8 @@ const TopBar = (props) =>
/> />
</p> </p>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => onClick={() =>
{ {
handleMenuClose(); handleMenuClose();
setAboutOpen(!room.aboutOpen); setAboutOpen(!room.aboutOpen);
@ -612,7 +612,7 @@ const TopBar = (props) =>
{ loginEnabled && { loginEnabled &&
<MenuItem <MenuItem
aria-label={loginTooltip} aria-label={loginTooltip}
onClick={() => onClick={() =>
{ {
handleMenuClose(); handleMenuClose();
loggedIn ? roomClient.logout() : roomClient.login(); loggedIn ? roomClient.logout() : roomClient.login();
@ -697,33 +697,6 @@ const TopBar = (props) =>
/> />
</p> </p>
</MenuItem> </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 <MenuItem
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'tooltip.participants', id : 'tooltip.participants',

View File

@ -327,7 +327,7 @@ const JoinDialog = ({
/> />
</Button> </Button>
</DialogActions> </DialogActions>
: :
<DialogContent> <DialogContent>
<DialogContentText <DialogContentText
className={classes.green} className={classes.green}

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import marked from 'marked'; import marked from 'marked';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
@ -14,7 +14,7 @@ linkRenderer.link = (href, title, text) =>
{ {
title = title ? title : href; title = title ? title : href;
text = text ? text : href; text = text ? text : href;
return `<a target='_blank' href='${ href }' title='${ title }'>${ text }</a>`; return `<a target='_blank' href='${ href }' title='${ title }'>${ text }</a>`;
}; };

View File

@ -60,7 +60,7 @@ class MessageList extends React.Component
myPicture, myPicture,
classes classes
} = this.props; } = this.props;
return ( return (
<div className={classes.root} ref={(node) => { this.node = node; }}> <div className={classes.root} ref={(node) => { this.node = node; }}>
{ {

View File

@ -279,7 +279,7 @@ const ListPeer = (props) =>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
} }
{ isModerator && webcamConsumer && { isModerator && webcamConsumer &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'tooltip.muteParticipantVideoModerator', id : 'tooltip.muteParticipantVideoModerator',

View File

@ -143,13 +143,13 @@ class Filmstrip extends React.PureComponent
let speakerWidth = (availableWidth - PADDING_H); let speakerWidth = (availableWidth - PADDING_H);
let speakerHeight = speakerWidth / RATIO; let speakerHeight = speakerWidth / RATIO;
if (this.isSharingCamera(this.getActivePeerId())) if (this.isSharingCamera(this.getActivePeerId()))
{ {
speakerWidth /= 2; speakerWidth /= 2;
speakerHeight = speakerWidth / RATIO; speakerHeight = speakerWidth / RATIO;
} }
if (speakerHeight > (availableSpeakerHeight - PADDING_V)) if (speakerHeight > (availableSpeakerHeight - PADDING_V))
{ {
speakerHeight = (availableSpeakerHeight - PADDING_V); speakerHeight = (availableSpeakerHeight - PADDING_V);
@ -167,7 +167,7 @@ class Filmstrip extends React.PureComponent
let filmStripHeight = availableFilmstripHeight - FILMSTRING_PADDING_V; let filmStripHeight = availableFilmstripHeight - FILMSTRING_PADDING_V;
let filmStripWidth = filmStripHeight * RATIO; let filmStripWidth = filmStripHeight * RATIO;
if ( if (
(filmStripWidth * this.props.boxes) > (filmStripWidth * this.props.boxes) >
(availableWidth - FILMSTRING_PADDING_H) (availableWidth - FILMSTRING_PADDING_H)
@ -254,7 +254,7 @@ class Filmstrip extends React.PureComponent
}; };
return ( return (
<div <div
className={classnames( className={classnames(
classes.root, classes.root,
toolbarsVisible || permanentTopBar ? toolbarsVisible || permanentTopBar ?

View File

@ -36,7 +36,7 @@ export default class PeerAudio extends React.PureComponent
if (prevProps !== this.props) if (prevProps !== this.props)
{ {
const { audioTrack, audioOutputDevice } = this.props; const { audioTrack, audioOutputDevice } = this.props;
this._setTrack(audioTrack); this._setTrack(audioTrack);
this._setOutputDevice(audioOutputDevice); this._setOutputDevice(audioOutputDevice);
} }
@ -70,7 +70,7 @@ export default class PeerAudio extends React.PureComponent
{ {
if (this._audioOutputDevice === audioOutputDevice) if (this._audioOutputDevice === audioOutputDevice)
return; return;
this._audioOutputDevice = audioOutputDevice; this._audioOutputDevice = audioOutputDevice;
const { audio } = this.refs; const { audio } = this.refs;

View File

@ -248,7 +248,7 @@ export const makePermissionSelector = (permission) =>
const permitted = roles.some((role) => const permitted = roles.some((role) =>
roomPermissions[permission].includes(role) roomPermissions[permission].includes(role)
); );
if (permitted) if (permitted)
return true; return true;
@ -265,7 +265,7 @@ export const makePermissionSelector = (permission) =>
).length === 0 ).length === 0
) )
return true; return true;
return false; return false;
} }
); );

View File

@ -4,13 +4,14 @@ import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../RoomContext'; import { withRoomContext } from '../../RoomContext';
import * as settingsActions from '../../actions/settingsActions'; import * as settingsActions from '../../actions/settingsActions';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useIntl, FormattedMessage } from 'react-intl'; import { useIntl, FormattedMessage } from 'react-intl';
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText'; import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl'; import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select'; import Select from '@material-ui/core/Select';
import Checkbox from '@material-ui/core/Checkbox'; import Switch from '@material-ui/core/Switch';
const styles = (theme) => const styles = (theme) =>
({ ({
@ -21,6 +22,13 @@ const styles = (theme) =>
formControl : formControl :
{ {
display : 'flex' display : 'flex'
},
switchLabel : {
justifyContent : 'space-between',
flex : 'auto',
display : 'flex',
padding : theme.spacing(1),
marginRight : 0
} }
}); });
@ -37,16 +45,18 @@ const AdvancedSettings = ({
return ( return (
<React.Fragment> <React.Fragment>
<FormControlLabel <FormControlLabel
className={classes.setting} className={classnames(classes.setting, classes.switchLabel)}
control={<Checkbox checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />} control={<Switch checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
labelPlacement='start'
label={intl.formatMessage({ label={intl.formatMessage({
id : 'settings.advancedMode', id : 'settings.advancedMode',
defaultMessage : 'Advanced mode' defaultMessage : 'Advanced mode'
})} })}
/> />
<FormControlLabel <FormControlLabel
className={classes.setting} className={classnames(classes.setting, classes.switchLabel)}
control={<Checkbox checked={settings.notificationSounds} onChange={onToggleNotificationSounds} value='notificationSounds' />} control={<Switch checked={settings.notificationSounds} onChange={onToggleNotificationSounds} value='notificationSounds' />}
labelPlacement='start'
label={intl.formatMessage({ label={intl.formatMessage({
id : 'settings.notificationSounds', id : 'settings.notificationSounds',
defaultMessage : 'Notification sounds' defaultMessage : 'Notification sounds'

View File

@ -4,6 +4,7 @@ import * as appPropTypes from '../appPropTypes';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import * as roomActions from '../../actions/roomActions'; import * as roomActions from '../../actions/roomActions';
import * as settingsActions from '../../actions/settingsActions'; import * as settingsActions from '../../actions/settingsActions';
import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl'; import { useIntl, FormattedMessage } from 'react-intl';
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from '@material-ui/core/MenuItem';
@ -11,7 +12,7 @@ import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl'; import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select'; import Select from '@material-ui/core/Select';
import Checkbox from '@material-ui/core/Checkbox'; import Switch from '@material-ui/core/Switch';
const styles = (theme) => const styles = (theme) =>
({ ({
@ -22,6 +23,13 @@ const styles = (theme) =>
formControl : formControl :
{ {
display : 'flex' display : 'flex'
},
switchLabel : {
justifyContent : 'space-between',
flex : 'auto',
display : 'flex',
padding : theme.spacing(1),
marginRight : 0
} }
}); });
@ -90,24 +98,28 @@ const AppearenceSettings = ({
</FormControl> </FormControl>
</form> </form>
<FormControlLabel <FormControlLabel
className={classes.setting} className={classnames(classes.setting, classes.switchLabel)}
control={<Checkbox checked={settings.permanentTopBar} onChange={onTogglePermanentTopBar} value='permanentTopBar' />} control={
<Switch checked={settings.permanentTopBar} onChange={onTogglePermanentTopBar} value='permanentTopBar' />}
labelPlacement='start'
label={intl.formatMessage({ label={intl.formatMessage({
id : 'settings.permanentTopBar', id : 'settings.permanentTopBar',
defaultMessage : 'Permanent top bar' defaultMessage : 'Permanent top bar'
})} })}
/> />
<FormControlLabel <FormControlLabel
className={classes.setting} className={classnames(classes.setting, classes.switchLabel)}
control={<Checkbox checked={settings.hiddenControls} onChange={onToggleHiddenControls} value='hiddenControls' />} control={<Switch checked={settings.hiddenControls} onChange={onToggleHiddenControls} value='hiddenControls' />}
labelPlacement='start'
label={intl.formatMessage({ label={intl.formatMessage({
id : 'settings.hiddenControls', id : 'settings.hiddenControls',
defaultMessage : 'Hidden media controls' defaultMessage : 'Hidden media controls'
})} })}
/> />
<FormControlLabel <FormControlLabel
className={classes.setting} className={classnames(classes.setting, classes.switchLabel)}
control={<Checkbox checked={settings.buttonControlBar} onChange={onToggleButtonControlBar} value='buttonControlBar' />} control={<Switch checked={settings.buttonControlBar} onChange={onToggleButtonControlBar} value='buttonControlBar' />}
labelPlacement='start'
label={intl.formatMessage({ label={intl.formatMessage({
id : 'settings.buttonControlBar', id : 'settings.buttonControlBar',
defaultMessage : 'Separate media controls' defaultMessage : 'Separate media controls'
@ -115,8 +127,9 @@ const AppearenceSettings = ({
/> />
{ !isMobile && { !isMobile &&
<FormControlLabel <FormControlLabel
className={classes.setting} className={classnames(classes.setting, classes.switchLabel)}
control={<Checkbox checked={settings.drawerOverlayed} onChange={onToggleDrawerOverlayed} value='drawerOverlayed' />} control={<Switch checked={settings.drawerOverlayed} onChange={onToggleDrawerOverlayed} value='drawerOverlayed' />}
labelPlacement='start'
label={intl.formatMessage({ label={intl.formatMessage({
id : 'settings.drawerOverlayed', id : 'settings.drawerOverlayed',
defaultMessage : 'Side drawer over content' defaultMessage : 'Side drawer over content'
@ -124,8 +137,9 @@ const AppearenceSettings = ({
/> />
} }
<FormControlLabel <FormControlLabel
className={classes.setting} className={classnames(classes.setting, classes.switchLabel)}
control={<Checkbox checked={settings.showNotifications} onChange={onToggleShowNotifications} value='showNotifications' />} control={<Switch checked={settings.showNotifications} onChange={onToggleShowNotifications} value='showNotifications' />}
labelPlacement='start'
label={intl.formatMessage({ label={intl.formatMessage({
id : 'settings.showNotifications', id : 'settings.showNotifications',
defaultMessage : 'Show notifications' defaultMessage : 'Show notifications'

View File

@ -12,13 +12,19 @@ import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl'; import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select'; import Select from '@material-ui/core/Select';
import Checkbox from '@material-ui/core/Checkbox';
import Slider from '@material-ui/core/Slider'; import Slider from '@material-ui/core/Slider';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Collapse from '@material-ui/core/Collapse';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import Switch from '@material-ui/core/Switch';
const NoiseSlider = withStyles( const NoiseSlider = withStyles(
{ {
root : root :
{ {
color : '#3880ff', color : '#3880ff',
height : 2, height : 2,
@ -48,10 +54,27 @@ const styles = (theme) => ({
{ {
padding : theme.spacing(2) padding : theme.spacing(2)
}, },
margin : margin :
{ {
height : theme.spacing(3) height : theme.spacing(3)
}, },
root : {
width : '100%',
backgroundColor : theme.palette.background.paper
},
switchLabel : {
justifyContent : 'space-between',
flex : 'auto',
display : 'flex',
padding : theme.spacing(1)
},
nested : {
display : 'block',
paddingTop : 0,
paddingBottom : 0,
paddingLeft : '25px',
paddingRight : '25px'
},
formControl : formControl :
{ {
display : 'flex' display : 'flex'
@ -71,7 +94,7 @@ const MediaSettings = ({
}) => }) =>
{ {
const intl = useIntl(); const intl = useIntl();
const resolutions = [ { const resolutions = [ {
value : 'low', value : 'low',
label : intl.formatMessage({ label : intl.formatMessage({
@ -121,7 +144,7 @@ const MediaSettings = ({
audioDevices = Object.values(me.audioDevices); audioDevices = Object.values(me.audioDevices);
else else
audioDevices = []; audioDevices = [];
let audioOutputDevices; let audioOutputDevices;
if (me.audioOutputDevices) if (me.audioOutputDevices)
@ -129,6 +152,13 @@ const MediaSettings = ({
else else
audioOutputDevices = []; audioOutputDevices = [];
const [ open, setOpen ] = React.useState(true);
const advancedAudioSettings = () =>
{
setOpen(!open);
};
return ( return (
<React.Fragment> <React.Fragment>
<form className={classes.setting} autoComplete='off'> <form className={classes.setting} autoComplete='off'>
@ -173,7 +203,7 @@ const MediaSettings = ({
<FormControl className={classes.formControl}> <FormControl className={classes.formControl}>
<Select <Select
value={settings.resolution || ''} value={settings.resolution || ''}
onChange={(event) => onChange={(event) =>
{ {
if (event.target.value) if (event.target.value)
roomClient.changeVideoResolution(event.target.value); roomClient.changeVideoResolution(event.target.value);
@ -182,7 +212,7 @@ const MediaSettings = ({
autoWidth autoWidth
className={classes.selectEmpty} className={classes.selectEmpty}
> >
{resolutions.map((resolution, index) => {resolutions.map((resolution, index) =>
{ {
return ( return (
<MenuItem key={index} value={resolution.value}> <MenuItem key={index} value={resolution.value}>
@ -287,90 +317,120 @@ const MediaSettings = ({
</FormControl> </FormControl>
</form> </form>
} }
<form className={classes.setting} autoComplete='off'> <List className={classes.root} component='nav'>
<FormControlLabel <ListItem button onClick={advancedAudioSettings}>
className={classes.setting} <ListItemText primary={intl.formatMessage({
control={ id : 'settings.showAdvancedAudio',
<Checkbox checked={settings.echoCancellation} onChange={ defaultMessage : 'Show advanced audio settings'
(event) =>
{
setEchoCancellation(event.target.checked);
roomClient.changeAudioDevice(settings.selectedAudioDevice);
}}
/>}
label={intl.formatMessage({
id : 'settings.echoCancellation',
defaultMessage : 'Echo cancellation'
})} })}
/> />
<FormControlLabel {open ? <ExpandLess /> : <ExpandMore />}
className={classes.setting} </ListItem>
control={ <Collapse in={!open} timeout='auto'>
<Checkbox checked={settings.autoGainControl} onChange={ <List component='div'>
(event) => <ListItem className={classes.nested}>
{ <FormControlLabel
setAutoGainControl(event.target.checked); className={classnames(classes.setting, classes.switchLabel)}
roomClient.changeAudioDevice(settings.selectedAudioDevice); control={
}} <Switch color='secondary'
/>} checked={settings.echoCancellation}
label={intl.formatMessage({ onChange={
id : 'settings.autoGainControl', (event) =>
defaultMessage : 'Auto gain control' {
})} setEchoCancellation(event.target.checked);
/> roomClient.changeAudioDevice(settings.selectedAudioDevice);
<FormControlLabel }}
className={classes.setting} />}
control={ labelPlacement='start'
<Checkbox checked={settings.noiseSuppression} onChange={ label={intl.formatMessage({
(event) => id : 'settings.echoCancellation',
{ defaultMessage : 'Echo cancellation'
setNoiseSuppression(event.target.checked); })}
roomClient.changeAudioDevice(settings.selectedAudioDevice); />
}} </ListItem>
/>} <ListItem className={classes.nested}>
label={intl.formatMessage({ <FormControlLabel
id : 'settings.noiseSuppression', className={classnames(classes.setting, classes.switchLabel)}
defaultMessage : 'Noise suppression' control={
})} <Switch color='secondary'
/> checked={settings.autoGainControl} onChange={
<FormControlLabel (event) =>
className={classes.setting} {
control={ setAutoGainControl(event.target.checked);
<Checkbox checked={settings.voiceActivatedUnmute} onChange={ roomClient.changeAudioDevice(settings.selectedAudioDevice);
(event) => }}
{ />}
setVoiceActivatedUnmute(event.target.checked); labelPlacement='start'
}} label={intl.formatMessage({
/>} id : 'settings.autoGainControl',
label={intl.formatMessage({ defaultMessage : 'Auto gain control'
id : 'settings.voiceActivatedUnmute', })}
defaultMessage : 'Voice activated unmute' />
})} </ListItem>
/> <ListItem className={classes.nested}>
<div className={classes.margin} /> <FormControlLabel
<Typography gutterBottom> className={classnames(classes.setting, classes.switchLabel)}
{ control={
intl.formatMessage({ <Switch color='secondary'
id : 'settings.noiseThreshold', checked={settings.noiseSuppression} onChange={
defaultMessage : 'Noise threshold:' (event) =>
}) {
} setNoiseSuppression(event.target.checked);
</Typography> roomClient.changeAudioDevice(settings.selectedAudioDevice);
<NoiseSlider className={classnames(classes.slider, classnames.setting)} }}
key={'noise-threshold-slider'} />}
min={-100} labelPlacement='start'
value={settings.noiseThreshold} label={intl.formatMessage({
max={0} id : 'settings.noiseSuppression',
valueLabelDisplay={'off'} defaultMessage : 'Noise suppression'
onChange={ })}
(event, value) => />
{ </ListItem>
roomClient._setNoiseThreshold(value); <ListItem className={classes.nested}>
}} <FormControlLabel
marks={[ { value: volume, label: 'level' } ]} className={classnames(classes.setting, classes.switchLabel)}
/> control={
<div className={classes.margin} /> <Switch color='secondary'
</form> checked={settings.voiceActivatedUnmute} onChange={
(event) =>
{
setVoiceActivatedUnmute(event.target.checked);
}}
/>}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.voiceActivatedUnmute',
defaultMessage : 'Voice activated unmute'
})}
/>
</ListItem>
<ListItem className={classes.nested}>
<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={'auto'}
onChange={
(event, value) =>
{
roomClient._setNoiseThreshold(value);
}}
marks={[ { value: volume, label: `${volume} dB` } ]}
/>
</ListItem>
</List>
</Collapse>
</List>
</React.Fragment> </React.Fragment>
); );
}; };
@ -399,8 +459,8 @@ const mapStateToProps = (state) =>
const mapDispatchToProps = { const mapDispatchToProps = {
setEchoCancellation : settingsActions.setEchoCancellation, setEchoCancellation : settingsActions.setEchoCancellation,
setAutoGainControl : settingsActions.toggleAutoGainControl, setAutoGainControl : settingsActions.setAutoGainControl,
setNoiseSuppression : settingsActions.toggleNoiseSuppression, setNoiseSuppression : settingsActions.setNoiseSuppression,
setVoiceActivatedUnmute : settingsActions.setVoiceActivatedUnmute setVoiceActivatedUnmute : settingsActions.setVoiceActivatedUnmute
}; };

View File

@ -0,0 +1,154 @@
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import Grid from '@material-ui/core/Grid';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import WebAssetIcon from '@material-ui/icons/WebAsset';
import ErrorIcon from '@material-ui/icons/Error';
import Hidden from '@material-ui/core/Hidden';
const styles = (theme) =>
({
dialogPaper :
{
width : '40vw',
[theme.breakpoints.down('lg')] :
{
width : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : '90vw'
}
// display : 'flex',
// flexDirection : 'column'
},
list : {
backgroundColor : theme.palette.background.paper
},
errorAvatar : {
width : theme.spacing(20),
height : theme.spacing(20)
}
});
const open=true;
const dividers=true;
let dense=false;
const supportedBrowsers=[
{ name: 'Chrome/Chromium', version: '74', vendor: 'Google' },
{ name: 'Edge', version: '18', vendor: 'Microsoft' },
{ name: 'Firefox', version: '60', vendor: 'Mozilla' },
{ name: 'Safari', version: '12', vendor: 'Apple' },
{ name: 'Opera', version: '62', vendor: '' },
// { name: 'Brave', version: '1.5', vendor: '' },
// { name: 'Vivaldi', version: '3', vendor: '' },
{ name: 'Samsung Internet', version: '11.1.1.52', vendor: '' }
];
const UnsupportedBrowser = ({
platform,
webrtcUnavailable,
classes
}) =>
{
if (platform !== 'desktop')
{
dense=true;
}
return (
<Dialog
open={open}
scroll={'body'}
classes={{
paper : classes.dialogPaper
}}
>
<DialogTitle id='form-dialog-title'>
{!webrtcUnavailable &&
<FormattedMessage
id='unsupportedBrowser.titleUnsupportedBrowser'
defaultMessage='Detected unsupported browser!'
/>
}
{webrtcUnavailable &&
<FormattedMessage
id='unsupportedBrowser.titlewebrtcUnavailable'
defaultMessage='Required functionality not availble in your browser!'
/>
}
</DialogTitle>
<DialogContent dividers={dividers} >
<FormattedMessage
id='unsupportedBrowser.bodyText'
defaultMessage='This meeting service requires a
functionality that is not supported by your browser.
Please upgrade, or switch to a different browser, or
check your settings. Supported browsers:'
/>
<Grid container spacing={2} justify='center' alignItems='center'>
<Grid item xs={12} md={7}>
<div className={classes.list}>
<List dense={dense}>
{supportedBrowsers.map((browser, index) =>
{
const supportedBrowser = `${browser.vendor} ${browser.name}`;
const supportedVersion = `${browser.version}+`;
return (
<ListItem key={index}>
<ListItemAvatar>
<Avatar>
<WebAssetIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={supportedBrowser}
secondary={supportedVersion}
/>
</ListItem>
);
})}
</List>
</div>
</Grid>
<Grid item xs={12} md={5} align='center'>
<Hidden mdDown>
<ErrorIcon className={classes.errorAvatar} color='error'/>
</Hidden>
</Grid>
</Grid>
</DialogContent>
</Dialog>
);
};
UnsupportedBrowser.propTypes =
{
webrtcUnavailable : PropTypes.bool.isRequired,
platform : PropTypes.string.isRequired,
classes : PropTypes.object.isRequired
};
export default withStyles(styles)(UnsupportedBrowser);

View File

@ -223,50 +223,50 @@ class VideoView extends React.PureComponent
if (videoScore || audioScore) if (videoScore || audioScore)
{ {
const score = videoScore ? videoScore : audioScore; const score = videoScore ? videoScore : audioScore;
switch (isMe ? score.score : score.producerScore) switch (isMe ? score.score : score.producerScore)
{ {
case 0: case 0:
case 1: case 1:
{ {
quality = <SignalCellular0BarIcon style={{ color: red[500] }}/>; quality = <SignalCellular0BarIcon style={{ color: red[500] }}/>;
break; break;
} }
case 2: case 2:
case 3: case 3:
{ {
quality = <SignalCellular1BarIcon style={{ color: red[500] }}/>; quality = <SignalCellular1BarIcon style={{ color: red[500] }}/>;
break; break;
} }
case 4: case 4:
case 5: case 5:
case 6: case 6:
{ {
quality = <SignalCellular2BarIcon style={{ color: orange[500] }}/>; quality = <SignalCellular2BarIcon style={{ color: orange[500] }}/>;
break; break;
} }
case 7: case 7:
case 8: case 8:
case 9: case 9:
{ {
quality = <SignalCellular3BarIcon style={{ color: yellow[500] }}/>; quality = <SignalCellular3BarIcon style={{ color: yellow[500] }}/>;
break; break;
} }
case 10: case 10:
{ {
quality = null; quality = null;
break; break;
} }
default: default:
{ {
break; break;
@ -351,7 +351,7 @@ class VideoView extends React.PureComponent
</div> </div>
{ showQuality && { showQuality &&
<div className={classnames(classes.box, 'right')}> <div className={classnames(classes.box, 'right')}>
{ {
quality quality
} }
</div> </div>

View File

@ -12,6 +12,7 @@ import RoomClient from './RoomClient';
import RoomContext from './RoomContext'; import RoomContext from './RoomContext';
import deviceInfo from './deviceInfo'; import deviceInfo from './deviceInfo';
import * as meActions from './actions/meActions'; import * as meActions from './actions/meActions';
import UnsupportedBrowser from './components/UnsupportedBrowser';
import ChooseRoom from './components/ChooseRoom'; import ChooseRoom from './components/ChooseRoom';
import LoadingView from './components/LoadingView'; import LoadingView from './components/LoadingView';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
@ -20,7 +21,7 @@ import { persistor, store } from './store';
import { SnackbarProvider } from 'notistack'; import { SnackbarProvider } from 'notistack';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
import { ReactLazyPreload } from './components/ReactLazyPreload'; import { ReactLazyPreload } from './components/ReactLazyPreload';
import { detectDevice } from 'mediasoup-client';
// import messagesEnglish from './translations/en'; // import messagesEnglish from './translations/en';
import messagesNorwegian from './translations/nb'; import messagesNorwegian from './translations/nb';
import messagesGerman from './translations/de'; import messagesGerman from './translations/de';
@ -70,6 +71,18 @@ const messages =
'lv' : messagesLatvian 'lv' : messagesLatvian
}; };
const supportedBrowsers={
'windows' : {
'internet explorer' : '>12',
'microsoft edge' : '>18'
},
'safari' : '>12',
'firefox' : '>=60',
'chrome' : '>=74',
'opera' : '>=62',
'samsung internet for android' : '>=11.1.1.52'
};
const browserLanguage = (navigator.language || navigator.browserLanguage).toLowerCase(); const browserLanguage = (navigator.language || navigator.browserLanguage).toLowerCase();
let locale = browserLanguage.split(/[-_]/)[0]; // language without region code let locale = browserLanguage.split(/[-_]/)[0]; // language without region code
@ -134,10 +147,62 @@ function run()
if (!basePath) if (!basePath)
basePath = '/'; basePath = '/';
// Get current device. // Get current device.
const device = deviceInfo(); const device = deviceInfo();
let unsupportedBrowser=false;
let webrtcUnavailable=false;
if (detectDevice() === undefined)
{
logger.error('Unsupported browser detected by mediasoup client detectDevice! deviceInfo: %o', device);
unsupportedBrowser=true;
}
else
if (
navigator.mediaDevices === undefined ||
navigator.mediaDevices.getUserMedia === undefined ||
window.RTCPeerConnection === undefined
)
{
logger.error('WebRTC is unavialable in your browser! deviceInfo: %o', device);
webrtcUnavailable=true;
}
else
if (!device.bowser.satisfies(
window.config.supportedBrowsers ? window.config.supportedBrowsers : supportedBrowsers)
)
{
logger.error(
'Your browser is not on the supported list! Ask your server admin to add your browser to the supported list, if you think that your browser should be supported! deviceInfo: %o',
device
);
unsupportedBrowser=true;
}
else
{
logger.debug('Supported Browser! deviceInfo: %o', device);
}
if (unsupportedBrowser || webrtcUnavailable)
{
render(
<MuiThemeProvider theme={theme}>
<RawIntlProvider value={intl}>
<UnsupportedBrowser
webrtcUnavailable={webrtcUnavailable}
platform={device.platform}
/>
</RawIntlProvider>
</MuiThemeProvider>,
document.getElementById('multiparty-meeting')
);
return;
}
store.dispatch( store.dispatch(
meActions.setMe({ meActions.setMe({
peerId, peerId,

View File

@ -0,0 +1,42 @@
class AudioAnalyzer extends EventEmitter
{
constructor()
{
if (prefix)
{
this._debug = debug(`${APP_NAME}:${prefix}`);
this._warn = debug(`${APP_NAME}:WARN:${prefix}`);
this._error = debug(`${APP_NAME}:ERROR:${prefix}`);
}
else
{
this._debug = debug(APP_NAME);
this._warn = debug(`${APP_NAME}:WARN`);
this._error = debug(`${APP_NAME}:ERROR`);
}
/* eslint-disable no-console */
this._debug.log = console.info.bind(console);
this._warn.log = console.warn.bind(console);
this._error.log = console.error.bind(console);
/* eslint-enable no-console */
}
get debug()
{
return this._debug;
}
get warn()
{
return this._warn;
}
get error()
{
return this._error;
}
}

View File

@ -1,6 +1,6 @@
const lobbyPeer = (state = {}, action) => const lobbyPeer = (state = {}, action) =>
{ {
switch (action.type) switch (action.type)
{ {
case 'ADD_LOBBY_PEER': case 'ADD_LOBBY_PEER':
return { id: action.payload.peerId }; return { id: action.payload.peerId };
@ -42,7 +42,7 @@ const lobbyPeers = (state = {}, action) =>
{ {
const oldLobbyPeer = state[action.payload.peerId]; const oldLobbyPeer = state[action.payload.peerId];
if (!oldLobbyPeer) if (!oldLobbyPeer)
{ {
// Tried to update non-existent lobbyPeer. Has probably been promoted, or left. // Tried to update non-existent lobbyPeer. Has probably been promoted, or left.
return state; return state;

View File

@ -10,13 +10,13 @@ const peerVolumes = (state = initialState, action) =>
peerId peerId
} = action.payload; } = action.payload;
return { ...state, [peerId]: 0 }; return { ...state, [peerId]: -100 };
} }
case 'ADD_PEER': case 'ADD_PEER':
{ {
const { peer } = action.payload; const { peer } = action.payload;
return { ...state, [peer.id]: 0 }; return { ...state, [peer.id]: -100 };
} }
case 'REMOVE_PEER': case 'REMOVE_PEER':

View File

@ -2,7 +2,7 @@ const initialState = {};
const peer = (state = initialState, action) => const peer = (state = initialState, action) =>
{ {
switch (action.type) switch (action.type)
{ {
case 'ADD_PEER': case 'ADD_PEER':
return action.payload.peer; return action.payload.peer;
@ -21,7 +21,7 @@ const peer = (state = initialState, action) =>
case 'SET_PEER_KICK_IN_PROGRESS': case 'SET_PEER_KICK_IN_PROGRESS':
return { ...state, peerKickInProgress: action.payload.flag }; return { ...state, peerKickInProgress: action.payload.flag };
case 'SET_PEER_RAISED_HAND': case 'SET_PEER_RAISED_HAND':
return { return {
...state, ...state,
@ -34,7 +34,7 @@ const peer = (state = initialState, action) =>
...state, ...state,
raisedHandInProgress : action.payload.flag raisedHandInProgress : action.payload.flag
}; };
case 'ADD_CONSUMER': case 'ADD_CONSUMER':
{ {
const consumers = [ ...state.consumers, action.payload.consumer.id ]; const consumers = [ ...state.consumers, action.payload.consumer.id ];
@ -127,14 +127,14 @@ const peers = (state = initialState, action) =>
{ {
const oldPeer = state[action.payload.peerId]; const oldPeer = state[action.payload.peerId];
if (!oldPeer) if (!oldPeer)
{ {
throw new Error('no Peer found'); throw new Error('no Peer found');
} }
return { ...state, [oldPeer.id]: peer(oldPeer, action) }; return { ...state, [oldPeer.id]: peer(oldPeer, action) };
} }
case 'SET_PEER_KICK_IN_PROGRESS': case 'SET_PEER_KICK_IN_PROGRESS':
case 'REMOVE_CONSUMER': case 'REMOVE_CONSUMER':
{ {

View File

@ -10,7 +10,7 @@ const initialState =
// access code to the room if locked and joinByAccessCode == true // access code to the room if locked and joinByAccessCode == true
accessCode : '', accessCode : '',
// if true: accessCode is a possibility to open the room // if true: accessCode is a possibility to open the room
joinByAccessCode : true, joinByAccessCode : true,
activeSpeakerId : null, activeSpeakerId : null,
torrentSupport : false, torrentSupport : false,
showSettings : false, showSettings : false,
@ -107,7 +107,7 @@ const room = (state = initialState, action) =>
return { ...state, lockDialogOpen }; return { ...state, lockDialogOpen };
} }
case 'SET_SETTINGS_OPEN': case 'SET_SETTINGS_OPEN':
{ {
const { settingsOpen } = action.payload; const { settingsOpen } = action.payload;
@ -135,7 +135,7 @@ const room = (state = initialState, action) =>
return { ...state, aboutOpen }; return { ...state, aboutOpen };
} }
case 'SET_SETTINGS_TAB': case 'SET_SETTINGS_TAB':
{ {
const { tab } = action.payload; const { tab } = action.payload;

View File

@ -22,7 +22,6 @@ const initialState =
notificationSounds : true, notificationSounds : true,
buttonControlBar : window.config.buttonControlBar || false, buttonControlBar : window.config.buttonControlBar || false,
drawerOverlayed : window.config.drawerOverlayed || true, drawerOverlayed : window.config.drawerOverlayed || true,
autoMuteThreshold : window.config.autoMuteThreshold || 4,
...window.config.defaultAudio ...window.config.defaultAudio
}; };
@ -44,7 +43,7 @@ const settings = (state = initialState, action) =>
{ {
return { ...state, selectedAudioOutputDevice: action.payload.deviceId }; return { ...state, selectedAudioOutputDevice: action.payload.deviceId };
} }
case 'SET_DISPLAY_NAME': case 'SET_DISPLAY_NAME':
{ {
const { displayName } = action.payload; const { displayName } = action.payload;
@ -122,27 +121,6 @@ const settings = (state = initialState, action) =>
return { ...state, audio }; return { ...state, audio };
} }
case 'TOGGLE_AUTO_GAIN_CONTROL':
{
const autoGainControl = !state.autoGainControl;
return { ...state, autoGainControl };
}
case 'TOGGLE_ECHO_CANCELLATION':
{
const echoCancellation = !state.echoCancellation;
return { ...state, echoCancellation };
}
case 'TOGGLE_NOISE_SUPPRESSION':
{
const noiseSuppression = !state.noiseSuppression;
return { ...state, noiseSuppression };
}
case 'SET_SAMPLE_SIZE': case 'SET_SAMPLE_SIZE':
{ {
const { sampleSize } = action.payload; const { sampleSize } = action.payload;

View File

@ -119,6 +119,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "设置", "settings.settings": "设置",
"settings.camera": "视频设备", "settings.camera": "视频设备",
@ -144,6 +145,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "无法保存文件", "filesharing.saveFileError": "无法保存文件",
"filesharing.startingFileShare": "正在尝试共享文件", "filesharing.startingFileShare": "正在尝试共享文件",
@ -189,5 +192,9 @@
"moderator.clearFiles": null, "moderator.clearFiles": null,
"moderator.muteAudio": null, "moderator.muteAudio": null,
"moderator.muteVideo": null, "moderator.muteVideo": null,
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -118,6 +118,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Nastavení", "settings.settings": "Nastavení",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -143,6 +144,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Není možné uložit soubor", "filesharing.saveFileError": "Není možné uložit soubor",
"filesharing.startingFileShare": "Pokouším se sdílet soubor", "filesharing.startingFileShare": "Pokouším se sdílet soubor",
@ -188,5 +191,9 @@
"moderator.clearFiles": null, "moderator.clearFiles": null,
"moderator.muteAudio": null, "moderator.muteAudio": null,
"moderator.muteVideo": null, "moderator.muteVideo": null,
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": "Video hinzufügen", "label.addVideo": "Video hinzufügen",
"label.promoteAllPeers": "Alle Teilnehmer reinlassen", "label.promoteAllPeers": "Alle Teilnehmer reinlassen",
"label.moreActions": "Weitere Aktionen", "label.moreActions": "Weitere Aktionen",
"label.version": null,
"settings.settings": "Einstellungen", "settings.settings": "Einstellungen",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -144,6 +145,8 @@
"settings.autoGainControl": "Automatische Pegelregelung (Audioeingang)", "settings.autoGainControl": "Automatische Pegelregelung (Audioeingang)",
"settings.noiseSuppression": "Rauschunterdrückung", "settings.noiseSuppression": "Rauschunterdrückung",
"settings.drawerOverlayed": "Seitenpanel verdeckt Hauptinhalt", "settings.drawerOverlayed": "Seitenpanel verdeckt Hauptinhalt",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Fehler beim Speichern der Datei", "filesharing.saveFileError": "Fehler beim Speichern der Datei",
"filesharing.startingFileShare": "Starte Teilen der Datei", "filesharing.startingFileShare": "Starte Teilen der Datei",
@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator hat geteilte Dateiliste gelöscht", "moderator.clearFiles": "Moderator hat geteilte Dateiliste gelöscht",
"moderator.muteAudio": "Moderator hat dich stummgeschaltet", "moderator.muteAudio": "Moderator hat dich stummgeschaltet",
"moderator.muteVideo": "Moderator hat dein Video gestoppt", "moderator.muteVideo": "Moderator hat dein Video gestoppt",
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Indstillinger", "settings.settings": "Indstillinger",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -144,6 +145,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Kan ikke gemme fil", "filesharing.saveFileError": "Kan ikke gemme fil",
"filesharing.startingFileShare": "Forsøger at dele filen", "filesharing.startingFileShare": "Forsøger at dele filen",
@ -189,5 +192,9 @@
"moderator.clearFiles": null, "moderator.clearFiles": null,
"moderator.muteAudio": null, "moderator.muteAudio": null,
"moderator.muteVideo": null, "moderator.muteVideo": null,
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Ρυθμίσεις", "settings.settings": "Ρυθμίσεις",
"settings.camera": "Κάμερα", "settings.camera": "Κάμερα",
@ -144,6 +145,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου", "filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου",
"filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου", "filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου",
@ -189,5 +192,9 @@
"moderator.clearFiles": null, "moderator.clearFiles": null,
"moderator.muteAudio": null, "moderator.muteAudio": null,
"moderator.muteVideo": null, "moderator.muteVideo": null,
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": "Add video", "label.addVideo": "Add video",
"label.promoteAllPeers": "Promote all", "label.promoteAllPeers": "Promote all",
"label.moreActions": "More actions", "label.moreActions": "More actions",
"label.version": "Version",
"settings.settings": "Settings", "settings.settings": "Settings",
"settings.camera": "Camera", "settings.camera": "Camera",
@ -144,6 +145,8 @@
"settings.autoGainControl": "Auto gain control", "settings.autoGainControl": "Auto gain control",
"settings.noiseSuppression": "Noise suppression", "settings.noiseSuppression": "Noise suppression",
"settings.drawerOverlayed": "Side drawer over content", "settings.drawerOverlayed": "Side drawer over content",
"settings.voiceActivatedUnmute": "Voice activated unmute",
"settings.noiseThreshold": "Noise threshold",
"filesharing.saveFileError": "Unable to save file", "filesharing.saveFileError": "Unable to save file",
"filesharing.startingFileShare": "Attempting to share file", "filesharing.startingFileShare": "Attempting to share file",
@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator cleared the files", "moderator.clearFiles": "Moderator cleared the files",
"moderator.muteAudio": "Moderator muted your audio", "moderator.muteAudio": "Moderator muted your audio",
"moderator.muteVideo": "Moderator muted your video", "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!",
"unsupportedBrowser.bodyText": "This meeting service requires a functionality that is not supported by your browser. Please upgrade, or switch to a different browser, or check your settings. Supported browsers:"
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Ajustes", "settings.settings": "Ajustes",
"settings.camera": "Cámara", "settings.camera": "Cámara",
@ -144,6 +145,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "No ha sido posible guardar el fichero", "filesharing.saveFileError": "No ha sido posible guardar el fichero",
"filesharing.startingFileShare": "Intentando compartir el fichero", "filesharing.startingFileShare": "Intentando compartir el fichero",
@ -189,5 +192,9 @@
"moderator.clearFiles": null, "moderator.clearFiles": null,
"moderator.muteAudio": null, "moderator.muteAudio": null,
"moderator.muteVideo": null, "moderator.muteVideo": null,
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Paramètres", "settings.settings": "Paramètres",
"settings.camera": "Caméra", "settings.camera": "Caméra",
@ -144,6 +145,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Impossible d'enregistrer le fichier", "filesharing.saveFileError": "Impossible d'enregistrer le fichier",
"filesharing.startingFileShare": "Début du transfert de fichier", "filesharing.startingFileShare": "Début du transfert de fichier",
@ -188,5 +191,9 @@
"moderator.clearFiles": null, "moderator.clearFiles": null,
"moderator.muteAudio": null, "moderator.muteAudio": null,
"moderator.muteVideo": null, "moderator.muteVideo": null,
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": "Dodaj video", "label.addVideo": "Dodaj video",
"label.promoteAllPeers": "Promoviraj sve", "label.promoteAllPeers": "Promoviraj sve",
"label.moreActions": "Više akcija", "label.moreActions": "Više akcija",
"label.version": null,
"settings.settings": "Postavke", "settings.settings": "Postavke",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -144,6 +145,8 @@
"settings.autoGainControl": "Automatsko upravljanje jačinom zvuka", "settings.autoGainControl": "Automatsko upravljanje jačinom zvuka",
"settings.noiseSuppression": "Poništavanje šuma", "settings.noiseSuppression": "Poništavanje šuma",
"settings.drawerOverlayed": "Bočni izbornik iznad sadržaja", "settings.drawerOverlayed": "Bočni izbornik iznad sadržaja",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Nije moguće spremiti datoteku", "filesharing.saveFileError": "Nije moguće spremiti datoteku",
"filesharing.startingFileShare": "Pokušaj dijeljenja datoteke", "filesharing.startingFileShare": "Pokušaj dijeljenja datoteke",
@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator je izbrisao datoteke", "moderator.clearFiles": "Moderator je izbrisao datoteke",
"moderator.muteAudio": "Moderator je utišao tvoj zvuk", "moderator.muteAudio": "Moderator je utišao tvoj zvuk",
"moderator.muteVideo": "Moderator je zaustavio tvoj video", "moderator.muteVideo": "Moderator je zaustavio tvoj video",
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -66,7 +66,7 @@
"room.browsePeersSpotlight": "Résztvevők böngészése", "room.browsePeersSpotlight": "Résztvevők böngészése",
"room.stopAllScreenSharing": "Összes képernyőmegosztás leállítása", "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.gotRole": "{role} szerepet kaptál",
"roles.lostRole": "Elvesztetted a {role} szerepet", "roles.lostRole": "Elvesztetted a {role} szerepet",
@ -86,9 +86,9 @@
"tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása", "tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása",
"tooltip.raisedHand": "Jelentkezés", "tooltip.raisedHand": "Jelentkezés",
"tooltip.muteScreenSharing": "Képernyőmegosztás szüneteltetése", "tooltip.muteScreenSharing": "Képernyőmegosztás szüneteltetése",
"tooltip.muteParticipantAudioModerator": "Résztvevő hangjának általános némítása", "tooltip.muteParticipantAudioModerator": "Résztvevő hangjának némítása mindenkinél",
"tooltip.muteParticipantVideoModerator": "Résztvevő videójának általános némítása", "tooltip.muteParticipantVideoModerator": "Résztvevő videójának némítása mindenkinél",
"tooltip.muteScreenSharingModerator": "Résztvevő képernyőmegosztásának általános némítása", "tooltip.muteScreenSharingModerator": "Résztvevő képernyőmegosztásának leállítása mindenkinél",
"label.roomName": "Konferencia", "label.roomName": "Konferencia",
"label.chooseRoomButton": "Tovább", "label.chooseRoomButton": "Tovább",
@ -119,6 +119,7 @@
"label.addVideo": "Videó hozzáadása", "label.addVideo": "Videó hozzáadása",
"label.promoteAllPeers": "Mindenkit beengedek", "label.promoteAllPeers": "Mindenkit beengedek",
"label.moreActions": "További műveletek", "label.moreActions": "További műveletek",
"label.version": "Verzió",
"settings.settings": "Beállítások", "settings.settings": "Beállítások",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -144,6 +145,8 @@
"settings.autoGainControl": "Automatikus hangerő", "settings.autoGainControl": "Automatikus hangerő",
"settings.noiseSuppression": "Zajelnyomás", "settings.noiseSuppression": "Zajelnyomás",
"settings.drawerOverlayed": "Oldalsáv a tartalom felett", "settings.drawerOverlayed": "Oldalsáv a tartalom felett",
"settings.voiceActivatedUnmute": "Beszéd aktivált mikrofon némítás",
"settings.noiseThreshold": "Zajszint",
"filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.saveFileError": "A file-t nem sikerült elmenteni",
"filesharing.startingFileShare": "Fájl megosztása", "filesharing.startingFileShare": "Fájl megosztása",
@ -189,5 +192,9 @@
"moderator.clearFiles": "A moderátor kiürítette a file megosztás történelmet", "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.muteAudio": "A moderátor elnémította a hangod",
"moderator.muteVideo": "A moderátor elnémította a videód", "moderator.muteVideo": "A moderátor elnémította a videód",
"moderator.muteScreenSharing": "A moderátor leállította 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.addVideo": "Aggiungi video",
"label.promoteAllPeers": "Promuovi tutti", "label.promoteAllPeers": "Promuovi tutti",
"label.moreActions": "Altre azioni", "label.moreActions": "Altre azioni",
"label.version": null,
"settings.settings": "Impostazioni", "settings.settings": "Impostazioni",
"settings.camera": "Videocamera", "settings.camera": "Videocamera",
@ -144,6 +145,8 @@
"settings.autoGainControl": "Controllo guadagno automatico", "settings.autoGainControl": "Controllo guadagno automatico",
"settings.noiseSuppression": "Riduzione del rumore", "settings.noiseSuppression": "Riduzione del rumore",
"settings.drawerOverlayed": "Barra laterale sovrapposta", "settings.drawerOverlayed": "Barra laterale sovrapposta",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Impossibile salvare file", "filesharing.saveFileError": "Impossibile salvare file",
"filesharing.startingFileShare": "Tentativo di condivisione file", "filesharing.startingFileShare": "Tentativo di condivisione file",
@ -189,5 +192,9 @@
"moderator.clearFiles": "Il moderatore ha pulito i file", "moderator.clearFiles": "Il moderatore ha pulito i file",
"moderator.muteAudio": "Il moderatore ha mutato il tuo audio", "moderator.muteAudio": "Il moderatore ha mutato il tuo audio",
"moderator.muteVideo": "Il moderatore ha fermato il tuo video", "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.advanced": "Advancēts",
"label.addVideo": "Pievienot video", "label.addVideo": "Pievienot video",
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Iestatījumi", "settings.settings": "Iestatījumi",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -138,6 +139,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Nav iespējams saglabāt failu", "filesharing.saveFileError": "Nav iespējams saglabāt failu",
"filesharing.startingFileShare": "Tiek mēģināts kopīgot failu", "filesharing.startingFileShare": "Tiek mēģināts kopīgot failu",
@ -183,5 +186,9 @@
"moderator.clearFiles": "Moderators notīrīja failus", "moderator.clearFiles": "Moderators notīrīja failus",
"moderator.muteAudio": "Moderators noklusināja jūsu mikrofonu", "moderator.muteAudio": "Moderators noklusināja jūsu mikrofonu",
"moderator.muteVideo": "Moderators atslēdza jūsu kameru", "moderator.muteVideo": "Moderators atslēdza jūsu kameru",
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": "Legg til video", "label.addVideo": "Legg til video",
"label.promoteAllPeers": "Slipp inn alle", "label.promoteAllPeers": "Slipp inn alle",
"label.moreActions": "Flere handlinger", "label.moreActions": "Flere handlinger",
"label.version": null,
"settings.settings": "Innstillinger", "settings.settings": "Innstillinger",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -144,6 +145,8 @@
"settings.autoGainControl": "Auto gain kontroll", "settings.autoGainControl": "Auto gain kontroll",
"settings.noiseSuppression": "Støy reduksjon", "settings.noiseSuppression": "Støy reduksjon",
"settings.drawerOverlayed": "Sidemeny over innhold", "settings.drawerOverlayed": "Sidemeny over innhold",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Klarte ikke å lagre fil", "filesharing.saveFileError": "Klarte ikke å lagre fil",
"filesharing.startingFileShare": "Starter fildeling", "filesharing.startingFileShare": "Starter fildeling",
@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator fjernet filer", "moderator.clearFiles": "Moderator fjernet filer",
"moderator.muteAudio": "Moderator mutet lyden din", "moderator.muteAudio": "Moderator mutet lyden din",
"moderator.muteVideo": "Moderator mutet videoen din", "moderator.muteVideo": "Moderator mutet videoen din",
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": "Dodaj wideo", "label.addVideo": "Dodaj wideo",
"label.promoteAllPeers": "Wpuść wszystkich", "label.promoteAllPeers": "Wpuść wszystkich",
"label.moreActions": "Więcej akcji", "label.moreActions": "Więcej akcji",
"label.version": null,
"settings.settings": "Ustawienia", "settings.settings": "Ustawienia",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -144,6 +145,8 @@
"settings.autoGainControl": "Auto korekta wzmocnienia", "settings.autoGainControl": "Auto korekta wzmocnienia",
"settings.noiseSuppression": "Wyciszenie szumów", "settings.noiseSuppression": "Wyciszenie szumów",
"settings.drawerOverlayed": "Szuflada nad zawartością", "settings.drawerOverlayed": "Szuflada nad zawartością",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Nie można zapisać pliku", "filesharing.saveFileError": "Nie można zapisać pliku",
"filesharing.startingFileShare": "Próba udostępnienia pliku", "filesharing.startingFileShare": "Próba udostępnienia pliku",
@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator wyczyścił pliki", "moderator.clearFiles": "Moderator wyczyścił pliki",
"moderator.muteAudio": "Moderator wyciszył audio", "moderator.muteAudio": "Moderator wyciszył audio",
"moderator.muteVideo": "Moderator wyciszył twoje video", "moderator.muteVideo": "Moderator wyciszył twoje video",
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Definições", "settings.settings": "Definições",
"settings.camera": "Camera", "settings.camera": "Camera",
@ -144,6 +145,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Impossível de gravar o ficheiro", "filesharing.saveFileError": "Impossível de gravar o ficheiro",
"filesharing.startingFileShare": "Tentando partilha de ficheiro", "filesharing.startingFileShare": "Tentando partilha de ficheiro",
@ -189,5 +192,9 @@
"moderator.clearFiles": null, "moderator.clearFiles": null,
"moderator.muteAudio": null, "moderator.muteAudio": null,
"moderator.muteVideo": null, "moderator.muteVideo": null,
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Setări", "settings.settings": "Setări",
"settings.camera": "Cameră video", "settings.camera": "Cameră video",
@ -144,6 +145,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat", "filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat",
"filesharing.startingFileShare": "Partajarea fișierului", "filesharing.startingFileShare": "Partajarea fișierului",
@ -189,5 +192,9 @@
"moderator.clearFiles": null, "moderator.clearFiles": null,
"moderator.muteAudio": null, "moderator.muteAudio": null,
"moderator.muteVideo": null, "moderator.muteVideo": null,
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Ayarlar", "settings.settings": "Ayarlar",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -141,6 +142,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Dosya kaydedilemiyor", "filesharing.saveFileError": "Dosya kaydedilemiyor",
"filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor", "filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor",
@ -181,5 +184,14 @@
"devices.cameraDisconnected": "Kamera bağlı değil", "devices.cameraDisconnected": "Kamera bağlı değil",
"devices.cameraError": "Kameranıza erişilirken bir hata oluştu", "devices.cameraError": "Kameranıza erişilirken bir hata oluştu",
"moderator.muteScreenSharing": null
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -118,6 +118,7 @@
"label.addVideo": "新增視訊", "label.addVideo": "新增視訊",
"label.promoteAllPeers": "提升全部", "label.promoteAllPeers": "提升全部",
"label.moreActions": "更多", "label.moreActions": "更多",
"label.version": null,
"settings.settings": "設置", "settings.settings": "設置",
"settings.camera": "視訊來源", "settings.camera": "視訊來源",
@ -143,6 +144,8 @@
"settings.autoGainControl": "自動增益控制", "settings.autoGainControl": "自動增益控制",
"settings.noiseSuppression": "噪音消除", "settings.noiseSuppression": "噪音消除",
"settings.drawerOverlayed": "側邊欄覆蓋畫面", "settings.drawerOverlayed": "側邊欄覆蓋畫面",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "無法保存文件", "filesharing.saveFileError": "無法保存文件",
"filesharing.startingFileShare": "開始分享文件", "filesharing.startingFileShare": "開始分享文件",
@ -188,5 +191,9 @@
"moderator.clearFiles": "管理員清除了所有檔案", "moderator.clearFiles": "管理員清除了所有檔案",
"moderator.muteAudio": "您已被管理員靜音", "moderator.muteAudio": "您已被管理員靜音",
"moderator.muteVideo": "您的視訊已被管理員關閉", "moderator.muteVideo": "您的視訊已被管理員關閉",
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -119,6 +119,7 @@
"label.addVideo": null, "label.addVideo": null,
"label.promoteAllPeers": null, "label.promoteAllPeers": null,
"label.moreActions": null, "label.moreActions": null,
"label.version": null,
"settings.settings": "Налаштування", "settings.settings": "Налаштування",
"settings.camera": "Камера", "settings.camera": "Камера",
@ -144,6 +145,8 @@
"settings.autoGainControl": null, "settings.autoGainControl": null,
"settings.noiseSuppression": null, "settings.noiseSuppression": null,
"settings.drawerOverlayed": null, "settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Неможливо зберегти файл", "filesharing.saveFileError": "Неможливо зберегти файл",
"filesharing.startingFileShare": "Спроба поділитися файлом", "filesharing.startingFileShare": "Спроба поділитися файлом",
@ -189,5 +192,9 @@
"moderator.clearFiles": null, "moderator.clearFiles": null,
"moderator.muteAudio": null, "moderator.muteAudio": null,
"moderator.muteVideo": null, "moderator.muteVideo": null,
"moderator.muteScreenSharing": null "moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -3,17 +3,35 @@
* after the given amount of milliseconds has passed since * after the given amount of milliseconds has passed since
* the last time the callback function was called. * the last time the callback function was called.
*/ */
export const idle = (callback, delay) => export const idle = (callback, delay) =>
{ {
let handle; let handle;
return () => return () =>
{ {
if (handle) if (handle)
{ {
clearTimeout(handle); clearTimeout(handle);
} }
handle = setTimeout(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-regexp": 2,
"no-invalid-this": 2, "no-invalid-this": 2,
"no-irregular-whitespace": 2, "no-irregular-whitespace": 2,
"no-trailing-spaces": [
"error",
{
"ignoreComments": true
}
],
"no-lonely-if": 2, "no-lonely-if": 2,
"no-mixed-operators": 2, "no-mixed-operators": 2,
"no-mixed-spaces-and-tabs": 2, "no-mixed-spaces-and-tabs": 2,

View File

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

View File

@ -83,9 +83,9 @@ class Peer extends EventEmitter
{ {
if (this.closed) if (this.closed)
return; return;
logger.debug('"disconnect" event [id:%s]', this.id); logger.debug('"disconnect" event [id:%s]', this.id);
this.close(); this.close();
}); });
} }
@ -208,7 +208,7 @@ class Peer extends EventEmitter
const oldDisplayName = this._displayName; const oldDisplayName = this._displayName;
this._displayName = displayName; this._displayName = displayName;
this.emit('displayNameChanged', { oldDisplayName }); this.emit('displayNameChanged', { oldDisplayName });
} }
} }
@ -225,7 +225,7 @@ class Peer extends EventEmitter
const oldPicture = this._picture; const oldPicture = this._picture;
this._picture = picture; this._picture = picture;
this.emit('pictureChanged', { oldPicture }); this.emit('pictureChanged', { oldPicture });
} }
} }

View File

@ -3,6 +3,7 @@ const AwaitQueue = require('awaitqueue');
const axios = require('axios'); const axios = require('axios');
const Logger = require('./Logger'); const Logger = require('./Logger');
const Lobby = require('./Lobby'); const Lobby = require('./Lobby');
const { SocketTimeoutError } = require('./errors');
const { v4: uuidv4 } = require('uuid'); const { v4: uuidv4 } = require('uuid');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const userRoles = require('../userRoles'); const userRoles = require('../userRoles');
@ -492,7 +493,7 @@ class Room extends EventEmitter
peer.socket.handshake.session.save(); peer.socket.handshake.session.save();
let turnServers; let turnServers;
if ('turnAPIURI' in config) if ('turnAPIURI' in config)
{ {
try try
@ -506,7 +507,7 @@ class Room extends EventEmitter
'ip' : peer.socket.request.connection.remoteAddress 'ip' : peer.socket.request.connection.remoteAddress
} }
}); });
turnServers = [ { turnServers = [ {
urls : data.uris, urls : data.uris,
username : data.username, username : data.username,
@ -517,7 +518,7 @@ class Room extends EventEmitter
{ {
if ('backupTurnServers' in config) if ('backupTurnServers' in config)
turnServers = config.backupTurnServers; turnServers = config.backupTurnServers;
logger.error('_peerJoining() | error on REST turn [error:"%o"]', error); logger.error('_peerJoining() | error on REST turn [error:"%o"]', error);
} }
} }
@ -525,7 +526,7 @@ class Room extends EventEmitter
{ {
turnServers = config.backupTurnServers; turnServers = config.backupTurnServers;
} }
this._notification(peer.socket, 'roomReady', { turnServers }); this._notification(peer.socket, 'roomReady', { turnServers });
} }
}) })
@ -778,7 +779,7 @@ class Room extends EventEmitter
// initiate mediasoup Transports and be ready when he later joins. // initiate mediasoup Transports and be ready when he later joins.
const { forceTcp, producing, consuming } = request.data; const { forceTcp, producing, consuming } = request.data;
const webRtcTransportOptions = const webRtcTransportOptions =
{ {
...config.mediasoup.webRtcTransport, ...config.mediasoup.webRtcTransport,
@ -1186,7 +1187,7 @@ class Room extends EventEmitter
throw new Error('peer not authorized'); throw new Error('peer not authorized');
const { chatMessage } = request.data; const { chatMessage } = request.data;
this._chatHistory.push(chatMessage); this._chatHistory.push(chatMessage);
// Spread to others // Spread to others
@ -1205,7 +1206,7 @@ class Room extends EventEmitter
{ {
if (!this._hasPermission(peer, MODERATE_CHAT)) if (!this._hasPermission(peer, MODERATE_CHAT))
throw new Error('peer not authorized'); throw new Error('peer not authorized');
this._chatHistory = []; this._chatHistory = [];
// Spread to others // Spread to others
@ -1258,7 +1259,7 @@ class Room extends EventEmitter
case 'setAccessCode': case 'setAccessCode':
{ {
const { accessCode } = request.data; const { accessCode } = request.data;
this._accessCode = accessCode; this._accessCode = accessCode;
// Spread to others // Spread to others
@ -1278,7 +1279,7 @@ class Room extends EventEmitter
case 'setJoinByAccessCode': case 'setJoinByAccessCode':
{ {
const { joinByAccessCode } = request.data; const { joinByAccessCode } = request.data;
this._joinByAccessCode = joinByAccessCode; this._joinByAccessCode = joinByAccessCode;
// Spread to others // Spread to others
@ -1327,7 +1328,7 @@ class Room extends EventEmitter
throw new Error('peer not authorized'); throw new Error('peer not authorized');
const { magnetUri } = request.data; const { magnetUri } = request.data;
this._fileHistory.push({ peerId: peer.id, magnetUri: magnetUri }); this._fileHistory.push({ peerId: peer.id, magnetUri: magnetUri });
// Spread to others // Spread to others
@ -1346,7 +1347,7 @@ class Room extends EventEmitter
{ {
if (!this._hasPermission(peer, MODERATE_FILES)) if (!this._hasPermission(peer, MODERATE_FILES))
throw new Error('peer not authorized'); throw new Error('peer not authorized');
this._fileHistory = []; this._fileHistory = [];
// Spread to others // Spread to others
@ -1759,9 +1760,9 @@ class Room extends EventEmitter
if (called) if (called)
return; return;
called = true; called = true;
callback(new Error('Request timeout.')); callback(new SocketTimeoutError('Request timed out'));
}, },
10000 config.requestTimeout || 20000
); );
return (...args) => return (...args) =>
@ -1775,7 +1776,7 @@ class Room extends EventEmitter
}; };
} }
_request(socket, method, data = {}) _sendRequest(socket, method, data = {})
{ {
return new Promise((resolve, reject) => 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) _notification(socket, method, data = {}, broadcast = false, includeSender = false)
{ {
if (broadcast) if (broadcast)
@ -1879,7 +1907,7 @@ class Room extends EventEmitter
for (const routerId of this._mediasoupRouters.keys()) for (const routerId of this._mediasoupRouters.keys())
{ {
const routerLoad = const routerLoad =
Object.values(this._peers).filter((peer) => peer.routerId === routerId).length; Object.values(this._peers).filter((peer) => peer.routerId === routerId).length;
if (routerLoad < load) if (routerLoad < load)

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
};