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 + + + 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 && 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)} - > - - - - - - - }
- : + : { 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 (
@@ -173,7 +203,7 @@ const MediaSettings = ({