diff --git a/app/package.json b/app/package.json index ec92c5b..fae4e97 100644 --- a/app/package.json +++ b/app/package.json @@ -27,6 +27,7 @@ "react": "^16.10.2", "react-cookie-consent": "^2.5.0", "react-dom": "^16.10.2", + "react-flip-toolkit": "^7.0.9", "react-intl": "^3.4.0", "react-redux": "^7.1.1", "react-router-dom": "^5.1.2", diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index 3f0ce49..b591c16 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -42,6 +42,17 @@ var config = { tcp : true }, + defaultAudio : + { + sampleRate : 48000, + channelCount : 1, + volume : 1.0, + autoGainControl : true, + echoCancellation : true, + noiseSuppression : true, + sampleSize : 16 + }, + background : 'images/background.jpg', defaultLayout : 'democratic', // democratic, filmstrip lastN : 4, mobileLastN : 1, @@ -49,7 +60,6 @@ var config = maxLastN : 5, // If truthy, users can NOT change number of speakers visible lockLastN : false, - background : 'images/background.jpg', // Add file and uncomment for adding logo to appbar // logo : 'images/logo.svg', title : 'Multiparty meeting', diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 8a502be..df276ae 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -475,9 +475,9 @@ export default class RoomClient window.open(url, 'loginWindow'); } - logout() + logout(roomId = this._roomId) { - window.open('/auth/logout', 'logoutWindow'); + window.open(`/auth/logout?peerId=${this._peerId}&roomId=${roomId}`, 'logoutWindow'); } receiveLoginChildWindow(data) @@ -953,20 +953,65 @@ export default class RoomClient } } - async getAudioTrack() + disconnectLocalHark() { - await navigator.mediaDevices.getUserMedia( - { - audio : true, video : false - }); + logger.debug('disconnectLocalHark() | Stopping harkStream.'); + if (this._harkStream != null) + { + this._harkStream.getAudioTracks()[0].stop(); + this._harkStream = null; + } + + if (this._hark != null) + { + logger.debug('disconnectLocalHark() Stopping hark.'); + this._hark.stop(); + } } - async getVideoTrack() + connectLocalHark(track) { - await navigator.mediaDevices.getUserMedia( + logger.debug('connectLocalHark() | Track:%o', track); + this._harkStream = new MediaStream(); + + this._harkStream.addTrack(track.clone()); + this._harkStream.getAudioTracks()[0].enabled = true; + + if (!this._harkStream.getAudioTracks()[0]) + throw new Error('getMicStream():something went wrong with hark'); + + this._hark = hark(this._harkStream, { play: false }); + + // eslint-disable-next-line no-unused-vars + this._hark.on('volume_change', (dBs, threshold) => + { + // The exact formula to convert from dBs (-100..0) to linear (0..1) is: + // Math.pow(10, dBs / 20) + // However it does not produce a visually useful output, so let exagerate + // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to + // minimize component renderings. + let volume = Math.round(Math.pow(10, dBs / 85) * 10); + + if (volume === 1) + volume = 0; + + volume = Math.round(volume); + + if (this._micProducer && volume !== this._micProducer.volume) { - audio : false, video : true - }); + this._micProducer.volume = volume; + + store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume)); + } + }); + this._hark.on('speaking', function() + { + store.dispatch(meActions.setIsSpeaking(true)); + }); + this._hark.on('stopped_speaking', function() + { + store.dispatch(meActions.setIsSpeaking(false)); + }); } async changeAudioDevice(deviceId) @@ -977,7 +1022,7 @@ export default class RoomClient meActions.setAudioInProgress(true)); try - { + { const device = this._audioDevices[deviceId]; if (!device) @@ -987,29 +1032,30 @@ export default class RoomClient 'changeAudioDevice() | new selected webcam [device:%o]', device); - if (this._hark != null) - this._hark.stop(); - - if (this._harkStream != null) - { - logger.debug('Stopping hark.'); - this._harkStream.getAudioTracks()[0].stop(); - this._harkStream = null; - } + this.disconnectLocalHark(); if (this._micProducer && this._micProducer.track) this._micProducer.track.stop(); - logger.debug('changeAudioDevice() | calling getUserMedia()'); + logger.debug('changeAudioDevice() | calling getUserMedia() %o', store.getState().settings); const stream = await navigator.mediaDevices.getUserMedia( { audio : { - deviceId : { exact: device.deviceId } + deviceId : { ideal: device.deviceId }, + sampleRate : store.getState().settings.sampleRate, + channelCount : store.getState().settings.channelCount, + volume : store.getState().settings.volume, + autoGainControl : store.getState().settings.autoGainControl, + echoCancellation : store.getState().settings.echoCancellation, + noiseSuppression : store.getState().settings.noiseSuppression, + sampleSize : store.getState().settings.sampleSize } - }); + } + ); + logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints()); const track = stream.getAudioTracks()[0]; if (this._micProducer) @@ -1017,47 +1063,8 @@ export default class RoomClient if (this._micProducer) this._micProducer.volume = 0; + this.connectLocalHark(track); - this._harkStream = new MediaStream(); - - this._harkStream.addTrack(track.clone()); - this._harkStream.getAudioTracks()[0].enabled = true; - - if (!this._harkStream.getAudioTracks()[0]) - throw new Error('changeAudioDevice(): given stream has no audio track'); - - this._hark = hark(this._harkStream, { play: false }); - - // eslint-disable-next-line no-unused-vars - this._hark.on('volume_change', (dBs, threshold) => - { - // The exact formula to convert from dBs (-100..0) to linear (0..1) is: - // Math.pow(10, dBs / 20) - // However it does not produce a visually useful output, so let exaggerate - // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to - // minimize component renderings. - let volume = Math.round(Math.pow(10, dBs / 85) * 10); - - if (volume === 1) - volume = 0; - - volume = Math.round(volume); - - if (this._micProducer && volume !== this._micProducer.volume) - { - this._micProducer.volume = volume; - - store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume)); - } - }); - this._hark.on('speaking', function() - { - store.dispatch(meActions.setIsSpeaking(true)); - }); - this._hark.on('stopped_speaking', function() - { - store.dispatch(meActions.setIsSpeaking(false)); - }); if (this._micProducer && this._micProducer.id) store.dispatch( producerActions.setProducerTrack(this._micProducer.id, track)); @@ -1533,6 +1540,26 @@ export default class RoomClient } } + async lowerPeerHand(peerId) + { + logger.debug('lowerPeerHand() [peerId:"%s"]', peerId); + + store.dispatch( + peerActions.setPeerRaisedHandInProgress(peerId, true)); + + try + { + await this.sendRequest('moderator:lowerHand', { peerId }); + } + catch (error) + { + logger.error('lowerPeerHand() | [error:"%o"]', error); + } + + store.dispatch( + peerActions.setPeerRaisedHandInProgress(peerId, false)); + } + async setRaisedHand(raisedHand) { logger.debug('setRaisedHand: ', raisedHand); @@ -2534,6 +2561,13 @@ export default class RoomClient break; } + case 'moderator:lowerHand': + { + this.setRaisedHand(false); + + break; + } + case 'gotRole': { const { peerId, role } = notification.data; @@ -2811,7 +2845,9 @@ export default class RoomClient { text : intl.formatMessage({ id : 'roles.gotRole', - defaultMessage : `You got the role: ${role}` + defaultMessage : 'You got the role: {role}' + }, { + role }) })); } @@ -3233,11 +3269,20 @@ export default class RoomClient const stream = await navigator.mediaDevices.getUserMedia( { audio : { - deviceId : { ideal: deviceId } + deviceId : { ideal: device.deviceId }, + sampleRate : store.getState().settings.sampleRate, + channelCount : store.getState().settings.channelCount, + volume : store.getState().settings.volume, + autoGainControl : store.getState().settings.autoGainControl, + echoCancellation : store.getState().settings.echoCancellation, + noiseSuppression : store.getState().settings.noiseSuppression, + sampleSize : store.getState().settings.sampleSize } } ); + logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints()); + track = stream.getAudioTracks()[0]; this._micProducer = await this._sendTransport.produce( @@ -3291,51 +3336,8 @@ export default class RoomClient this._micProducer.volume = 0; - if (this._hark != null) - this._hark.stop(); + this.connectLocalHark(track); - if (this._harkStream != null) - this._harkStream.getAudioTracks()[0].stop(); - - this._harkStream = new MediaStream(); - - this._harkStream.addTrack(track.clone()); - - if (!this._harkStream.getAudioTracks()[0]) - throw new Error('enableMic(): given stream has no audio track'); - - this._hark = hark(this._harkStream, { play: false }); - - // eslint-disable-next-line no-unused-vars - this._hark.on('volume_change', (dBs, threshold) => - { - // The exact formula to convert from dBs (-100..0) to linear (0..1) is: - // Math.pow(10, dBs / 20) - // However it does not produce a visually useful output, so let exaggerate - // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to - // minimize component renderings. - let volume = Math.round(Math.pow(10, dBs / 85) * 10); - - if (volume === 1) - volume = 0; - - volume = Math.round(volume); - - if (this._micProducer && volume !== this._micProducer.volume) - { - this._micProducer.volume = volume; - - store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume)); - } - }); - this._hark.on('speaking', function() - { - store.dispatch(meActions.setIsSpeaking(true)); - }); - this._hark.on('stopped_speaking', function() - { - store.dispatch(meActions.setIsSpeaking(false)); - }); } catch (error) { diff --git a/app/src/actions/peerActions.js b/app/src/actions/peerActions.js index fee30a5..414b744 100644 --- a/app/src/actions/peerActions.js +++ b/app/src/actions/peerActions.js @@ -40,6 +40,12 @@ export const setPeerRaisedHand = (peerId, raisedHand, raisedHandTimestamp) => payload : { peerId, raisedHand, raisedHandTimestamp } }); +export const setPeerRaisedHandInProgress = (peerId, flag) => + ({ + type : 'SET_PEER_RAISED_HAND_IN_PROGRESS', + payload : { peerId, flag } + }); + export const setPeerPicture = (peerId, picture) => ({ type : 'SET_PEER_PICTURE', diff --git a/app/src/actions/settingsActions.js b/app/src/actions/settingsActions.js index 112dd0b..63c12bf 100644 --- a/app/src/actions/settingsActions.js +++ b/app/src/actions/settingsActions.js @@ -38,6 +38,50 @@ export const togglePermanentTopBar = () => type : 'TOGGLE_PERMANENT_TOPBAR' }); +export const toggleShowNotifications = () => + ({ + type : 'TOGGLE_SHOW_NOTIFICATIONS' + }); + +export const setEchoCancellation = (echoCancellation) => + ({ + type : 'SET_ECHO_CANCELLATION', + payload : { echoCancellation } + }); + +export const setAutoGainControl = (autoGainControl) => + ({ + type : 'SET_AUTO_GAIN_CONTROL', + payload : { autoGainControl } + }); + +export const setNoiseSuppression = (noiseSuppression) => + ({ + type : 'SET_NOISE_SUPPRESSION', + payload : { noiseSuppression } + }); + +export const setDefaultAudio = (audio) => + ({ + type : 'SET_DEFAULT_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/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index cbe4bee..6422788 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -16,11 +16,13 @@ import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import MenuItem from '@material-ui/core/MenuItem'; import Menu from '@material-ui/core/Menu'; +import Popover from '@material-ui/core/Popover'; import Typography from '@material-ui/core/Typography'; import IconButton from '@material-ui/core/IconButton'; import MenuIcon from '@material-ui/icons/Menu'; import Avatar from '@material-ui/core/Avatar'; import Badge from '@material-ui/core/Badge'; +import Paper from '@material-ui/core/Paper'; import ExtensionIcon from '@material-ui/icons/Extension'; import AccountCircle from '@material-ui/icons/AccountCircle'; import FullScreenIcon from '@material-ui/icons/Fullscreen'; @@ -33,6 +35,7 @@ import LockOpenIcon from '@material-ui/icons/LockOpen'; import VideoCallIcon from '@material-ui/icons/VideoCall'; import Button from '@material-ui/core/Button'; import Tooltip from '@material-ui/core/Tooltip'; +import MoreIcon from '@material-ui/icons/MoreVert'; const styles = (theme) => ({ @@ -77,9 +80,17 @@ const styles = (theme) => display : 'block' } }, - actionButtons : - { - display : 'flex' + sectionDesktop : { + display : 'none', + [theme.breakpoints.up('md')] : { + display : 'flex' + } + }, + sectionMobile : { + display : 'flex', + [theme.breakpoints.up('md')] : { + display : 'none' + } }, actionButton : { @@ -96,7 +107,7 @@ const styles = (theme) => }, moreAction : { - margin : theme.spacing(0, 0, 0, 1) + margin : theme.spacing(0.5, 0, 0.5, 1.5) } }); @@ -135,16 +146,36 @@ const TopBar = (props) => { const intl = useIntl(); - const [ moreActionsElement, setMoreActionsElement ] = useState(null); + const [ mobileMoreAnchorEl, setMobileMoreAnchorEl ] = useState(null); + const [ anchorEl, setAnchorEl ] = useState(null); + const [ currentMenu, setCurrentMenu ] = useState(null); - const handleMoreActionsOpen = (event) => + const handleExited = () => { - setMoreActionsElement(event.currentTarget); + setCurrentMenu(null); }; - const handleMoreActionsClose = () => + const handleMobileMenuOpen = (event) => { - setMoreActionsElement(null); + setMobileMoreAnchorEl(event.currentTarget); + }; + + const handleMobileMenuClose = () => + { + setMobileMoreAnchorEl(null); + }; + + const handleMenuOpen = (event, menu) => + { + setAnchorEl(event.currentTarget); + setCurrentMenu(menu); + }; + + const handleMenuClose = () => + { + setAnchorEl(null); + + handleMobileMenuClose(); }; const { @@ -171,7 +202,8 @@ const TopBar = (props) => classes } = props; - const isMoreActionsMenuOpen = Boolean(moreActionsElement); + const isMenuOpen = Boolean(anchorEl); + const isMobileMenuOpen = Boolean(mobileMoreAnchorEl); const lockTooltip = room.locked ? intl.formatMessage({ @@ -239,10 +271,15 @@ const TopBar = (props) => { window.config.title ? window.config.title : 'Multiparty meeting' }
-
+
handleMenuOpen(event, 'moreActions')} color='inherit' > @@ -386,52 +423,253 @@ const TopBar = (props) => } -
-
+
+ + + +
+
+ - + { currentMenu === 'moreActions' && + + + { + handleMenuClose(); + setExtraVideoOpen(!room.extraVideoOpen); + }} + > + +

