diff --git a/app/.env b/app/.env
new file mode 100644
index 0000000..86c714e
--- /dev/null
+++ b/app/.env
@@ -0,0 +1,2 @@
+REACT_APP_VERSION=$npm_package_version
+REACT_APP_NAME=$npm_package_name
\ No newline at end of file
diff --git a/app/.eslintrc.json b/app/.eslintrc.json
index 44a50ba..d052d77 100644
--- a/app/.eslintrc.json
+++ b/app/.eslintrc.json
@@ -159,6 +159,12 @@
"no-inner-declarations": 2,
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
+ "no-trailing-spaces": [
+ "error",
+ {
+ "ignoreComments": true
+ }
+ ],
"no-lonely-if": 2,
"no-mixed-operators": 2,
"no-mixed-spaces-and-tabs": 2,
diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js
index e07d994..84ed59e 100644
--- a/app/public/config/config.example.js
+++ b/app/public/config/config.example.js
@@ -5,6 +5,26 @@ var config =
developmentPort : 3443,
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:
* low ~ 320x240
@@ -26,6 +46,15 @@ var config =
{ scaleResolutionDownBy: 1 }
],
+ /**
+ * Alternative simulcast setting:
+ * [
+ * { maxBitRate: 50000 },
+ * { maxBitRate: 1000000 },
+ * { maxBitRate: 4800000 }
+ *],
+ **/
+
/**
* White listing browsers that support audio output device selection.
* It is not yet fully implemented in Firefox.
@@ -37,12 +66,13 @@ var config =
'opera'
],
// Socket.io request timeout
- requestTimeout : 10000,
+ requestTimeout : 20000,
+ requestRetries : 3,
transportOptions :
{
tcp : true
},
- defaultAudio :
+ defaultAudio :
{
sampleRate : 48000,
channelCount : 1,
@@ -55,14 +85,16 @@ var config =
},
/**
- * Set the auto mute / Push To Talk threshold
- * default value is 4
+ * Set max number participants in one room that join
+ * 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
* full mesh audio strongly decrease room capacity!
*/
- autoMuteThreshold : 4,
+ autoMuteThreshold : 4,
background : 'images/background.jpg',
defaultLayout : 'democratic', // democratic, filmstrip
// If true, will show media control buttons in separate
@@ -75,9 +107,12 @@ var config =
notificationPosition : 'right',
// Timeout for autohiding topbar and button control bar
hideTimeout : 3000,
+ // max number of participant that will be visible in
+ // as speaker
lastN : 4,
mobileLastN : 1,
- // Highest number of speakers user can select
+ // Highest number of lastN the user can select manually in
+ // userinteface
maxLastN : 5,
// If truthy, users can NOT change number of speakers visible
lockLastN : false,
diff --git a/app/public/index.html b/app/public/index.html
index 86b1003..17a531d 100644
--- a/app/public/index.html
+++ b/app/public/index.html
@@ -16,6 +16,44 @@
Multiparty Meeting
+
+
+
You need to enable JavaScript to run this app.
diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js
index e22341c..01e172c 100644
--- a/app/src/RoomClient.js
+++ b/app/src/RoomClient.js
@@ -1,6 +1,7 @@
import Logger from './Logger';
import hark from 'hark';
import { getSignalingUrl } from './urlFactory';
+import { SocketTimeoutError } from './utils';
import * as requestActions from './actions/requestActions';
import * as meActions from './actions/meActions';
import * as roomActions from './actions/roomActions';
@@ -315,7 +316,7 @@ export default class RoomClient
{
const newPeerId = this._spotlights.getNextAsSelected(
store.getState().room.selectedPeerId);
-
+
if (newPeerId) this.setSelectedPeer(newPeerId);
break;
}
@@ -575,7 +576,7 @@ export default class RoomClient
if (called)
return;
called = true;
- callback(new Error('Request timeout.'));
+ callback(new SocketTimeoutError('Request timed out'));
},
ROOM_OPTIONS.requestTimeout
);
@@ -591,13 +592,13 @@ export default class RoomClient
};
}
- sendRequest(method, data)
+ _sendRequest(method, data)
{
return new Promise((resolve, reject) =>
{
if (!this._signalingSocket)
{
- reject('No socket connection.');
+ reject('No socket connection');
}
else
{
@@ -607,13 +608,9 @@ export default class RoomClient
this.timeoutCallback((err, response) =>
{
if (err)
- {
reject(err);
- }
else
- {
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)
{
logger.debug('changeDisplayName() [displayName:"%s"]', displayName);
@@ -805,7 +829,7 @@ export default class RoomClient
}
});
- torrent.on('done', () =>
+ torrent.on('done', () =>
{
store.dispatch(
fileActions.setFileDone(
@@ -955,7 +979,7 @@ export default class RoomClient
{
await this.sendRequest(
'resumeProducer', { producerId: this._micProducer.id });
-
+
store.dispatch(
producerActions.setProducerResumed(this._micProducer.id));
}
@@ -1007,23 +1031,23 @@ export default class RoomClient
}
}
- disconnectLocalHark()
+ disconnectLocalHark()
{
logger.debug('disconnectLocalHark() | Stopping harkStream.');
- if (this._harkStream != null)
+ if (this._harkStream != null)
{
this._harkStream.getAudioTracks()[0].stop();
this._harkStream = null;
}
- if (this._hark != null)
+ if (this._hark != null)
{
logger.debug('disconnectLocalHark() Stopping hark.');
this._hark.stop();
}
}
- connectLocalHark(track)
+ connectLocalHark(track)
{
logger.debug('connectLocalHark() | Track:%o', track);
this._harkStream = new MediaStream();
@@ -1034,23 +1058,23 @@ export default class RoomClient
if (!this._harkStream.getAudioTracks()[0])
throw new Error('getMicStream():something went wrong with hark');
- this._hark = hark(this._harkStream,
- {
- play : false,
- interval : 5,
+ this._hark = hark(this._harkStream,
+ {
+ play : false,
+ interval : 10,
threshold : store.getState().settings.noiseThreshold,
- history : 30
+ history : 100
});
this._hark.lastVolume = -100;
- this._hark.on('volume_change', (volume) =>
+ this._hark.on('volume_change', (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;
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
@@ -1059,7 +1083,7 @@ export default class RoomClient
this._hark.on('speaking', () =>
{
store.dispatch(meActions.setIsSpeaking(true));
- if ((store.getState().settings.voiceActivatedUnmute ||
+ if ((store.getState().settings.voiceActivatedUnmute ||
store.getState().me.isAutoMuted) &&
this._micProducer &&
this._micProducer.paused)
@@ -1071,9 +1095,9 @@ export default class RoomClient
this._hark.on('stopped_speaking', () =>
{
store.dispatch(meActions.setIsSpeaking(false));
- if (store.getState().settings.voiceActivatedUnmute &&
+ if (store.getState().settings.voiceActivatedUnmute &&
this._micProducer &&
- !this._micProducer.paused)
+ !this._micProducer.paused)
{
this._micProducer.pause();
store.dispatch(meActions.setAutoMuted(true));
@@ -1089,7 +1113,7 @@ export default class RoomClient
meActions.setAudioInProgress(true));
try
- {
+ {
const device = this._audioDevices[deviceId];
if (!device)
@@ -1208,7 +1232,7 @@ export default class RoomClient
...VIDEO_CONSTRAINS[resolution]
}
});
-
+
if (stream)
{
const track = stream.getVideoTracks()[0];
@@ -1219,15 +1243,15 @@ export default class RoomClient
{
await this._webcamProducer.replaceTrack({ track });
}
- else
+ else
{
this._webcamProducer = await this._sendTransport.produce({
track,
- appData :
+ appData :
{
source : 'webcam'
}
- });
+ });
}
store.dispatch(
@@ -1237,7 +1261,7 @@ export default class RoomClient
{
logger.warn('getVideoTracks Error: First Video Track is null');
}
-
+
}
else
{
@@ -1271,7 +1295,7 @@ export default class RoomClient
if (!device)
throw new Error('no webcam devices');
-
+
logger.debug(
'changeWebcam() | new selected webcam [device:%o]',
device);
@@ -1299,17 +1323,17 @@ export default class RoomClient
{
await this._webcamProducer.replaceTrack({ track });
}
- else
+ else
{
this._webcamProducer = await this._sendTransport.produce({
track,
- appData :
+ appData :
{
source : 'webcam'
}
- });
+ });
}
-
+
store.dispatch(
producerActions.setProducerTrack(this._webcamProducer.id, track));
@@ -1318,7 +1342,7 @@ export default class RoomClient
{
logger.warn('getVideoTracks Error: First Video Track is null');
}
-
+
}
else
{
@@ -2106,7 +2130,7 @@ export default class RoomClient
const { displayName } = store.getState().settings;
const { picture } = store.getState().me;
-
+
await this.sendRequest('changeDisplayName', { displayName });
await this.sendRequest('changePicture', { picture });
break;
@@ -2115,10 +2139,10 @@ export default class RoomClient
case 'signInRequired':
{
store.dispatch(roomActions.setSignInRequired(true));
-
+
break;
}
-
+
case 'overRoomLimit':
{
store.dispatch(roomActions.setOverRoomLimit(true));
@@ -2134,24 +2158,24 @@ export default class RoomClient
store.dispatch(roomActions.toggleJoined());
store.dispatch(roomActions.setInLobby(false));
-
+
await this._joinRoom({ joinVideo });
-
+
break;
}
case 'roomBack':
{
await this._joinRoom({ joinVideo });
-
+
break;
}
-
+
case 'lockRoom':
{
store.dispatch(
roomActions.setRoomLocked());
-
+
store.dispatch(requestActions.notify(
{
text : intl.formatMessage({
@@ -2159,15 +2183,15 @@ export default class RoomClient
defaultMessage : 'Room is now locked'
})
}));
-
+
break;
}
-
+
case 'unlockRoom':
{
store.dispatch(
roomActions.setRoomUnLocked());
-
+
store.dispatch(requestActions.notify(
{
text : intl.formatMessage({
@@ -2175,21 +2199,21 @@ export default class RoomClient
defaultMessage : 'Room is now unlocked'
})
}));
-
+
break;
}
-
+
case 'parkedPeer':
{
const { peerId } = notification.data;
-
+
store.dispatch(
lobbyPeerActions.addLobbyPeer(peerId));
store.dispatch(
roomActions.setToolbarsVisible(true));
this._soundNotification();
-
+
store.dispatch(requestActions.notify(
{
text : intl.formatMessage({
@@ -2197,7 +2221,7 @@ export default class RoomClient
defaultMessage : 'New participant entered the lobby'
})
}));
-
+
break;
}
@@ -2226,7 +2250,7 @@ export default class RoomClient
)
);
});
-
+
store.dispatch(
roomActions.setToolbarsVisible(true));
@@ -2243,14 +2267,14 @@ export default class RoomClient
break;
}
-
+
case 'lobby:peerClosed':
{
const { peerId } = notification.data;
-
+
store.dispatch(
lobbyPeerActions.removeLobbyPeer(peerId));
-
+
store.dispatch(requestActions.notify(
{
text : intl.formatMessage({
@@ -2258,10 +2282,10 @@ export default class RoomClient
defaultMessage : 'Participant in lobby left'
})
}));
-
+
break;
}
-
+
case 'lobby:promotedPeer':
{
const { peerId } = notification.data;
@@ -2271,7 +2295,7 @@ export default class RoomClient
break;
}
-
+
case 'lobby:changeDisplayName':
{
const { peerId, displayName } = notification.data;
@@ -2291,11 +2315,11 @@ export default class RoomClient
break;
}
-
+
case 'lobby:changePicture':
{
const { peerId, picture } = notification.data;
-
+
store.dispatch(
lobbyPeerActions.setLobbyPeerPicture(picture, peerId));
@@ -2313,7 +2337,7 @@ export default class RoomClient
case 'setAccessCode':
{
const { accessCode } = notification.data;
-
+
store.dispatch(
roomActions.setAccessCode(accessCode));
@@ -2327,14 +2351,14 @@ export default class RoomClient
break;
}
-
+
case 'setJoinByAccessCode':
{
const { joinByAccessCode } = notification.data;
-
+
store.dispatch(
roomActions.setJoinByAccessCode(joinByAccessCode));
-
+
if (joinByAccessCode)
{
store.dispatch(requestActions.notify(
@@ -2345,7 +2369,7 @@ export default class RoomClient
})
}));
}
- else
+ else
{
store.dispatch(requestActions.notify(
{
@@ -2358,20 +2382,20 @@ export default class RoomClient
break;
}
-
+
case 'activeSpeaker':
{
const { peerId } = notification.data;
-
+
store.dispatch(
roomActions.setRoomActiveSpeaker(peerId));
if (peerId && peerId !== this._peerId)
this._spotlights.handleActiveSpeaker(peerId);
-
+
break;
}
-
+
case 'changeDisplayName':
{
const { peerId, displayName, oldDisplayName } = notification.data;
@@ -2579,74 +2603,74 @@ export default class RoomClient
{
const { consumerId } = notification.data;
const consumer = this._consumers.get(consumerId);
-
+
if (!consumer)
break;
-
+
consumer.close();
-
+
if (consumer.hark != null)
consumer.hark.stop();
-
+
this._consumers.delete(consumerId);
-
+
const { peerId } = consumer.appData;
-
+
store.dispatch(
consumerActions.removeConsumer(consumerId, peerId));
-
+
break;
}
-
+
case 'consumerPaused':
{
const { consumerId } = notification.data;
const consumer = this._consumers.get(consumerId);
-
+
if (!consumer)
break;
-
+
store.dispatch(
consumerActions.setConsumerPaused(consumerId, 'remote'));
break;
}
-
+
case 'consumerResumed':
{
const { consumerId } = notification.data;
const consumer = this._consumers.get(consumerId);
-
+
if (!consumer)
break;
-
+
store.dispatch(
consumerActions.setConsumerResumed(consumerId, 'remote'));
-
+
break;
}
-
+
case 'consumerLayersChanged':
{
const { consumerId, spatialLayer, temporalLayer } = notification.data;
const consumer = this._consumers.get(consumerId);
-
+
if (!consumer)
break;
-
+
store.dispatch(consumerActions.setConsumerCurrentLayers(
consumerId, spatialLayer, temporalLayer));
-
+
break;
}
-
+
case 'consumerScore':
{
const { consumerId, score } = notification.data;
-
+
store.dispatch(
consumerActions.setConsumerScore(consumerId, score));
-
+
break;
}
@@ -2690,7 +2714,7 @@ export default class RoomClient
store.dispatch(requestActions.notify(
{
text : intl.formatMessage({
- id : 'moderator.muteScreenSharingModerator',
+ id : 'moderator.stopScreenSharing',
defaultMessage : 'Moderator stopped your screen sharing'
})
}));
@@ -2760,7 +2784,7 @@ export default class RoomClient
break;
}
-
+
default:
{
logger.error(
@@ -2807,7 +2831,7 @@ export default class RoomClient
this._webTorrent.on('error', (error) =>
{
logger.error('Filesharing [error:"%o"]', error);
-
+
store.dispatch(requestActions.notify(
{
type : 'error',
@@ -3029,7 +3053,7 @@ export default class RoomClient
);
}
- locked ?
+ locked ?
store.dispatch(roomActions.setRoomLocked()) :
store.dispatch(roomActions.setRoomUnLocked());
@@ -3053,16 +3077,20 @@ export default class RoomClient
if (!this._muted)
{
await this.enableMic();
- const { autoMuteThreshold } = store.getState().settings;
-
- if (autoMuteThreshold && peers.length > autoMuteThreshold)
+ let autoMuteThreshold = 4;
+
+ if ('autoMuteThreshold' in window.config)
+ {
+ autoMuteThreshold = window.config.autoMuteThreshold;
+ }
+ if (autoMuteThreshold && peers.length >= autoMuteThreshold)
this.muteMic();
}
if (joinVideo && this._mediasoupDevice.canProduce('video'))
this.enableWebcam();
}
-
+
await this._updateAudioOutputDevices();
const { selectedAudioOutputDevice } = store.getState().settings;
@@ -3075,7 +3103,7 @@ export default class RoomClient
)
);
}
-
+
store.dispatch(roomActions.setRoomState('connected'));
// Clean all the existing notifications.
@@ -3259,7 +3287,7 @@ export default class RoomClient
if (!device)
throw new Error('no webcam devices');
-
+
logger.debug(
'addExtraVideo() | new selected webcam [device:%o]',
device);
@@ -3304,7 +3332,7 @@ export default class RoomClient
{
videoGoogleStartBitrate : 1000
},
- appData :
+ appData :
{
source : 'extravideo'
}
@@ -3314,7 +3342,7 @@ export default class RoomClient
{
producer = await this._sendTransport.produce({
track,
- appData :
+ appData :
{
source : 'extravideo'
}
@@ -3408,7 +3436,7 @@ export default class RoomClient
if (!device)
throw new Error('no audio devices');
-
+
logger.debug(
'enableMic() | new selected audio device [device:%o]',
device);
@@ -3445,7 +3473,7 @@ export default class RoomClient
opusPtime : '3',
opusMaxPlaybackRate : 48000
},
- appData :
+ appData :
{ source: 'mic' }
});
@@ -3605,7 +3633,7 @@ export default class RoomClient
{
videoGoogleStartBitrate : 1000
},
- appData :
+ appData :
{
source : 'screen'
}
@@ -3615,7 +3643,7 @@ export default class RoomClient
{
this._screenSharingProducer = await this._sendTransport.produce({
track,
- appData :
+ appData :
{
source : 'screen'
}
@@ -3733,7 +3761,7 @@ export default class RoomClient
if (!device)
throw new Error('no webcam devices');
-
+
logger.debug(
'_setWebcamProducer() | new selected webcam [device:%o]',
device);
@@ -3776,7 +3804,7 @@ export default class RoomClient
{
videoGoogleStartBitrate : 1000
},
- appData :
+ appData :
{
source : 'webcam'
}
@@ -3786,7 +3814,7 @@ export default class RoomClient
{
this._webcamProducer = await this._sendTransport.produce({
track,
- appData :
+ appData :
{
source : 'webcam'
}
diff --git a/app/src/Spotlights.js b/app/src/Spotlights.js
index 10d6638..9fc2301 100644
--- a/app/src/Spotlights.js
+++ b/app/src/Spotlights.js
@@ -60,7 +60,7 @@ export default class Spotlights extends EventEmitter
const oldIndex = this._unmutablePeerList.indexOf(peerId);
let index = oldIndex;
-
+
index++;
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);
let index = oldIndex;
-
+
index--;
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);
const index = this._selectedSpotlights.indexOf(peerId);
-
+
if (index !== -1)
{
this._selectedSpotlights = [];
@@ -177,7 +177,7 @@ export default class Spotlights extends EventEmitter
{
logger.debug(
'room "newpeer" event [id: "%s"]', id);
-
+
if (this._peerList.indexOf(id) === -1) // We don't have this peer in the list
{
logger.debug('_handlePeer() | adding peer [peerId: "%s"]', id);
diff --git a/app/src/actions/chatActions.js b/app/src/actions/chatActions.js
index f7b0cf3..0f9f066 100644
--- a/app/src/actions/chatActions.js
+++ b/app/src/actions/chatActions.js
@@ -9,7 +9,7 @@ export const addResponseMessage = (message) =>
type : 'ADD_NEW_RESPONSE_MESSAGE',
payload : { message }
});
-
+
export const addChatHistory = (chatHistory) =>
({
type : 'ADD_CHAT_HISTORY',
diff --git a/app/src/actions/meActions.js b/app/src/actions/meActions.js
index be7c1ee..7e7d3de 100644
--- a/app/src/actions/meActions.js
+++ b/app/src/actions/meActions.js
@@ -80,13 +80,13 @@ export const setAudioOutputInProgress = (flag) =>
type : 'SET_AUDIO_OUTPUT_IN_PROGRESS',
payload : { flag }
});
-
+
export const setWebcamInProgress = (flag) =>
({
type : 'SET_WEBCAM_IN_PROGRESS',
payload : { flag }
});
-
+
export const setScreenShareInProgress = (flag) =>
({
type : 'SET_SCREEN_SHARE_IN_PROGRESS',
diff --git a/app/src/actions/peerActions.js b/app/src/actions/peerActions.js
index 1a1e2d3..7cbea6f 100644
--- a/app/src/actions/peerActions.js
+++ b/app/src/actions/peerActions.js
@@ -26,13 +26,13 @@ export const setPeerVideoInProgress = (peerId, flag) =>
type : 'SET_PEER_VIDEO_IN_PROGRESS',
payload : { peerId, flag }
});
-
+
export const setPeerAudioInProgress = (peerId, flag) =>
({
type : 'SET_PEER_AUDIO_IN_PROGRESS',
payload : { peerId, flag }
});
-
+
export const setPeerScreenInProgress = (peerId, flag) =>
({
type : 'SET_PEER_SCREEN_IN_PROGRESS',
diff --git a/app/src/actions/settingsActions.js b/app/src/actions/settingsActions.js
index 90b019a..0a4d626 100644
--- a/app/src/actions/settingsActions.js
+++ b/app/src/actions/settingsActions.js
@@ -73,14 +73,14 @@ export const setNoiseSuppression = (noiseSuppression) =>
export const setVoiceActivatedUnmute = (voiceActivatedUnmute) =>
({
- type: 'SET_VOICE_ACTIVATED_UNMUTE',
- payload: { voiceActivatedUnmute }
+ type : 'SET_VOICE_ACTIVATED_UNMUTE',
+ payload : { voiceActivatedUnmute }
});
export const setNoiseThreshold = (noiseThreshold) =>
({
- type: 'SET_NOISE_THRESHOLD',
- payload: { noiseThreshold }
+ type : 'SET_NOISE_THRESHOLD',
+ payload : { noiseThreshold }
});
export const setDefaultAudio = (audio) =>
@@ -89,21 +89,6 @@ export const setDefaultAudio = (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 = () =>
({
type : 'TOGGLE_HIDDEN_CONTROLS'
diff --git a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js
index 050994d..e955b53 100644
--- a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js
+++ b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js
@@ -39,7 +39,7 @@ const ListLobbyPeer = (props) =>
const picture = peer.picture || EmptyAvatar;
return (
-
{ lobbyPeers.length > 0 ?
-
diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js
index bde4a5c..0b32639 100644
--- a/app/src/components/Containers/Me.js
+++ b/app/src/components/Containers/Me.js
@@ -287,9 +287,9 @@ const Me = (props) =>
defaultMessage : 'Start screen sharing'
});
}
- const [
- screenShareTooltipOpen,
- screenShareTooltipSetOpen
+ const [
+ screenShareTooltipOpen,
+ screenShareTooltipSetOpen
] = React.useState(false);
const screenShareTooltipHandleClose = () =>
@@ -380,7 +380,7 @@ const Me = (props) =>
}}
style={spacingStyle}
>
-
+
{ me.browser.platform !== 'mobile' && smallContainer &&
className={classes.smallContainer}
disabled={!me.canSendMic || me.audioInProgress}
color={
- micState === 'on' ?
- settings.voiceActivatedUnmute && !me.isAutoMuted ?
+ micState === 'on' ?
+ settings.voiceActivatedUnmute && !me.isAutoMuted ?
'primary'
: 'default'
: 'secondary'}
@@ -474,7 +474,7 @@ const Me = (props) =>
}}
>
{ micState === 'on' ?
-
@@ -492,9 +492,9 @@ const Me = (props) =>
})}
className={classes.fab}
disabled={!me.canSendMic || me.audioInProgress}
- color={micState === 'on' ?
+ color={micState === 'on' ?
settings.voiceActivatedUnmute && !me.isAutoMuted? 'primary'
- : 'default'
+ : 'default'
: 'secondary'}
size='large'
onClick={() =>
@@ -509,8 +509,10 @@ const Me = (props) =>
>
{ micState === 'on' ?
:
@@ -574,9 +576,9 @@ const Me = (props) =>
}
{ me.browser.platform !== 'mobile' &&
-
{ smallContainer ?
@@ -868,7 +870,7 @@ const Me = (props) =>
defaultMessage='ME'
/>
-
+
const mapStateToProps = (state) =>
{
let volume;
-
+
// 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)
- 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)));
- }
+ }
// noiseVolume over threshold: no noise but voice
else { volume = 0; }
@@ -949,7 +951,7 @@ export default withRoomContext(connect(
return (
prev.room === next.room &&
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]) &&
prev.peers === next.peers &&
prev.producers === next.producers &&
diff --git a/app/src/components/Containers/Peer.js b/app/src/components/Containers/Peer.js
index 9c2a6fa..c81627b 100644
--- a/app/src/components/Containers/Peer.js
+++ b/app/src/components/Containers/Peer.js
@@ -228,14 +228,14 @@ const Peer = (props) =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
setHover(true);
}}
onTouchEnd={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
touchTimeout = setTimeout(() =>
{
setHover(false);
@@ -445,14 +445,14 @@ const Peer = (props) =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
setHover(true);
}}
onTouchEnd={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
touchTimeout = setTimeout(() =>
{
setHover(false);
@@ -471,7 +471,7 @@ const Peer = (props) =>
}
-
+
setHover(true)}
@@ -480,14 +480,14 @@ const Peer = (props) =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
setHover(true);
}}
onTouchEnd={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
touchTimeout = setTimeout(() =>
{
setHover(false);
@@ -544,7 +544,7 @@ const Peer = (props) =>
}
}
-
+
}
-
+
{
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
setHover(true);
}}
onTouchEnd={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
touchTimeout = setTimeout(() =>
{
setHover(false);
@@ -663,7 +663,7 @@ const Peer = (props) =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
setHover(true);
}}
onTouchEnd={() =>
@@ -671,7 +671,7 @@ const Peer = (props) =>
if (touchTimeout)
clearTimeout(touchTimeout);
-
+
touchTimeout = setTimeout(() =>
{
setHover(false);
diff --git a/app/src/components/Containers/Volume.js b/app/src/components/Containers/Volume.js
index 81a050b..4ac09bb 100644
--- a/app/src/components/Containers/Volume.js
+++ b/app/src/components/Containers/Volume.js
@@ -58,27 +58,27 @@ const styles = () =>
'&.level6' :
{
height : '60%',
- backgroundColor : 'rgba(255, 0, 0, 0.65)'
+ backgroundColor : 'rgba(255, 165, 0, 0.65)'
},
'&.level7' :
{
height : '70%',
- backgroundColor : 'rgba(255, 0, 0, 0.65)'
+ backgroundColor : 'rgba(255, 100, 0, 0.65)'
},
'&.level8' :
{
height : '80%',
- backgroundColor : 'rgba(0, 0, 0, 0.65)'
+ backgroundColor : 'rgba(255, 60, 0, 0.65)'
},
'&.level9' :
{
height : '90%',
- backgroundColor : 'rgba(0, 0, 0, 0.65)'
+ backgroundColor : 'rgba(255, 30, 0, 0.65)'
},
'&.level10' :
{
height : '100%',
- backgroundColor : 'rgba(0, 0, 0, 0.65)'
+ backgroundColor : 'rgba(255, 0, 0, 0.65)'
}
},
volumeSmall :
diff --git a/app/src/components/Controls/About.js b/app/src/components/Controls/About.js
index d361a8c..103df43 100644
--- a/app/src/components/Controls/About.js
+++ b/app/src/components/Controls/About.js
@@ -42,8 +42,9 @@ const styles = (theme) =>
},
link :
{
- display : 'block',
- textAlign : 'center'
+ display : 'block',
+ textAlign : 'center',
+ marginBottom : theme.spacing(1)
}
});
@@ -68,15 +69,16 @@ const About = ({
/>
-
+
Contributions to this work were made on behalf of the GÉANT
project, a project that has received funding from the
- European Union’s Horizon 2020 research and innovation
- programme under Grant Agreement No. 731122 (GN4-2).
+ European Union’s Horizon 2020 research and innovation
+ programme under Grant Agreement No. 731122 (GN4-2).
On behalf of GÉANT project, GÉANT Association is the sole
- owner of the copyright in all material which was developed
- by a member of the GÉANT project.
-
+ owner of the copyright in all material which was developed
+ by a member of the GÉANT project.
+
+
GÉANT Vereniging (Association) is registered with the
Chamber of Commerce in Amsterdam with registration number
40535155 and operates in the UK as a branch of GÉANT
@@ -87,6 +89,13 @@ const About = ({
https://edumeet.org
+
+
+ :{` ${process.env.REACT_APP_VERSION}`}
+
{ window.config.logo && }
@@ -97,7 +106,7 @@ const About = ({
/>
-
+
);
};
@@ -105,7 +114,7 @@ About.propTypes =
{
roomClient : PropTypes.object.isRequired,
aboutOpen : PropTypes.bool.isRequired,
- handleCloseAbout : PropTypes.func.isRequired,
+ handleCloseAbout : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};
diff --git a/app/src/components/Controls/Help.js b/app/src/components/Controls/Help.js
index ab188b8..d789d10 100644
--- a/app/src/components/Controls/Help.js
+++ b/app/src/components/Controls/Help.js
@@ -107,13 +107,13 @@ const Help = ({
}
/>
-
+
- {shortcuts.map((value, index) =>
+ {shortcuts.map((value, index) =>
{
return (
-
+
{value.key}
-
+
);
};
@@ -142,7 +142,7 @@ Help.propTypes =
{
roomClient : PropTypes.object.isRequired,
helpOpen : PropTypes.bool.isRequired,
- handleCloseHelp : PropTypes.func.isRequired,
+ handleCloseHelp : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};
diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js
index 148f4a7..55bb849 100644
--- a/app/src/components/Controls/TopBar.js
+++ b/app/src/components/Controls/TopBar.js
@@ -312,7 +312,7 @@ const TopBar = (props) =>
-
}
-
{ lobbyPeers.length > 0 &&
-
})}
className={classes.actionButton}
color='inherit'
- onClick={() =>
+ onClick={() =>
{
loggedIn ? roomClient.logout() : roomClient.login();
}}
@@ -472,6 +472,34 @@ const TopBar = (props) =>
}
+ { lobbyPeers.length > 0 &&
+
+
+ setLockDialogOpen(!room.lockDialogOpen)}
+ >
+
+
+
+
+
+
+ }
- { lobbyPeers.length > 0 &&
-
-
- setLockDialogOpen(!room.lockDialogOpen)}
- >
-
-
-
-
-
-
- }
/>
-
+
{
handleMenuClose();
setHelpOpen(!room.helpOpen);
@@ -578,8 +578,8 @@ const TopBar = (props) =>
/>
-
+
{
handleMenuClose();
setAboutOpen(!room.aboutOpen);
@@ -612,7 +612,7 @@ const TopBar = (props) =>
{ loginEnabled &&
+ onClick={() =>
{
handleMenuClose();
loggedIn ? roomClient.logout() : roomClient.login();
@@ -697,33 +697,6 @@ const TopBar = (props) =>
/>
- { lobbyPeers.length > 0 &&
-
- {
- handleMenuClose();
- setLockDialogOpen(!room.lockDialogOpen);
- }}
- >
-
-
-
-
-
-
-
- }
- :
+ :
{
title = title ? title : href;
text = text ? text : href;
-
+
return `${ text } `;
};
diff --git a/app/src/components/MeetingDrawer/Chat/MessageList.js b/app/src/components/MeetingDrawer/Chat/MessageList.js
index da6891b..d1c86d2 100644
--- a/app/src/components/MeetingDrawer/Chat/MessageList.js
+++ b/app/src/components/MeetingDrawer/Chat/MessageList.js
@@ -60,7 +60,7 @@ class MessageList extends React.Component
myPicture,
classes
} = this.props;
-
+
return (
{ this.node = node; }}>
{
diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js
index b11f631..d1d13e0 100644
--- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js
+++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js
@@ -279,7 +279,7 @@ const ListPeer = (props) =>
}
- { isModerator && webcamConsumer &&
+ { isModerator && webcamConsumer &&
(availableSpeakerHeight - PADDING_V))
{
speakerHeight = (availableSpeakerHeight - PADDING_V);
@@ -167,7 +167,7 @@ class Filmstrip extends React.PureComponent
let filmStripHeight = availableFilmstripHeight - FILMSTRING_PADDING_V;
let filmStripWidth = filmStripHeight * RATIO;
-
+
if (
(filmStripWidth * this.props.boxes) >
(availableWidth - FILMSTRING_PADDING_H)
@@ -254,7 +254,7 @@ class Filmstrip extends React.PureComponent
};
return (
-
const permitted = roles.some((role) =>
roomPermissions[permission].includes(role)
);
-
+
if (permitted)
return true;
@@ -265,7 +265,7 @@ export const makePermissionSelector = (permission) =>
).length === 0
)
return true;
-
+
return false;
}
);
diff --git a/app/src/components/Settings/AdvancedSettings.js b/app/src/components/Settings/AdvancedSettings.js
index 520dc0f..b8c1c19 100644
--- a/app/src/components/Settings/AdvancedSettings.js
+++ b/app/src/components/Settings/AdvancedSettings.js
@@ -4,13 +4,14 @@ import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../RoomContext';
import * as settingsActions from '../../actions/settingsActions';
import PropTypes from 'prop-types';
+import classnames from 'classnames';
import { useIntl, FormattedMessage } from 'react-intl';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select';
-import Checkbox from '@material-ui/core/Checkbox';
+import Switch from '@material-ui/core/Switch';
const styles = (theme) =>
({
@@ -21,6 +22,13 @@ const styles = (theme) =>
formControl :
{
display : 'flex'
+ },
+ switchLabel : {
+ justifyContent : 'space-between',
+ flex : 'auto',
+ display : 'flex',
+ padding : theme.spacing(1),
+ marginRight : 0
}
});
@@ -37,16 +45,18 @@ const AdvancedSettings = ({
return (
}
+ className={classnames(classes.setting, classes.switchLabel)}
+ control={ }
+ labelPlacement='start'
label={intl.formatMessage({
id : 'settings.advancedMode',
defaultMessage : 'Advanced mode'
})}
/>
}
+ className={classnames(classes.setting, classes.switchLabel)}
+ control={ }
+ labelPlacement='start'
label={intl.formatMessage({
id : 'settings.notificationSounds',
defaultMessage : 'Notification sounds'
diff --git a/app/src/components/Settings/AppearenceSettings.js b/app/src/components/Settings/AppearenceSettings.js
index 46cc898..f2463a2 100644
--- a/app/src/components/Settings/AppearenceSettings.js
+++ b/app/src/components/Settings/AppearenceSettings.js
@@ -4,6 +4,7 @@ import * as appPropTypes from '../appPropTypes';
import { withStyles } from '@material-ui/core/styles';
import * as roomActions from '../../actions/roomActions';
import * as settingsActions from '../../actions/settingsActions';
+import classnames from 'classnames';
import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl';
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 FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select';
-import Checkbox from '@material-ui/core/Checkbox';
+import Switch from '@material-ui/core/Switch';
const styles = (theme) =>
({
@@ -22,6 +23,13 @@ const styles = (theme) =>
formControl :
{
display : 'flex'
+ },
+ switchLabel : {
+ justifyContent : 'space-between',
+ flex : 'auto',
+ display : 'flex',
+ padding : theme.spacing(1),
+ marginRight : 0
}
});
@@ -90,24 +98,28 @@ const AppearenceSettings = ({
}
+ className={classnames(classes.setting, classes.switchLabel)}
+ control={
+ }
+ labelPlacement='start'
label={intl.formatMessage({
id : 'settings.permanentTopBar',
defaultMessage : 'Permanent top bar'
})}
/>
}
+ className={classnames(classes.setting, classes.switchLabel)}
+ control={ }
+ labelPlacement='start'
label={intl.formatMessage({
id : 'settings.hiddenControls',
defaultMessage : 'Hidden media controls'
})}
/>
}
+ className={classnames(classes.setting, classes.switchLabel)}
+ control={ }
+ labelPlacement='start'
label={intl.formatMessage({
id : 'settings.buttonControlBar',
defaultMessage : 'Separate media controls'
@@ -115,8 +127,9 @@ const AppearenceSettings = ({
/>
{ !isMobile &&
}
+ className={classnames(classes.setting, classes.switchLabel)}
+ control={ }
+ labelPlacement='start'
label={intl.formatMessage({
id : 'settings.drawerOverlayed',
defaultMessage : 'Side drawer over content'
@@ -124,8 +137,9 @@ const AppearenceSettings = ({
/>
}
}
+ className={classnames(classes.setting, classes.switchLabel)}
+ control={ }
+ labelPlacement='start'
label={intl.formatMessage({
id : 'settings.showNotifications',
defaultMessage : 'Show notifications'
diff --git a/app/src/components/Settings/MediaSettings.js b/app/src/components/Settings/MediaSettings.js
index d326b91..59b1bd3 100644
--- a/app/src/components/Settings/MediaSettings.js
+++ b/app/src/components/Settings/MediaSettings.js
@@ -12,13 +12,19 @@ import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select';
-import Checkbox from '@material-ui/core/Checkbox';
import Slider from '@material-ui/core/Slider';
import Typography from '@material-ui/core/Typography';
+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(
{
- root :
+ root :
{
color : '#3880ff',
height : 2,
@@ -48,10 +54,27 @@ const styles = (theme) => ({
{
padding : theme.spacing(2)
},
- margin :
+ margin :
{
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 :
{
display : 'flex'
@@ -71,7 +94,7 @@ const MediaSettings = ({
}) =>
{
const intl = useIntl();
-
+
const resolutions = [ {
value : 'low',
label : intl.formatMessage({
@@ -121,7 +144,7 @@ const MediaSettings = ({
audioDevices = Object.values(me.audioDevices);
else
audioDevices = [];
-
+
let audioOutputDevices;
if (me.audioOutputDevices)
@@ -129,6 +152,13 @@ const MediaSettings = ({
else
audioOutputDevices = [];
+ const [ open, setOpen ] = React.useState(true);
+
+ const advancedAudioSettings = () =>
+ {
+ setOpen(!open);
+ };
+
return (
}
-
+ />
+ {open ? : }
+
+
+
+
+
+ {
+ setEchoCancellation(event.target.checked);
+ roomClient.changeAudioDevice(settings.selectedAudioDevice);
+ }}
+ />}
+ labelPlacement='start'
+ label={intl.formatMessage({
+ id : 'settings.echoCancellation',
+ defaultMessage : 'Echo cancellation'
+ })}
+ />
+
+
+
+ {
+ setAutoGainControl(event.target.checked);
+ roomClient.changeAudioDevice(settings.selectedAudioDevice);
+ }}
+ />}
+ labelPlacement='start'
+ label={intl.formatMessage({
+ id : 'settings.autoGainControl',
+ defaultMessage : 'Auto gain control'
+ })}
+ />
+
+
+
+ {
+ setNoiseSuppression(event.target.checked);
+ roomClient.changeAudioDevice(settings.selectedAudioDevice);
+ }}
+ />}
+ labelPlacement='start'
+ label={intl.formatMessage({
+ id : 'settings.noiseSuppression',
+ defaultMessage : 'Noise suppression'
+ })}
+ />
+
+
+
+ {
+ setVoiceActivatedUnmute(event.target.checked);
+ }}
+ />}
+ labelPlacement='start'
+ label={intl.formatMessage({
+ id : 'settings.voiceActivatedUnmute',
+ defaultMessage : 'Voice activated unmute'
+ })}
+ />
+
+
+
+
+ {
+ intl.formatMessage({
+ id : 'settings.noiseThreshold',
+ defaultMessage : 'Noise threshold'
+ })
+ }:
+
+
+ {
+ roomClient._setNoiseThreshold(value);
+ }}
+ marks={[ { value: volume, label: `${volume} dB` } ]}
+ />
+
+
+
+
);
};
@@ -399,8 +459,8 @@ const mapStateToProps = (state) =>
const mapDispatchToProps = {
setEchoCancellation : settingsActions.setEchoCancellation,
- setAutoGainControl : settingsActions.toggleAutoGainControl,
- setNoiseSuppression : settingsActions.toggleNoiseSuppression,
+ setAutoGainControl : settingsActions.setAutoGainControl,
+ setNoiseSuppression : settingsActions.setNoiseSuppression,
setVoiceActivatedUnmute : settingsActions.setVoiceActivatedUnmute
};
diff --git a/app/src/components/UnsupportedBrowser.js b/app/src/components/UnsupportedBrowser.js
new file mode 100644
index 0000000..480e792
--- /dev/null
+++ b/app/src/components/UnsupportedBrowser.js
@@ -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 (
+
+
+ {!webrtcUnavailable &&
+
+ }
+ {webrtcUnavailable &&
+
+ }
+
+
+
+
+
+
+
+
+ {supportedBrowsers.map((browser, index) =>
+ {
+ const supportedBrowser = `${browser.vendor} ${browser.name}`;
+ const supportedVersion = `${browser.version}+`;
+
+ return (
+
+
+
+
+
+
+
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+UnsupportedBrowser.propTypes =
+{
+ webrtcUnavailable : PropTypes.bool.isRequired,
+ platform : PropTypes.string.isRequired,
+ classes : PropTypes.object.isRequired
+};
+
+export default withStyles(styles)(UnsupportedBrowser);
diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js
index 3237a9d..39913ab 100644
--- a/app/src/components/VideoContainers/VideoView.js
+++ b/app/src/components/VideoContainers/VideoView.js
@@ -223,50 +223,50 @@ class VideoView extends React.PureComponent
if (videoScore || audioScore)
{
const score = videoScore ? videoScore : audioScore;
-
+
switch (isMe ? score.score : score.producerScore)
{
case 0:
case 1:
{
quality = ;
-
+
break;
}
-
+
case 2:
case 3:
{
quality = ;
-
+
break;
}
-
+
case 4:
case 5:
case 6:
{
quality = ;
-
+
break;
}
-
+
case 7:
case 8:
case 9:
{
quality = ;
-
+
break;
}
-
+
case 10:
{
quality = null;
-
+
break;
}
-
+
default:
{
break;
@@ -351,7 +351,7 @@ class VideoView extends React.PureComponent
{ showQuality &&
- {
+ {
quality
}
diff --git a/app/src/index.js b/app/src/index.js
index c5f333d..9abbed6 100644
--- a/app/src/index.js
+++ b/app/src/index.js
@@ -12,6 +12,7 @@ import RoomClient from './RoomClient';
import RoomContext from './RoomContext';
import deviceInfo from './deviceInfo';
import * as meActions from './actions/meActions';
+import UnsupportedBrowser from './components/UnsupportedBrowser';
import ChooseRoom from './components/ChooseRoom';
import LoadingView from './components/LoadingView';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
@@ -20,7 +21,7 @@ import { persistor, store } from './store';
import { SnackbarProvider } from 'notistack';
import * as serviceWorker from './serviceWorker';
import { ReactLazyPreload } from './components/ReactLazyPreload';
-
+import { detectDevice } from 'mediasoup-client';
// import messagesEnglish from './translations/en';
import messagesNorwegian from './translations/nb';
import messagesGerman from './translations/de';
@@ -70,6 +71,18 @@ const messages =
'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();
let locale = browserLanguage.split(/[-_]/)[0]; // language without region code
@@ -134,10 +147,62 @@ function run()
if (!basePath)
basePath = '/';
-
+
// Get current device.
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(
+
+
+
+
+ ,
+ document.getElementById('multiparty-meeting')
+ );
+
+ return;
+ }
+
store.dispatch(
meActions.setMe({
peerId,
diff --git a/app/src/localAudioAnalyzer.js b/app/src/localAudioAnalyzer.js
new file mode 100644
index 0000000..4f387c6
--- /dev/null
+++ b/app/src/localAudioAnalyzer.js
@@ -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;
+ }
+}
diff --git a/app/src/reducers/lobbyPeers.js b/app/src/reducers/lobbyPeers.js
index 4cf9cc1..2e18ce0 100644
--- a/app/src/reducers/lobbyPeers.js
+++ b/app/src/reducers/lobbyPeers.js
@@ -1,6 +1,6 @@
const lobbyPeer = (state = {}, action) =>
{
- switch (action.type)
+ switch (action.type)
{
case 'ADD_LOBBY_PEER':
return { id: action.payload.peerId };
@@ -42,7 +42,7 @@ const lobbyPeers = (state = {}, action) =>
{
const oldLobbyPeer = state[action.payload.peerId];
- if (!oldLobbyPeer)
+ if (!oldLobbyPeer)
{
// Tried to update non-existent lobbyPeer. Has probably been promoted, or left.
return state;
diff --git a/app/src/reducers/peerVolumes.js b/app/src/reducers/peerVolumes.js
index 826a038..3ecfe8c 100644
--- a/app/src/reducers/peerVolumes.js
+++ b/app/src/reducers/peerVolumes.js
@@ -10,13 +10,13 @@ const peerVolumes = (state = initialState, action) =>
peerId
} = action.payload;
- return { ...state, [peerId]: 0 };
+ return { ...state, [peerId]: -100 };
}
case 'ADD_PEER':
{
const { peer } = action.payload;
- return { ...state, [peer.id]: 0 };
+ return { ...state, [peer.id]: -100 };
}
case 'REMOVE_PEER':
diff --git a/app/src/reducers/peers.js b/app/src/reducers/peers.js
index 8e6a7f7..b45d469 100644
--- a/app/src/reducers/peers.js
+++ b/app/src/reducers/peers.js
@@ -2,7 +2,7 @@ const initialState = {};
const peer = (state = initialState, action) =>
{
- switch (action.type)
+ switch (action.type)
{
case 'ADD_PEER':
return action.payload.peer;
@@ -21,7 +21,7 @@ const peer = (state = initialState, action) =>
case 'SET_PEER_KICK_IN_PROGRESS':
return { ...state, peerKickInProgress: action.payload.flag };
-
+
case 'SET_PEER_RAISED_HAND':
return {
...state,
@@ -34,7 +34,7 @@ const peer = (state = initialState, action) =>
...state,
raisedHandInProgress : action.payload.flag
};
-
+
case 'ADD_CONSUMER':
{
const consumers = [ ...state.consumers, action.payload.consumer.id ];
@@ -127,14 +127,14 @@ const peers = (state = initialState, action) =>
{
const oldPeer = state[action.payload.peerId];
- if (!oldPeer)
+ if (!oldPeer)
{
throw new Error('no Peer found');
}
return { ...state, [oldPeer.id]: peer(oldPeer, action) };
}
-
+
case 'SET_PEER_KICK_IN_PROGRESS':
case 'REMOVE_CONSUMER':
{
diff --git a/app/src/reducers/room.js b/app/src/reducers/room.js
index 193e8e0..d03194b 100644
--- a/app/src/reducers/room.js
+++ b/app/src/reducers/room.js
@@ -10,7 +10,7 @@ const initialState =
// access code to the room if locked and joinByAccessCode == true
accessCode : '',
// if true: accessCode is a possibility to open the room
- joinByAccessCode : true,
+ joinByAccessCode : true,
activeSpeakerId : null,
torrentSupport : false,
showSettings : false,
@@ -107,7 +107,7 @@ const room = (state = initialState, action) =>
return { ...state, lockDialogOpen };
}
-
+
case 'SET_SETTINGS_OPEN':
{
const { settingsOpen } = action.payload;
@@ -135,7 +135,7 @@ const room = (state = initialState, action) =>
return { ...state, aboutOpen };
}
-
+
case 'SET_SETTINGS_TAB':
{
const { tab } = action.payload;
diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js
index 750df24..fcd80d4 100644
--- a/app/src/reducers/settings.js
+++ b/app/src/reducers/settings.js
@@ -22,7 +22,6 @@ const initialState =
notificationSounds : true,
buttonControlBar : window.config.buttonControlBar || false,
drawerOverlayed : window.config.drawerOverlayed || true,
- autoMuteThreshold : window.config.autoMuteThreshold || 4,
...window.config.defaultAudio
};
@@ -44,7 +43,7 @@ const settings = (state = initialState, action) =>
{
return { ...state, selectedAudioOutputDevice: action.payload.deviceId };
}
-
+
case 'SET_DISPLAY_NAME':
{
const { displayName } = action.payload;
@@ -122,27 +121,6 @@ const settings = (state = initialState, action) =>
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':
{
const { sampleSize } = action.payload;
diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json
index c5f79e4..10a8d93 100644
--- a/app/src/translations/cn.json
+++ b/app/src/translations/cn.json
@@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "设置",
"settings.camera": "视频设备",
@@ -144,6 +145,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "无法保存文件",
"filesharing.startingFileShare": "正在尝试共享文件",
@@ -189,5 +192,9 @@
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
\ No newline at end of file
diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json
index c1ea569..8530037 100644
--- a/app/src/translations/cs.json
+++ b/app/src/translations/cs.json
@@ -118,6 +118,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Nastavení",
"settings.camera": "Kamera",
@@ -143,6 +144,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Není možné uložit soubor",
"filesharing.startingFileShare": "Pokouším se sdílet soubor",
@@ -188,5 +191,9 @@
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
\ No newline at end of file
diff --git a/app/src/translations/de.json b/app/src/translations/de.json
index 9339d4f..d186f22 100644
--- a/app/src/translations/de.json
+++ b/app/src/translations/de.json
@@ -119,6 +119,7 @@
"label.addVideo": "Video hinzufügen",
"label.promoteAllPeers": "Alle Teilnehmer reinlassen",
"label.moreActions": "Weitere Aktionen",
+ "label.version": null,
"settings.settings": "Einstellungen",
"settings.camera": "Kamera",
@@ -144,6 +145,8 @@
"settings.autoGainControl": "Automatische Pegelregelung (Audioeingang)",
"settings.noiseSuppression": "Rauschunterdrückung",
"settings.drawerOverlayed": "Seitenpanel verdeckt Hauptinhalt",
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Fehler beim Speichern der Datei",
"filesharing.startingFileShare": "Starte Teilen der Datei",
@@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator hat geteilte Dateiliste gelöscht",
"moderator.muteAudio": "Moderator hat dich stummgeschaltet",
"moderator.muteVideo": "Moderator hat dein Video gestoppt",
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json
index edeb619..2b3aa22 100644
--- a/app/src/translations/dk.json
+++ b/app/src/translations/dk.json
@@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Indstillinger",
"settings.camera": "Kamera",
@@ -144,6 +145,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Kan ikke gemme fil",
"filesharing.startingFileShare": "Forsøger at dele filen",
@@ -189,5 +192,9 @@
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
diff --git a/app/src/translations/el.json b/app/src/translations/el.json
index 6807831..9c2c9c4 100644
--- a/app/src/translations/el.json
+++ b/app/src/translations/el.json
@@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Ρυθμίσεις",
"settings.camera": "Κάμερα",
@@ -144,6 +145,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου",
"filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου",
@@ -189,5 +192,9 @@
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
\ No newline at end of file
diff --git a/app/src/translations/en.json b/app/src/translations/en.json
index 30c21d7..cb82bde 100644
--- a/app/src/translations/en.json
+++ b/app/src/translations/en.json
@@ -119,6 +119,7 @@
"label.addVideo": "Add video",
"label.promoteAllPeers": "Promote all",
"label.moreActions": "More actions",
+ "label.version": "Version",
"settings.settings": "Settings",
"settings.camera": "Camera",
@@ -144,6 +145,8 @@
"settings.autoGainControl": "Auto gain control",
"settings.noiseSuppression": "Noise suppression",
"settings.drawerOverlayed": "Side drawer over content",
+ "settings.voiceActivatedUnmute": "Voice activated unmute",
+ "settings.noiseThreshold": "Noise threshold",
"filesharing.saveFileError": "Unable to save file",
"filesharing.startingFileShare": "Attempting to share file",
@@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator cleared the files",
"moderator.muteAudio": "Moderator muted your audio",
"moderator.muteVideo": "Moderator muted your video",
- "moderator.muteScreenSharing": "Moderator muted your screen sharing"
+ "moderator.stopScreenSharing": "Moderator stopped your screen sharing",
+
+ "unsupportedBrowser.titleUsnsupportedBrowser": "Detected unsupported browser!",
+ "unsupportedBrowser.titlewebrtcUnavailable": "Required functionality not available in your browser!",
+ "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:"
}
\ No newline at end of file
diff --git a/app/src/translations/es.json b/app/src/translations/es.json
index 4e0c12a..088981d 100644
--- a/app/src/translations/es.json
+++ b/app/src/translations/es.json
@@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Ajustes",
"settings.camera": "Cámara",
@@ -144,6 +145,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "No ha sido posible guardar el fichero",
"filesharing.startingFileShare": "Intentando compartir el fichero",
@@ -189,5 +192,9 @@
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json
index 0f2ac0b..c636c51 100644
--- a/app/src/translations/fr.json
+++ b/app/src/translations/fr.json
@@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Paramètres",
"settings.camera": "Caméra",
@@ -144,6 +145,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Impossible d'enregistrer le fichier",
"filesharing.startingFileShare": "Début du transfert de fichier",
@@ -188,5 +191,9 @@
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json
index 59873d4..cc95c02 100644
--- a/app/src/translations/hr.json
+++ b/app/src/translations/hr.json
@@ -119,6 +119,7 @@
"label.addVideo": "Dodaj video",
"label.promoteAllPeers": "Promoviraj sve",
"label.moreActions": "Više akcija",
+ "label.version": null,
"settings.settings": "Postavke",
"settings.camera": "Kamera",
@@ -144,6 +145,8 @@
"settings.autoGainControl": "Automatsko upravljanje jačinom zvuka",
"settings.noiseSuppression": "Poništavanje šuma",
"settings.drawerOverlayed": "Bočni izbornik iznad sadržaja",
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Nije moguće spremiti datoteku",
"filesharing.startingFileShare": "Pokušaj dijeljenja datoteke",
@@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator je izbrisao datoteke",
"moderator.muteAudio": "Moderator je utišao tvoj zvuk",
"moderator.muteVideo": "Moderator je zaustavio tvoj video",
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json
index 1a2832b..4936b18 100644
--- a/app/src/translations/hu.json
+++ b/app/src/translations/hu.json
@@ -66,7 +66,7 @@
"room.browsePeersSpotlight": "Résztvevők böngészése",
"room.stopAllScreenSharing": "Összes képernyőmegosztás leállítása",
- "me.mutedPTT": "Némítva vagy, ha beszélnél nyomd le a szóköz billentyűt",
+ "me.mutedPTT": "Némítva vagy, ha beszélnél nyomd le a SZÓKÖZ billentyűt",
"roles.gotRole": "{role} szerepet kaptál",
"roles.lostRole": "Elvesztetted a {role} szerepet",
@@ -86,9 +86,9 @@
"tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása",
"tooltip.raisedHand": "Jelentkezés",
"tooltip.muteScreenSharing": "Képernyőmegosztás szüneteltetése",
- "tooltip.muteParticipantAudioModerator": "Résztvevő hangjának általános némítása",
- "tooltip.muteParticipantVideoModerator": "Résztvevő videójának általános némítása",
- "tooltip.muteScreenSharingModerator": "Résztvevő képernyőmegosztásá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 némítása mindenkinél",
+ "tooltip.muteScreenSharingModerator": "Résztvevő képernyőmegosztásának leállítása mindenkinél",
"label.roomName": "Konferencia",
"label.chooseRoomButton": "Tovább",
@@ -119,6 +119,7 @@
"label.addVideo": "Videó hozzáadása",
"label.promoteAllPeers": "Mindenkit beengedek",
"label.moreActions": "További műveletek",
+ "label.version": "Verzió",
"settings.settings": "Beállítások",
"settings.camera": "Kamera",
@@ -144,6 +145,8 @@
"settings.autoGainControl": "Automatikus hangerő",
"settings.noiseSuppression": "Zajelnyomás",
"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.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.muteAudio": "A moderátor elnémította a hangod",
"moderator.muteVideo": "A moderátor elnémította a videód",
- "moderator.muteScreenSharing": "A moderátor leállította képernyőmegosztásod"
+ "moderator.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:"
}
diff --git a/app/src/translations/it.json b/app/src/translations/it.json
index 02b53e7..63efd8c 100644
--- a/app/src/translations/it.json
+++ b/app/src/translations/it.json
@@ -119,6 +119,7 @@
"label.addVideo": "Aggiungi video",
"label.promoteAllPeers": "Promuovi tutti",
"label.moreActions": "Altre azioni",
+ "label.version": null,
"settings.settings": "Impostazioni",
"settings.camera": "Videocamera",
@@ -144,6 +145,8 @@
"settings.autoGainControl": "Controllo guadagno automatico",
"settings.noiseSuppression": "Riduzione del rumore",
"settings.drawerOverlayed": "Barra laterale sovrapposta",
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Impossibile salvare file",
"filesharing.startingFileShare": "Tentativo di condivisione file",
@@ -189,5 +192,9 @@
"moderator.clearFiles": "Il moderatore ha pulito i file",
"moderator.muteAudio": "Il moderatore ha mutato il tuo audio",
"moderator.muteVideo": "Il moderatore ha fermato il tuo video",
- "moderator.muteScreenSharing": null
-}
\ No newline at end of file
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
+}
diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json
index 62ee61e..56cdfb3 100644
--- a/app/src/translations/lv.json
+++ b/app/src/translations/lv.json
@@ -116,6 +116,7 @@
"label.advanced": "Advancēts",
"label.addVideo": "Pievienot video",
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Iestatījumi",
"settings.camera": "Kamera",
@@ -138,6 +139,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Nav iespējams saglabāt failu",
"filesharing.startingFileShare": "Tiek mēģināts kopīgot failu",
@@ -183,5 +186,9 @@
"moderator.clearFiles": "Moderators notīrīja failus",
"moderator.muteAudio": "Moderators noklusināja jūsu mikrofonu",
"moderator.muteVideo": "Moderators atslēdza jūsu kameru",
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json
index 5bafeb6..9c2bf69 100644
--- a/app/src/translations/nb.json
+++ b/app/src/translations/nb.json
@@ -119,6 +119,7 @@
"label.addVideo": "Legg til video",
"label.promoteAllPeers": "Slipp inn alle",
"label.moreActions": "Flere handlinger",
+ "label.version": null,
"settings.settings": "Innstillinger",
"settings.camera": "Kamera",
@@ -144,6 +145,8 @@
"settings.autoGainControl": "Auto gain kontroll",
"settings.noiseSuppression": "Støy reduksjon",
"settings.drawerOverlayed": "Sidemeny over innhold",
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Klarte ikke å lagre fil",
"filesharing.startingFileShare": "Starter fildeling",
@@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator fjernet filer",
"moderator.muteAudio": "Moderator mutet lyden din",
"moderator.muteVideo": "Moderator mutet videoen din",
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
\ No newline at end of file
diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json
index 7612f99..173b560 100644
--- a/app/src/translations/pl.json
+++ b/app/src/translations/pl.json
@@ -119,6 +119,7 @@
"label.addVideo": "Dodaj wideo",
"label.promoteAllPeers": "Wpuść wszystkich",
"label.moreActions": "Więcej akcji",
+ "label.version": null,
"settings.settings": "Ustawienia",
"settings.camera": "Kamera",
@@ -144,6 +145,8 @@
"settings.autoGainControl": "Auto korekta wzmocnienia",
"settings.noiseSuppression": "Wyciszenie szumów",
"settings.drawerOverlayed": "Szuflada nad zawartością",
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Nie można zapisać pliku",
"filesharing.startingFileShare": "Próba udostępnienia pliku",
@@ -189,5 +192,9 @@
"moderator.clearFiles": "Moderator wyczyścił pliki",
"moderator.muteAudio": "Moderator wyciszył audio",
"moderator.muteVideo": "Moderator wyciszył twoje video",
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
\ No newline at end of file
diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json
index a2a3310..5646823 100644
--- a/app/src/translations/pt.json
+++ b/app/src/translations/pt.json
@@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Definições",
"settings.camera": "Camera",
@@ -144,6 +145,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Impossível de gravar o ficheiro",
"filesharing.startingFileShare": "Tentando partilha de ficheiro",
@@ -189,5 +192,9 @@
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json
index 7161bd4..a1b5fba 100644
--- a/app/src/translations/ro.json
+++ b/app/src/translations/ro.json
@@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Setări",
"settings.camera": "Cameră video",
@@ -144,6 +145,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat",
"filesharing.startingFileShare": "Partajarea fișierului",
@@ -189,5 +192,9 @@
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json
index 1876bab..71c2e15 100644
--- a/app/src/translations/tr.json
+++ b/app/src/translations/tr.json
@@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Ayarlar",
"settings.camera": "Kamera",
@@ -141,6 +142,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Dosya kaydedilemiyor",
"filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor",
@@ -181,5 +184,14 @@
"devices.cameraDisconnected": "Kamera bağlı değil",
"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
}
diff --git a/app/src/translations/tw.json b/app/src/translations/tw.json
index 99c32d7..38e7316 100644
--- a/app/src/translations/tw.json
+++ b/app/src/translations/tw.json
@@ -118,6 +118,7 @@
"label.addVideo": "新增視訊",
"label.promoteAllPeers": "提升全部",
"label.moreActions": "更多",
+ "label.version": null,
"settings.settings": "設置",
"settings.camera": "視訊來源",
@@ -143,6 +144,8 @@
"settings.autoGainControl": "自動增益控制",
"settings.noiseSuppression": "噪音消除",
"settings.drawerOverlayed": "側邊欄覆蓋畫面",
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "無法保存文件",
"filesharing.startingFileShare": "開始分享文件",
@@ -188,5 +191,9 @@
"moderator.clearFiles": "管理員清除了所有檔案",
"moderator.muteAudio": "您已被管理員靜音",
"moderator.muteVideo": "您的視訊已被管理員關閉",
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
\ No newline at end of file
diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json
index 2ee6774..9c507f2 100644
--- a/app/src/translations/uk.json
+++ b/app/src/translations/uk.json
@@ -119,6 +119,7 @@
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
+ "label.version": null,
"settings.settings": "Налаштування",
"settings.camera": "Камера",
@@ -144,6 +145,8 @@
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
+ "settings.voiceActivatedUnmute": null,
+ "settings.noiseThreshold": null,
"filesharing.saveFileError": "Неможливо зберегти файл",
"filesharing.startingFileShare": "Спроба поділитися файлом",
@@ -189,5 +192,9 @@
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
- "moderator.muteScreenSharing": null
+ "moderator.stopScreenSharing": null,
+
+ "unsupportedBrowser.titleUnsupportedBrowser": null,
+ "unsupportedBrowser.titlewebrtcUnavailable": null,
+ "unsupportedBrowser.bodyText": null
}
\ No newline at end of file
diff --git a/app/src/utils.js b/app/src/utils.js
index 1fba8b3..21339fe 100644
--- a/app/src/utils.js
+++ b/app/src/utils.js
@@ -3,17 +3,35 @@
* after the given amount of milliseconds has passed since
* the last time the callback function was called.
*/
-export const idle = (callback, delay) =>
+export const idle = (callback, delay) =>
{
let handle;
- return () =>
+ return () =>
{
- if (handle)
+ if (handle)
{
clearTimeout(handle);
}
handle = setTimeout(callback, delay);
};
-};
\ No newline at end of file
+};
+
+/**
+ * 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;
+ }
+}
\ No newline at end of file
diff --git a/server/.eslintrc.json b/server/.eslintrc.json
index d98852a..b4a49d1 100644
--- a/server/.eslintrc.json
+++ b/server/.eslintrc.json
@@ -106,6 +106,12 @@
"no-invalid-regexp": 2,
"no-invalid-this": 2,
"no-irregular-whitespace": 2,
+ "no-trailing-spaces": [
+ "error",
+ {
+ "ignoreComments": true
+ }
+ ],
"no-lonely-if": 2,
"no-mixed-operators": 2,
"no-mixed-spaces-and-tabs": 2,
diff --git a/server/config/config.example.js b/server/config/config.example.js
index e08f910..6ca4a27 100644
--- a/server/config/config.example.js
+++ b/server/config/config.example.js
@@ -276,6 +276,10 @@ module.exports =
// maxUsersPerRoom : 20,
// Room size before spreading to new router
routerScaleSize : 40,
+ // Socket timout value
+ requestTimeout : 20000,
+ // Socket retries when timeout
+ requestRetries : 3,
// Mediasoup settings
mediasoup :
{
diff --git a/server/lib/Peer.js b/server/lib/Peer.js
index 8e11ca2..5752d3e 100644
--- a/server/lib/Peer.js
+++ b/server/lib/Peer.js
@@ -83,9 +83,9 @@ class Peer extends EventEmitter
{
if (this.closed)
return;
-
+
logger.debug('"disconnect" event [id:%s]', this.id);
-
+
this.close();
});
}
@@ -208,7 +208,7 @@ class Peer extends EventEmitter
const oldDisplayName = this._displayName;
this._displayName = displayName;
-
+
this.emit('displayNameChanged', { oldDisplayName });
}
}
@@ -225,7 +225,7 @@ class Peer extends EventEmitter
const oldPicture = this._picture;
this._picture = picture;
-
+
this.emit('pictureChanged', { oldPicture });
}
}
diff --git a/server/lib/Room.js b/server/lib/Room.js
index f110327..df46f0e 100644
--- a/server/lib/Room.js
+++ b/server/lib/Room.js
@@ -3,6 +3,7 @@ const AwaitQueue = require('awaitqueue');
const axios = require('axios');
const Logger = require('./Logger');
const Lobby = require('./Lobby');
+const { SocketTimeoutError } = require('./errors');
const { v4: uuidv4 } = require('uuid');
const jwt = require('jsonwebtoken');
const userRoles = require('../userRoles');
@@ -492,7 +493,7 @@ class Room extends EventEmitter
peer.socket.handshake.session.save();
let turnServers;
-
+
if ('turnAPIURI' in config)
{
try
@@ -506,7 +507,7 @@ class Room extends EventEmitter
'ip' : peer.socket.request.connection.remoteAddress
}
});
-
+
turnServers = [ {
urls : data.uris,
username : data.username,
@@ -517,7 +518,7 @@ class Room extends EventEmitter
{
if ('backupTurnServers' in config)
turnServers = config.backupTurnServers;
-
+
logger.error('_peerJoining() | error on REST turn [error:"%o"]', error);
}
}
@@ -525,7 +526,7 @@ class Room extends EventEmitter
{
turnServers = config.backupTurnServers;
}
-
+
this._notification(peer.socket, 'roomReady', { turnServers });
}
})
@@ -778,7 +779,7 @@ class Room extends EventEmitter
// initiate mediasoup Transports and be ready when he later joins.
const { forceTcp, producing, consuming } = request.data;
-
+
const webRtcTransportOptions =
{
...config.mediasoup.webRtcTransport,
@@ -1186,7 +1187,7 @@ class Room extends EventEmitter
throw new Error('peer not authorized');
const { chatMessage } = request.data;
-
+
this._chatHistory.push(chatMessage);
// Spread to others
@@ -1205,7 +1206,7 @@ class Room extends EventEmitter
{
if (!this._hasPermission(peer, MODERATE_CHAT))
throw new Error('peer not authorized');
-
+
this._chatHistory = [];
// Spread to others
@@ -1258,7 +1259,7 @@ class Room extends EventEmitter
case 'setAccessCode':
{
const { accessCode } = request.data;
-
+
this._accessCode = accessCode;
// Spread to others
@@ -1278,7 +1279,7 @@ class Room extends EventEmitter
case 'setJoinByAccessCode':
{
const { joinByAccessCode } = request.data;
-
+
this._joinByAccessCode = joinByAccessCode;
// Spread to others
@@ -1327,7 +1328,7 @@ class Room extends EventEmitter
throw new Error('peer not authorized');
const { magnetUri } = request.data;
-
+
this._fileHistory.push({ peerId: peer.id, magnetUri: magnetUri });
// Spread to others
@@ -1346,7 +1347,7 @@ class Room extends EventEmitter
{
if (!this._hasPermission(peer, MODERATE_FILES))
throw new Error('peer not authorized');
-
+
this._fileHistory = [];
// Spread to others
@@ -1759,9 +1760,9 @@ class Room extends EventEmitter
if (called)
return;
called = true;
- callback(new Error('Request timeout.'));
+ callback(new SocketTimeoutError('Request timed out'));
},
- 10000
+ config.requestTimeout || 20000
);
return (...args) =>
@@ -1775,7 +1776,7 @@ class Room extends EventEmitter
};
}
- _request(socket, method, data = {})
+ _sendRequest(socket, method, data = {})
{
return new Promise((resolve, reject) =>
{
@@ -1797,6 +1798,33 @@ class Room extends EventEmitter
});
}
+ async _request(socket, method, data)
+ {
+ logger.debug('_request() [method:"%s", data:"%o"]', method, data);
+
+ const {
+ requestRetries = 3
+ } = config;
+
+ for (let tries = 0; tries < requestRetries; tries++)
+ {
+ try
+ {
+ return await this._sendRequest(socket, method, data);
+ }
+ catch (error)
+ {
+ if (
+ error instanceof SocketTimeoutError &&
+ tries < requestRetries
+ )
+ logger.warn('_request() | timeout, retrying [attempt:"%s"]', tries);
+ else
+ throw error;
+ }
+ }
+ }
+
_notification(socket, method, data = {}, broadcast = false, includeSender = false)
{
if (broadcast)
@@ -1879,7 +1907,7 @@ class Room extends EventEmitter
for (const routerId of this._mediasoupRouters.keys())
{
- const routerLoad =
+ const routerLoad =
Object.values(this._peers).filter((peer) => peer.routerId === routerId).length;
if (routerLoad < load)
diff --git a/server/lib/errors.js b/server/lib/errors.js
new file mode 100644
index 0000000..379838d
--- /dev/null
+++ b/server/lib/errors.js
@@ -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
+};
\ No newline at end of file