+ +

+
+
+ } + + + { loginEnabled && + + { + handleMenuClose(); + loggedIn ? roomClient.logout() : roomClient.login(); + }} + > + { myPicture ? + + : + + } + { loggedIn ? +

+ +

+ : +

+ +

+ } +
+ } { - handleMoreActionsClose(); - setExtraVideoOpen(!room.extraVideoOpen); + handleMenuClose(); + + if (room.locked) + { + roomClient.unlockRoom(); + } + else + { + roomClient.lockRoom(); + } }} > - + { room.locked ? + + : + + } + { room.locked ? +

+ +

+ : +

+ +

+ } +
+ + { + handleMenuClose(); + setSettingsOpen(!room.settingsOpen); + }} + > +

+

+
+ { lobbyPeers.length > 0 && + + { + handleMenuClose(); + setLockDialogOpen(!room.lockDialogOpen); + }} + > + + + +

+ +

+
+ } + + { + handleMenuClose(); + openUsersTab(); + }} + > + + + +

+ +

+
+ { fullscreenEnabled && + + { + handleMenuClose(); + onFullscreen(); + }} + > + { fullscreen ? + + : + + } +

+ +

+
+ } + handleMenuOpen(event, 'moreActions')} + > + +

+

diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index 3d2596f..0ad8ef8 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -224,7 +224,7 @@ const JoinDialog = ({ myPicture={myPicture} onLogin={() => { - loggedIn ? roomClient.logout() : roomClient.login(roomId); + loggedIn ? roomClient.logout(roomId) : roomClient.login(roomId); }} loggedIn={loggedIn} > diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js index a32412f..33873d2 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js @@ -2,10 +2,12 @@ import React from 'react'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; +import classnames from 'classnames'; import PropTypes from 'prop-types'; import * as appPropTypes from '../../appPropTypes'; import { useIntl } from 'react-intl'; import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; import PanIcon from '@material-ui/icons/PanTool'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; @@ -22,7 +24,7 @@ const styles = (theme) => { borderRadius : '50%', height : '2rem', - marginTop : theme.spacing(1) + marginTop : theme.spacing(0.5) }, peerInfo : { @@ -32,6 +34,10 @@ const styles = (theme) => flexGrow : 1, alignItems : 'center' }, + buttons : + { + padding : theme.spacing(1) + }, green : { color : 'rgba(0, 153, 0, 1)' @@ -58,23 +64,33 @@ const ListMe = (props) =>
{settings.displayName}
- - { - e.stopPropagation(); - - roomClient.setRaisedHand(!me.raisedHand); - }} + placement='bottom' > - - + + { + e.stopPropagation(); + + roomClient.setRaisedHand(!me.raisedHand); + }} + > + + +
); }; diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index 914ae04..d8b3fb3 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -6,7 +6,9 @@ import PropTypes from 'prop-types'; import * as appPropTypes from '../../appPropTypes'; import { withRoomContext } from '../../../RoomContext'; import { useIntl } from 'react-intl'; +import { green } from '@material-ui/core/colors'; import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; import VideocamIcon from '@material-ui/icons/Videocam'; import VideocamOffIcon from '@material-ui/icons/VideocamOff'; import VolumeUpIcon from '@material-ui/icons/VolumeUp'; @@ -16,6 +18,7 @@ import ScreenOffIcon from '@material-ui/icons/StopScreenShare'; import ExitIcon from '@material-ui/icons/ExitToApp'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import PanIcon from '@material-ui/icons/PanTool'; +import RecordVoiceOverIcon from '@material-ui/icons/RecordVoiceOver'; const styles = (theme) => ({ @@ -30,7 +33,7 @@ const styles = (theme) => { borderRadius : '50%', height : '2rem', - marginTop : theme.spacing(1) + marginTop : theme.spacing(0.5) }, peerInfo : { @@ -43,11 +46,16 @@ const styles = (theme) => indicators : { display : 'flex', - padding : theme.spacing(1.5) + padding : theme.spacing(1) + }, + buttons : + { + padding : theme.spacing(1) }, green : { - color : 'rgba(0, 153, 0, 1)' + color : 'rgba(0, 153, 0, 1)', + marginLeft : theme.spacing(2) } }); @@ -58,6 +66,7 @@ const ListPeer = (props) => const { roomClient, isModerator, + spotlight, peer, micConsumer, webcamConsumer, @@ -93,96 +102,151 @@ const ListPeer = (props) =>
{peer.displayName}
-
- { peer.raisedHand && - - } -
- { screenConsumer && + { peer.raisedHand && + { + e.stopPropagation(); + + roomClient.lowerPeerHand(peer.id); + }} + > + + + } + { spotlight && + + + + } + { screenConsumer && + - { - e.stopPropagation(); - - screenVisible ? - roomClient.modifyPeerConsumer(peer.id, 'screen', true) : - roomClient.modifyPeerConsumer(peer.id, 'screen', false); - }} + placement='bottom' > - { screenVisible ? - - : - - } - + + { + e.stopPropagation(); + + screenVisible ? + roomClient.modifyPeerConsumer(peer.id, 'screen', true) : + roomClient.modifyPeerConsumer(peer.id, 'screen', false); + }} + > + { screenVisible ? + + : + + } + + } - - { - e.stopPropagation(); - - webcamEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'webcam', true) : - roomClient.modifyPeerConsumer(peer.id, 'webcam', false); - }} + placement='bottom' > - { webcamEnabled ? - - : - - } - - - { - e.stopPropagation(); - - micEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'mic', true) : - roomClient.modifyPeerConsumer(peer.id, 'mic', false); - }} - > - { micEnabled ? - - : - - } - - { isModerator && { e.stopPropagation(); - roomClient.kickPeer(peer.id); + webcamEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'webcam', true) : + roomClient.modifyPeerConsumer(peer.id, 'webcam', false); }} > - + { webcamEnabled ? + + : + + } + + + + { + e.stopPropagation(); + + micEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'mic', true) : + roomClient.modifyPeerConsumer(peer.id, 'mic', false); + }} + > + { micEnabled ? + + : + + } + + + { isModerator && + + + { + e.stopPropagation(); + + roomClient.kickPeer(peer.id); + }} + > + + + } {children}
@@ -194,6 +258,7 @@ ListPeer.propTypes = roomClient : PropTypes.any.isRequired, advancedMode : PropTypes.bool, isModerator : PropTypes.bool, + spotlight : PropTypes.bool, peer : appPropTypes.Peer.isRequired, micConsumer : appPropTypes.Consumer, webcamConsumer : appPropTypes.Consumer, diff --git a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js index af35dbd..07e5d24 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js @@ -1,13 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; import { - passivePeersSelector, - spotlightSortedPeersSelector + participantListSelector } from '../../Selectors'; -import classNames from 'classnames'; +import classnames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; import PropTypes from 'prop-types'; +import { Flipper, Flipped } from 'react-flip-toolkit'; import { FormattedMessage } from 'react-intl'; import ListPeer from './ListPeer'; import ListMe from './ListMe'; @@ -76,9 +76,9 @@ class ParticipantList extends React.PureComponent roomClient, advancedMode, isModerator, - passivePeers, + participants, + spotlights, selectedPeerId, - spotlightPeers, classes } = this.props; @@ -107,50 +107,42 @@ class ParticipantList extends React.PureComponent -
); @@ -162,9 +154,9 @@ ParticipantList.propTypes = roomClient : PropTypes.any.isRequired, advancedMode : PropTypes.bool, isModerator : PropTypes.bool, - passivePeers : PropTypes.array, + participants : PropTypes.array, + spotlights : PropTypes.array, selectedPeerId : PropTypes.string, - spotlightPeers : PropTypes.array, classes : PropTypes.object.isRequired }; @@ -174,9 +166,9 @@ const mapStateToProps = (state) => isModerator : state.me.roles.some((role) => state.room.permissionsFromRoles.MODERATE_ROOM.includes(role)), - passivePeers : passivePeersSelector(state), - selectedPeerId : state.room.selectedPeerId, - spotlightPeers : spotlightSortedPeersSelector(state) + participants : participantListSelector(state), + spotlights : state.room.spotlights, + selectedPeerId : state.room.selectedPeerId }; }; diff --git a/app/src/components/Room.js b/app/src/components/Room.js index cdda66f..7562ee4 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -142,6 +142,7 @@ class Room extends React.PureComponent room, browser, advancedMode, + showNotifications, toolAreaOpen, toggleToolArea, classes, @@ -178,7 +179,9 @@ class Room extends React.PureComponent - + { showNotifications && + + } @@ -232,6 +235,7 @@ Room.propTypes = room : appPropTypes.Room.isRequired, browser : PropTypes.object.isRequired, advancedMode : PropTypes.bool.isRequired, + showNotifications : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired, setToolbarsVisible : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired, @@ -241,10 +245,11 @@ Room.propTypes = const mapStateToProps = (state) => ({ - room : state.room, - browser : state.me.browser, - advancedMode : state.settings.advancedMode, - toolAreaOpen : state.toolarea.toolAreaOpen + room : state.room, + browser : state.me.browser, + advancedMode : state.settings.advancedMode, + showNotifications : state.settings.showNotifications, + toolAreaOpen : state.toolarea.toolAreaOpen }); const mapDispatchToProps = (dispatch) => @@ -270,6 +275,7 @@ export default connect( prev.room === next.room && prev.me.browser === next.me.browser && prev.settings.advancedMode === next.settings.advancedMode && + prev.settings.showNotifications === next.settings.showNotifications && prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); } diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index fd22aff..b8e5e41 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -12,7 +12,8 @@ const peersKeySelector = createSelector( peersSelector, (peers) => Object.keys(peers) ); -const peersValueSelector = createSelector( + +export const peersValueSelector = createSelector( peersSelector, (peers) => Object.values(peers) ); @@ -113,8 +114,31 @@ export const spotlightPeersSelector = createSelector( export const spotlightSortedPeersSelector = createSelector( spotlightsSelector, peersValueSelector, - (spotlights, peers) => peers.filter((peer) => spotlights.includes(peer.id)) - .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) + (spotlights, peers) => + peers.filter((peer) => spotlights.includes(peer.id) && !peer.raisedHand) + .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) +); + +const raisedHandSortedPeers = createSelector( + peersValueSelector, + (peers) => peers.filter((peer) => peer.raisedHand) + .sort((a, b) => a.raisedHandTimestamp - b.raisedHandTimestamp) +); + +const peersSortedSelector = createSelector( + spotlightsSelector, + peersValueSelector, + (spotlights, peers) => + peers.filter((peer) => !spotlights.includes(peer.id) && !peer.raisedHand) + .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) +); + +export const participantListSelector = createSelector( + raisedHandSortedPeers, + spotlightSortedPeersSelector, + peersSortedSelector, + (raisedHands, spotlights, peers) => + [ ...raisedHands, ...spotlights, ...peers ] ); export const peersLengthSelector = createSelector( diff --git a/app/src/components/Settings/AppearenceSettings.js b/app/src/components/Settings/AppearenceSettings.js index 705b2f6..a34a5e1 100644 --- a/app/src/components/Settings/AppearenceSettings.js +++ b/app/src/components/Settings/AppearenceSettings.js @@ -30,6 +30,7 @@ const AppearenceSettings = ({ settings, onTogglePermanentTopBar, onToggleHiddenControls, + onToggleShowNotifications, handleChangeMode, classes }) => @@ -101,18 +102,27 @@ const AppearenceSettings = ({ defaultMessage : 'Hidden media controls' })} /> + } + label={intl.formatMessage({ + id : 'settings.showNotifications', + defaultMessage : 'Show notifications' + })} + /> ); }; AppearenceSettings.propTypes = { - room : appPropTypes.Room.isRequired, - settings : PropTypes.object.isRequired, - onTogglePermanentTopBar : PropTypes.func.isRequired, - onToggleHiddenControls : PropTypes.func.isRequired, - handleChangeMode : PropTypes.func.isRequired, - classes : PropTypes.object.isRequired + room : appPropTypes.Room.isRequired, + settings : PropTypes.object.isRequired, + onTogglePermanentTopBar : PropTypes.func.isRequired, + onToggleHiddenControls : PropTypes.func.isRequired, + onToggleShowNotifications : PropTypes.func.isRequired, + handleChangeMode : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => @@ -122,9 +132,10 @@ const mapStateToProps = (state) => }); const mapDispatchToProps = { - onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, - onToggleHiddenControls : settingsActions.toggleHiddenControls, - handleChangeMode : roomActions.setDisplayMode + onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, + onToggleHiddenControls : settingsActions.toggleHiddenControls, + onToggleShowNotifications : settingsActions.toggleShowNotifications, + handleChangeMode : roomActions.setDisplayMode }; export default connect( diff --git a/app/src/components/Settings/MediaSettings.js b/app/src/components/Settings/MediaSettings.js index fa9728b..d215181 100644 --- a/app/src/components/Settings/MediaSettings.js +++ b/app/src/components/Settings/MediaSettings.js @@ -3,12 +3,15 @@ import { connect } from 'react-redux'; import * as appPropTypes from '../appPropTypes'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../RoomContext'; +import * as settingsActions from '../../actions/settingsActions'; import PropTypes from 'prop-types'; 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'; const styles = (theme) => ({ @@ -23,6 +26,9 @@ const styles = (theme) => }); const MediaSettings = ({ + setEchoCancellation, + setAutoGainControl, + setNoiseSuppression, roomClient, me, settings, @@ -247,6 +253,51 @@ const MediaSettings = ({ /> + + { + setEchoCancellation(event.target.checked); + roomClient.changeAudioDevice(settings.selectedAudioDevice); + }} + />} + label={intl.formatMessage({ + id : 'settings.echoCancellation', + defaultMessage : 'Echo Cancellation' + })} + /> + + { + setAutoGainControl(event.target.checked); + roomClient.changeAudioDevice(settings.selectedAudioDevice); + }} + />} + label={intl.formatMessage({ + id : 'settings.autoGainControl', + defaultMessage : 'Auto Gain Control' + })} + /> + + { + setNoiseSuppression(event.target.checked); + roomClient.changeAudioDevice(settings.selectedAudioDevice); + }} + />} + label={intl.formatMessage({ + id : 'settings.noiseSuppression', + defaultMessage : 'Noise Suppression' + })} + /> ); @@ -254,10 +305,13 @@ const MediaSettings = ({ MediaSettings.propTypes = { - roomClient : PropTypes.any.isRequired, - me : appPropTypes.Me.isRequired, - settings : PropTypes.object.isRequired, - classes : PropTypes.object.isRequired + roomClient : PropTypes.any.isRequired, + setEchoCancellation : PropTypes.func.isRequired, + setAutoGainControl : PropTypes.func.isRequired, + setNoiseSuppression : PropTypes.func.isRequired, + me : appPropTypes.Me.isRequired, + settings : PropTypes.object.isRequired, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => @@ -268,9 +322,15 @@ const mapStateToProps = (state) => }; }; +const mapDispatchToProps = { + setEchoCancellation : settingsActions.setEchoCancellation, + setAutoGainControl : settingsActions.toggleAutoGainControl, + setNoiseSuppression : settingsActions.toggleNoiseSuppression +}; + export default withRoomContext(connect( mapStateToProps, - null, + mapDispatchToProps, null, { areStatesEqual : (next, prev) => diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index fd84bae..f0e407b 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -4,13 +4,12 @@ import classnames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import EditableInput from '../Controls/EditableInput'; import Logger from '../../Logger'; -import { green, yellow, orange, red } from '@material-ui/core/colors'; +import { yellow, orange, red } from '@material-ui/core/colors'; import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff'; import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar'; import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar'; import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar'; import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar'; -import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt'; const logger = new Logger('VideoView'); @@ -162,8 +161,6 @@ class VideoView extends React.PureComponent videoMultiLayer, audioScore, videoScore, - // consumerSpatialLayers, - // consumerTemporalLayers, consumerCurrentSpatialLayer, consumerCurrentTemporalLayer, consumerPreferredSpatialLayer, @@ -215,16 +212,16 @@ class VideoView extends React.PureComponent case 7: case 8: + case 9: { quality = ; break; } - case 9: case 10: { - quality = ; + quality = null; break; } @@ -261,7 +258,7 @@ class VideoView extends React.PureComponent

{videoWidth}x{videoHeight}

}
- { !isMe && + { !isMe &&
{ quality diff --git a/app/src/reducers/peers.js b/app/src/reducers/peers.js index 4c8bee1..3e2c6a0 100644 --- a/app/src/reducers/peers.js +++ b/app/src/reducers/peers.js @@ -26,6 +26,12 @@ const peer = (state = {}, action) => raisedHand : action.payload.raisedHand, raisedHandTimestamp : action.payload.raisedHandTimestamp }; + + case 'SET_PEER_RAISED_HAND_IN_PROGRESS': + return { + ...state, + raisedHandInProgress : action.payload.flag + }; case 'ADD_CONSUMER': { @@ -91,6 +97,7 @@ const peers = (state = {}, action) => case 'SET_PEER_AUDIO_IN_PROGRESS': case 'SET_PEER_SCREEN_IN_PROGRESS': case 'SET_PEER_RAISED_HAND': + case 'SET_PEER_RAISED_HAND_IN_PROGRESS': case 'SET_PEER_PICTURE': case 'ADD_CONSUMER': case 'ADD_PEER_ROLE': diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index 549219c..7186fdc 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -4,12 +4,21 @@ const initialState = selectedWebcam : null, selectedAudioDevice : null, advancedMode : false, + sampleRate : 48000, + channelCount : 1, + volume : 1.0, + autoGainControl : true, + echoCancellation : true, + noiseSuppression : true, + sampleSize : 16, // low, medium, high, veryhigh, ultra resolution : window.config.defaultResolution || 'medium', lastN : 4, permanentTopBar : true, hiddenControls : false, - notificationSounds : true + showNotifications : true, + notificationSounds : true, + ...window.config.defaultAudio }; const settings = (state = initialState, action) => @@ -45,6 +54,83 @@ const settings = (state = initialState, action) => return { ...state, advancedMode }; } + case 'SET_SAMPLE_RATE': + { + const { sampleRate } = action.payload; + + return { ...state, sampleRate }; + } + + case 'SET_CHANNEL_COUNT': + { + const { channelCount } = action.payload; + + return { ...state, channelCount }; + } + + case 'SET_VOLUME': + { + const { volume } = action.payload; + + return { ...state, volume }; + } + + case 'SET_AUTO_GAIN_CONTROL': + { + const { autoGainControl } = action.payload; + + return { ...state, autoGainControl }; + } + + case 'SET_ECHO_CANCELLATION': + { + const { echoCancellation } = action.payload; + + return { ...state, echoCancellation }; + } + + case 'SET_NOISE_SUPPRESSION': + { + const { noiseSuppression } = action.payload; + + return { ...state, noiseSuppression }; + } + + case 'SET_DEFAULT_AUDIO': + { + const { audio } = action.payload; + + 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; + + return { ...state, sampleSize }; + } + case 'SET_LAST_N': { const { lastN } = action.payload; @@ -73,6 +159,13 @@ const settings = (state = initialState, action) => return { ...state, notificationSounds }; } + case 'TOGGLE_SHOW_NOTIFICATIONS': + { + const showNotifications = !state.showNotifications; + + return { ...state, showNotifications }; + } + case 'SET_VIDEO_RESOLUTION': { const { resolution } = action.payload; diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index e3b5597..26724a4 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "房间名称", "label.chooseRoomButton": "继续", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "设置", "settings.camera": "视频设备", @@ -128,6 +130,7 @@ "settings.lastn": "可见视频数量", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "无法保存文件", "filesharing.startingFileShare": "正在尝试共享文件", diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index fd3cc3c..b80ab14 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -79,6 +79,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Jméno místnosti", "label.chooseRoomButton": "Pokračovat", @@ -108,6 +109,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Nastavení", "settings.camera": "Kamera", @@ -127,6 +129,7 @@ "settings.lastn": null, "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Není možné uložit soubor", "filesharing.startingFileShare": "Pokouším se sdílet soubor", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 8a119ad..3eabb28 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Name des Raums", "label.chooseRoomButton": "Weiter", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Einstellungen", "settings.camera": "Kamera", @@ -128,6 +130,7 @@ "settings.lastn": "Anzahl der sichtbaren Videos", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Fehler beim Speichern der Datei", "filesharing.startingFileShare": "Starte Teilen der Datei", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 40e2698..cab3586 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Værelsesnavn", "label.chooseRoomButton": "Fortsæt", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Indstillinger", "settings.camera": "Kamera", @@ -128,6 +130,7 @@ "settings.lastn": "Antal synlige videoer", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Kan ikke gemme fil", "filesharing.startingFileShare": "Forsøger at dele filen", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 2600ab2..5a83427 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Όνομα δωματίου", "label.chooseRoomButton": "Συνέχεια", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Ρυθμίσεις", "settings.camera": "Κάμερα", @@ -128,6 +130,7 @@ "settings.lastn": "Αριθμός ορατών βίντεο", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου", "filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 19c46b4..2b40a30 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": "Mute participant", "tooltip.muteParticipantVideo": "Mute participant video", "tooltip.raisedHand": "Raise hand", + "tooltip.muteScreenSharing": "Mute participant share", "label.roomName": "Room name", "label.chooseRoomButton": "Continue", @@ -109,6 +110,7 @@ "label.advanced": "Advanced", "label.addVideo": "Add video", "label.promoteAllPeers": "Promote all", + "label.moreActions": "More actions", "settings.settings": "Settings", "settings.camera": "Camera", @@ -128,6 +130,7 @@ "settings.lastn": "Number of visible videos", "settings.hiddenControls": "Hidden media controls", "settings.notificationSounds": "Notification sounds", + "settings.showNotifications": "Show notifications", "filesharing.saveFileError": "Unable to save file", "filesharing.startingFileShare": "Attempting to share file", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 4c8abea..63c2f7d 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Nombre de la sala", "label.chooseRoomButton": "Continuar", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Ajustes", "settings.camera": "Cámara", @@ -128,6 +130,7 @@ "settings.lastn": "Cantidad de videos visibles", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "No ha sido posible guardar el fichero", "filesharing.startingFileShare": "Intentando compartir el fichero", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 9148655..4471861 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Nom de la salle", "label.chooseRoomButton": "Continuer", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Paramètres", "settings.camera": "Caméra", @@ -128,6 +130,7 @@ "settings.lastn": "Nombre de vidéos visibles", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Impossible d'enregistrer le fichier", "filesharing.startingFileShare": "Début du transfert de fichier", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index 1f5ce55..682dfbe 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": "Utišaj sudionika", "tooltip.muteParticipantVideo": "Ne primaj video sudionika", "tooltip.raisedHand": "Podigni ruku", + "tooltip.muteScreenSharing": null, "label.roomName": "Naziv sobe", "label.chooseRoomButton": "Nastavi", @@ -109,6 +110,7 @@ "label.advanced": "Napredno", "label.addVideo": "Dodaj video", "label.promoteAllPeers": "Promoviraj sve", + "label.moreActions": null, "settings.settings": "Postavke", "settings.camera": "Kamera", @@ -128,6 +130,7 @@ "settings.lastn": "Broj vidljivih videozapisa", "settings.hiddenControls": "Skrivene kontrole medija", "settings.notificationSounds": "Zvuk obavijesti", + "settings.showNotifications": null, "filesharing.saveFileError": "Nije moguće spremiti datoteku", "filesharing.startingFileShare": "Pokušaj dijeljenja datoteke", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index e185cfb..262f117 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -1,24 +1,24 @@ { "socket.disconnected": "A kapcsolat lebomlott", "socket.reconnecting": "A kapcsolat lebomlott, újrapróbálkozás", - "socket.reconnected": "Sikeres újarkapcsolódás", + "socket.reconnected": "Sikeres újrakapcsolódás", "socket.requestError": "Sikertelen szerver lekérés", "room.chooseRoom": "Válaszd ki a konferenciaszobát", "room.cookieConsent": "Ez a weblap a felhasználói élmény fokozása miatt sütiket használ", "room.consentUnderstand": "Megértettem", - "room.joined": "Csatlakozátál a konferenciához", + "room.joined": "Csatlakoztál a konferenciához", "room.cantJoin": "Sikertelen csatlakozás a konferenciához", "room.youLocked": "A konferenciába való belépés letiltva", - "room.cantLock": "Sikertelen a konferenciaba való belépés letiltása", + "room.cantLock": "Sikertelen a konferenciába való belépés letiltása", "room.youUnLocked": "A konferenciába való belépés engedélyezve", "room.cantUnLock": "Sikertelen a konferenciába való belépés engedélyezése", "room.locked": "A konferenciába való belépés letiltva", "room.unlocked": "A konferenciába való belépés engedélyezve", "room.newLobbyPeer": "Új részvevő lépett be a konferencia előszobájába", "room.lobbyPeerLeft": "A konferencia előszobájából a részvevő távozott", - "room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő meváltoztatta a nevét: {displayName}", - "room.lobbyPeerChangedPicture": "Az előszobai résztvevő meváltoztatta a képét", + "room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő megváltoztatta a nevét: {displayName}", + "room.lobbyPeerChangedPicture": "Az előszobai résztvevő megváltoztatta a képét", "room.setAccessCode": "A konferencia hozzáférési kódja megváltozott", "room.accessCodeOn": "A konferencia hozzáférési kódja aktiválva", "room.accessCodeOff": "A konferencia hozzáférési kódka deaktiválva", @@ -39,7 +39,7 @@ "room.audioOnly": "csak Hang", "room.audioVideo": "Hang és Videó", "room.youAreReady": "Ok, kész vagy", - "room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferecnia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.", + "room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferencia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.", "room.locketWait": "Az automatikus belépés tiltva van - Várj amíg valaki beenged ...", "room.lobbyAdministration": "Előszoba adminisztráció", "room.peersInLobby": "Résztvevők az előszobában", @@ -54,7 +54,7 @@ "room.closeMeeting": "Konferencia lebontása", "room.clearChat": "Chat történelem kiürítése", "room.clearFileSharing": "File megosztás kiürítése", - "room.speechUnsupported": "A böngésződ nem támogatja a hangszint", + "room.speechUnsupported": "A böngésződ nem támogatja a hangfelismerést", "room.moderatoractions": "Moderátor funkciók", "room.raisedHand": "{displayName} jelentkezik", "room.loweredHand": "{displayName} leeresztette a kezét", @@ -68,7 +68,7 @@ "tooltip.login": "Belépés", "tooltip.logout": "Kilépés", - "tooltip.admitFromLobby": "Beenegdem az előszobából", + "tooltip.admitFromLobby": "Beengedem az előszobából", "tooltip.lockRoom": "A konferenciába való belépés letiltása", "tooltip.unLockRoom": "konferenciába való belépés engedélyezése", "tooltip.enterFullscreen": "Teljes képernyős mód", @@ -78,8 +78,9 @@ "tooltip.participants": "Résztvevők", "tooltip.kickParticipant": "Résztvevő kirúgása", "tooltip.muteParticipant": "Résztvevő némítása", - "tooltip.muteParticipantVideo": "Résztvevő video némítása", + "tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása", "tooltip.raisedHand": "Jelentkezés", + "tooltip.muteScreenSharing": "Képernyőmegosztás szüneteltetése", "label.roomName": "Konferencia", "label.chooseRoomButton": "Tovább", @@ -109,6 +110,7 @@ "label.advanced": "Részletek", "label.addVideo": "Videó hozzáadása", "label.promoteAllPeers": "Mindenkit beengedek", + "label.moreActions": "További műveletek", "settings.settings": "Beállítások", "settings.camera": "Kamera", @@ -127,12 +129,13 @@ "settings.permanentTopBar": "Állandó felső sáv", "settings.lastn": "A látható videók száma", "settings.hiddenControls": "Média Gombok automatikus elrejtése", - "settings.notificationSounds": "Értesítések hangjelzjéssel", + "settings.notificationSounds": "Értesítések hangjelzéssel", + "settings.showNotifications": null, "filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.startingFileShare": "Fájl megosztása", "filesharing.successfulFileShare": "A fájl sikeresen megosztva", - "filesharing.unableToShare": "Sikereteln fájl megosztás", + "filesharing.unableToShare": "Sikertelen fájl megosztás", "filesharing.error": "Hiba a fájlmegosztás során", "filesharing.finished": "A fájl letöltés befejeződött", "filesharing.save": "Mentés", @@ -142,7 +145,7 @@ "devices.devicesChanged": "Az eszközei megváltoztak, konfiguráld őket be a beállítások menüben", - "device.audioUnsupported": "A hnag nem támogatott", + "device.audioUnsupported": "A hang nem támogatott", "device.activateAudio": "Hang aktiválása", "device.muteAudio": "Hang némítása", "device.unMuteAudio": "Hang némítás kikapcsolása", @@ -153,9 +156,9 @@ "device.screenSharingUnsupported": "A képernyő megosztás nem támogatott", "device.startScreenSharing": "Képernyőmegosztás indítása", - "device.stopScreenSharing": "Képernyőmegosztás leáłłítása", + "device.stopScreenSharing": "Képernyőmegosztás leállítása", - "devices.microphoneDisconnected": "Microphone kapcsolat bontva", + "devices.microphoneDisconnected": "Mikrofon kapcsolat bontva", "devices.microphoneError": "Hiba történt a mikrofon hangeszköz elérése közben", "devices.microphoneMute": "A mikrofon némítva lett", "devices.microphoneUnMute": "A mikrofon némítása ki lett kapocsolva", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 2c2e01a..27b3194 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -49,22 +49,22 @@ "room.spotlights": "Partecipanti in Evidenza", "room.passive": "Participanti Passivi", "room.videoPaused": "Il video è in pausa", - "room.muteAll": null, - "room.stopAllVideo": null, - "room.closeMeeting": null, - "room.clearChat": null, - "room.clearFileSharing": null, - "room.speechUnsupported": null, - "room.moderatoractions": null, - "room.raisedHand": null, - "room.loweredHand": null, - "room.extraVideo": null, + "room.muteAll": "Muta tutti", + "room.stopAllVideo": "Ferma tutti i video", + "room.closeMeeting": "Termina meeting", + "room.clearChat": "Pulisci chat", + "room.clearFileSharing": "Pulisci file sharing", + "room.speechUnsupported": "Il tuo browser non supporta il riconoscimento vocale", + "room.moderatoractions": "Azioni moderatore", + "room.raisedHand": "{displayName} ha alzato la mano", + "room.loweredHand": "{displayName} ha abbassato la mano", + "room.extraVideo": "Video extra", "room.overRoomLimit": null, - "me.mutedPTT": null, + "me.mutedPTT": "Sei mutato, tieni premuto SPAZIO per parlare", - "roles.gotRole": null, - "roles.lostRole": null, + "roles.gotRole": "Hai ottenuto il ruolo: {role}", + "roles.lostRole": "Hai perso il ruolo: {role}", "tooltip.login": "Log in", "tooltip.logout": "Log out", @@ -76,9 +76,10 @@ "tooltip.lobby": "Mostra lobby", "tooltip.settings": "Mostra impostazioni", "tooltip.participants": "Mostra partecipanti", - "tooltip.muteParticipant": null, - "tooltip.muteParticipantVideo": null, - "tooltip.raisedHand": null, + "tooltip.muteParticipant": "Muta partecipante", + "tooltip.muteParticipantVideo": "Ferma video partecipante", + "tooltip.raisedHand": "Mano alzata", + "tooltip.muteScreenSharing": null, "label.roomName": "Nome della stanza", "label.chooseRoomButton": "Continua", @@ -103,11 +104,12 @@ "label.veryHigh": "Molto alta (FHD)", "label.ultra": "Ultra (UHD)", "label.close": "Chiudi", - "label.media": null, - "label.appearence": null, - "label.advanced": null, - "label.addVideo": null, - "label.promoteAllPeers": null, + "label.media": "Media", + "label.appearence": "Aspetto", + "label.advanced": "Avanzate", + "label.addVideo": "Aggiungi video", + "label.promoteAllPeers": "Promuovi tutti", + "label.moreActions": null, "settings.settings": "Impostazioni", "settings.camera": "Videocamera", @@ -125,8 +127,9 @@ "settings.advancedMode": "Modalità avanzata", "settings.permanentTopBar": "Barra superiore permanente", "settings.lastn": "Numero di video visibili", - "settings.hiddenControls": null, - "settings.notificationSounds": null, + "settings.hiddenControls": "Controlli media nascosti", + "settings.notificationSounds": "Suoni di notifica", + "settings.showNotifications": null, "filesharing.saveFileError": "Impossibile salvare file", "filesharing.startingFileShare": "Tentativo di condivisione file", @@ -168,8 +171,8 @@ "devices.cameraDisconnected": "Videocamera scollegata", "devices.cameraError": "Errore con l'accesso alla videocamera", - "moderator.clearChat": null, - "moderator.clearFiles": null, - "moderator.muteAudio": null, - "moderator.muteVideo": null + "moderator.clearChat": "Il moderatore ha pulito la chat", + "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" } diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index dce2204..d355c84 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -79,6 +79,7 @@ "tooltip.muteParticipant": "Noklusināt dalībnieku", "tooltip.muteParticipantVideo": "Atslēgt dalībnieka video", "tooltip.raisedHand": "Pacelt roku", + "tooltip.muteScreenSharing": null, "label.roomName": "Sapulces telpas nosaukums (ID)", "label.chooseRoomButton": "Turpināt", @@ -106,6 +107,7 @@ "label.appearence": "Izskats", "label.advanced": "Advancēts", "label.addVideo": "Pievienot video", + "label.moreActions": null, "settings.settings": "Iestatījumi", "settings.camera": "Kamera", @@ -122,6 +124,7 @@ "settings.lastn": "Jums redzamo video/kameru skaits", "settings.hiddenControls": "Slēpto mediju vadība", "settings.notificationSounds": "Paziņojumu skaņas", + "settings.showNotifications": null, "filesharing.saveFileError": "Nav iespējams saglabāt failu", "filesharing.startingFileShare": "Tiek mēģināts kopīgot failu", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 2965f59..59edd6c 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": "Demp deltaker", "tooltip.muteParticipantVideo": "Demp deltakervideo", "tooltip.raisedHand": "Rekk opp hånden", + "tooltip.muteScreenSharing": "Demp deltaker skjermdeling", "label.roomName": "Møtenavn", "label.chooseRoomButton": "Fortsett", @@ -109,6 +110,7 @@ "label.advanced": "Avansert", "label.addVideo": "Legg til video", "label.promoteAllPeers": "Slipp inn alle", + "label.moreActions": "Flere handlinger", "settings.settings": "Innstillinger", "settings.camera": "Kamera", @@ -128,6 +130,7 @@ "settings.lastn": "Antall videoer synlig", "settings.hiddenControls": "Skjul media knapper", "settings.notificationSounds": "Varslingslyder", + "settings.showNotifications": "Vis varslinger", "filesharing.saveFileError": "Klarte ikke å lagre fil", "filesharing.startingFileShare": "Starter fildeling", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 14f5146..1413c73 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Nazwa konferencji", "label.chooseRoomButton": "Kontynuuj", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Ustawienia", "settings.camera": "Kamera", @@ -128,6 +130,7 @@ "settings.lastn": "Liczba widocznych uczestników (zdalnych)", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Nie można zapisać pliku", "filesharing.startingFileShare": "Próba udostępnienia pliku", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 89a8da1..3b0a078 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Nome da sala", "label.chooseRoomButton": "Continuar", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Definições", "settings.camera": "Camera", @@ -128,6 +130,7 @@ "settings.lastn": "Número de vídeos visíveis", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Impossível de gravar o ficheiro", "filesharing.startingFileShare": "Tentando partilha de ficheiro", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 49936b0..e666ddf 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Numele camerei", "label.chooseRoomButton": "Continuare", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Setări", "settings.camera": "Cameră video", @@ -128,6 +130,7 @@ "settings.lastn": "Numărul de videoclipuri vizibile", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat", "filesharing.startingFileShare": "Partajarea fișierului", diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index 3fb115f..16aaae0 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Oda adı", "label.chooseRoomButton": "Devam", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Ayarlar", "settings.camera": "Kamera", @@ -125,6 +127,7 @@ "settings.lastn": "İzlenebilir video sayısı", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Dosya kaydedilemiyor", "filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor", diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index 6c15f7a..fe9dd52 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Назва кімнати", "label.chooseRoomButton": "Продовжити", @@ -109,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Налаштування", "settings.camera": "Камера", @@ -128,6 +130,7 @@ "settings.lastn": "Кількість видимих ​​відео", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Неможливо зберегти файл", "filesharing.startingFileShare": "Спроба поділитися файлом", diff --git a/server/config/config.example.js b/server/config/config.example.js index 9d27f11..dda9e2f 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -96,8 +96,8 @@ module.exports = this._queue = new AwaitQueue(); } - // rooms: number of rooms - // peers: number of peers + // rooms: rooms object + // peers: peers object // eslint-disable-next-line no-unused-vars async log({ rooms, peers }) { @@ -106,9 +106,9 @@ module.exports = // Do your logging in here, use queue to keep correct order // eslint-disable-next-line no-console - console.log('Number of rooms: ', rooms); + console.log('Number of rooms: ', rooms.size); // eslint-disable-next-line no-console - console.log('Number of peers: ', peers); + console.log('Number of peers: ', peers.size); }) .catch((error) => { diff --git a/server/lib/Room.js b/server/lib/Room.js index 0f89b4d..02ba41c 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -117,6 +117,8 @@ class Room extends EventEmitter this._peers = {}; + this._selfDestructTimeout = null; + // Array of mediasoup Router instances. this._mediasoupRouters = mediasoupRouters; @@ -146,6 +148,11 @@ class Room extends EventEmitter this._closed = true; + if (this._selfDestructTimeout) + clearTimeout(this._selfDestructTimeout); + + this._selfDestructTimeout = null; + this._chatHistory = null; this._fileHistory = null; @@ -411,7 +418,10 @@ class Room extends EventEmitter { logger.debug('selfDestructCountdown() started'); - setTimeout(() => + if (this._selfDestructTimeout) + clearTimeout(this._selfDestructTimeout); + + this._selfDestructTimeout = setTimeout(() => { if (this._closed) return; @@ -659,7 +669,14 @@ class Room extends EventEmitter .filter((joinedPeer) => joinedPeer.id !== peer.id) .map((joinedPeer) => (joinedPeer.peerInfo)); - const lobbyPeers = this._lobby.peerList(); + let lobbyPeers = []; + + if ( // Allowed to promote peers, notify about lobbypeers + peer.roles.some((role) => + permissionsFromRoles.PROMOTE_PEER.includes(role) + ) + ) + lobbyPeers = this._lobby.peerList(); cb(null, { roles : peer.roles, @@ -1430,6 +1447,29 @@ class Room extends EventEmitter break; } + case 'moderator:lowerHand': + { + if ( + !peer.roles.some( + (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) + ) + ) + throw new Error('peer not authorized'); + + const { peerId } = request.data; + + const lowerPeer = this._peers[peerId]; + + if (!lowerPeer) + throw new Error(`peer with id "${peerId}" not found`); + + this._notification(lowerPeer.socket, 'moderator:lowerHand'); + + cb(); + + break; + } + default: { logger.error('unknown request.method "%s"', request.method); diff --git a/server/package.json b/server/package.json index ef96f36..d407d03 100644 --- a/server/package.json +++ b/server/package.json @@ -7,7 +7,7 @@ "license": "MIT", "main": "lib/index.js", "scripts": { - "start": "DEBUG=${DEBUG:='*mediasoup* *INFO* *WARN* *ERROR*'} INTERACTIVE=${INTERACTIVE:='true'} node server.js", + "start": "node server.js", "connect": "node connect.js", "lint": "eslint -c .eslintrc.json --ext .js *.js lib/" }, diff --git a/server/server.js b/server/server.js index 42112fe..fcde0b1 100755 --- a/server/server.js +++ b/server/server.js @@ -127,69 +127,58 @@ let oidcStrategy; async function run() { - // Open the interactive server. - await interactiveServer(rooms, peers); - - // start Prometheus exporter - if (config.prometheus) + try { - await promExporter(rooms, peers, config.prometheus); - } + // Open the interactive server. + await interactiveServer(rooms, peers); - if (typeof(config.auth) === 'undefined') - { - logger.warn('Auth is not configured properly!'); - } - else - { - await setupAuth(); - } - - // Run a mediasoup Worker. - await runMediasoupWorkers(); - - // Run HTTPS server. - await runHttpsServer(); - - // Run WebSocketServer. - await runWebSocketServer(); - - // eslint-disable-next-line no-unused-vars - function errorHandler(err, req, res, next) - { - const trackingId = uuidv4(); - - res.status(500).send( - `

Internal Server Error

-

If you report this error, please also report this - tracking ID which makes it possible to locate your session - in the logs which are available to the system administrator: - ${trackingId}

` - ); - logger.error( - 'Express error handler dump with tracking ID: %s, error dump: %o', - trackingId, err); - } - - app.use(errorHandler); - - // Log rooms status every 30 seconds. - setInterval(() => - { - for (const room of rooms.values()) + // start Prometheus exporter + if (config.prometheus) { - room.logStatus(); + await promExporter(rooms, peers, config.prometheus); } - }, 120000); - // check for deserted rooms - setInterval(() => - { - for (const room of rooms.values()) + if (typeof(config.auth) === 'undefined') { - room.checkEmpty(); + logger.warn('Auth is not configured properly!'); } - }, 10000); + else + { + await setupAuth(); + } + + // Run a mediasoup Worker. + await runMediasoupWorkers(); + + // Run HTTPS server. + await runHttpsServer(); + + // Run WebSocketServer. + await runWebSocketServer(); + + const errorHandler = (err, req, res, next) => + { + const trackingId = uuidv4(); + + res.status(500).send( + `

Internal Server Error

+

If you report this error, please also report this + tracking ID which makes it possible to locate your session + in the logs which are available to the system administrator: + ${trackingId}

` + ); + logger.error( + 'Express error handler dump with tracking ID: %s, error dump: %o', + trackingId, err); + }; + + // eslint-disable-next-line no-unused-vars + app.use(errorHandler); + } + catch (error) + { + logger.error('run() [error:"%o"]', error); + } } function statusLog() @@ -197,8 +186,8 @@ function statusLog() if (statusLogger) { statusLogger.log({ - rooms : rooms.size, - peers : peers.size + rooms : rooms, + peers : peers }); } }