Merge remote-tracking branch 'upstream/develop' into mm-exporter

auto_join_3.3
Christian Hörtnagl 2020-05-01 07:58:19 +02:00
commit c61ebc8287
40 changed files with 638 additions and 179 deletions

View File

@ -62,14 +62,6 @@ OR
## Configure multiparty-meeting servers ## Configure multiparty-meeting servers
### App config
mm/configs/app/config.js
``` js
multipartyServer : 'meet.example.com',
```
### Server config ### Server config
mm/configs/server/config.js mm/configs/server/config.js

View File

@ -60,6 +60,7 @@
], ],
"devDependencies": { "devDependencies": {
"electron": "^7.1.1", "electron": "^7.1.1",
"eslint-plugin-react": "^7.19.0",
"foreman": "^3.0.1", "foreman": "^3.0.1",
"redux-mock-store": "^1.5.3" "redux-mock-store": "^1.5.3"
} }

View File

@ -1,9 +1,9 @@
// eslint-disable-next-line // eslint-disable-next-line
var config = var config =
{ {
loginEnabled : false, loginEnabled : false,
developmentPort : 3443, developmentPort : 3443,
productionPort : 443, productionPort : 443,
/** /**
* If defaultResolution is set, it will override user settings when joining: * If defaultResolution is set, it will override user settings when joining:
@ -25,6 +25,7 @@ var config =
{ scaleResolutionDownBy: 2 }, { scaleResolutionDownBy: 2 },
{ scaleResolutionDownBy: 1 } { scaleResolutionDownBy: 1 }
], ],
/** /**
* White listing browsers that support audio output device selection. * White listing browsers that support audio output device selection.
* It is not yet fully implemented in Firefox. * It is not yet fully implemented in Firefox.
@ -41,13 +42,18 @@ var config =
{ {
tcp : true tcp : true
}, },
lastN : 4, defaultLayout : 'democratic', // democratic, filmstrip
mobileLastN : 1, lastN : 4,
background : 'images/background.jpg', mobileLastN : 1,
// Highest number of speakers user can select
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 // Add file and uncomment for adding logo to appbar
// logo : 'images/logo.svg', // logo : 'images/logo.svg',
title : 'Multiparty meeting', title : 'Multiparty meeting',
theme : theme :
{ {
palette : palette :
{ {

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pleaceholder for Privacy Statetment/Policy, AUP</title>
</head>
<body>
<h1>Privacy Statement</h1>
<h1>Privacy Policy</h1>
<h1>Acceptable use policy (AUP)</h1>
</body>
</html>

View File

@ -31,8 +31,7 @@ let Spotlights;
let requestTimeout, let requestTimeout,
transportOptions, transportOptions,
lastN, lastN,
mobileLastN, mobileLastN;
defaultResolution;
if (process.env.NODE_ENV !== 'test') if (process.env.NODE_ENV !== 'test')
{ {
@ -40,8 +39,7 @@ if (process.env.NODE_ENV !== 'test')
requestTimeout, requestTimeout,
transportOptions, transportOptions,
lastN, lastN,
mobileLastN, mobileLastN
defaultResolution
} = window.config); } = window.config);
} }
@ -205,9 +203,6 @@ export default class RoomClient
// Our WebTorrent client // Our WebTorrent client
this._webTorrent = null; this._webTorrent = null;
if (defaultResolution)
store.dispatch(settingsActions.setVideoResolution(defaultResolution));
// Max spotlights // Max spotlights
if (device.platform === 'desktop') if (device.platform === 'desktop')
this._maxSpotlights = lastN; this._maxSpotlights = lastN;
@ -470,9 +465,9 @@ export default class RoomClient
}); });
} }
login() login(roomId = this._roomId)
{ {
const url = `/auth/login?peerId=${this._peerId}&roomId=${this._roomId}`; const url = `/auth/login?peerId=${this._peerId}&roomId=${roomId}`;
window.open(url, 'loginWindow'); window.open(url, 'loginWindow');
} }
@ -534,11 +529,6 @@ export default class RoomClient
} }
} }
notify(text)
{
store.dispatch(requestActions.notify({ text: text }));
}
timeoutCallback(callback) timeoutCallback(callback)
{ {
let called = false; let called = false;
@ -684,7 +674,7 @@ export default class RoomClient
{ {
if (err) if (err)
{ {
return store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : intl.formatMessage({ text : intl.formatMessage({
@ -692,6 +682,8 @@ export default class RoomClient
defaultMessage : 'Unable to save file' defaultMessage : 'Unable to save file'
}) })
})); }));
return;
} }
saveAs(blob, file.name); saveAs(blob, file.name);
@ -708,7 +700,9 @@ export default class RoomClient
if (existingTorrent) if (existingTorrent)
{ {
// Never add duplicate torrents, use the existing one instead. // Never add duplicate torrents, use the existing one instead.
return this._handleTorrent(existingTorrent); this._handleTorrent(existingTorrent);
return;
} }
this._webTorrent.add(magnetUri, this._handleTorrent); this._webTorrent.add(magnetUri, this._handleTorrent);
@ -720,11 +714,13 @@ export default class RoomClient
// same file was sent multiple times. // same file was sent multiple times.
if (torrent.progress === 1) if (torrent.progress === 1)
{ {
return store.dispatch( store.dispatch(
fileActions.setFileDone( fileActions.setFileDone(
torrent.magnetURI, torrent.magnetURI,
torrent.files torrent.files
)); ));
return;
} }
let lastMove = 0; let lastMove = 0;
@ -767,7 +763,7 @@ export default class RoomClient
{ {
if (err) if (err)
{ {
return store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
text : intl.formatMessage({ text : intl.formatMessage({
@ -775,13 +771,30 @@ export default class RoomClient
defaultMessage : 'Unable to share file' defaultMessage : 'Unable to share file'
}) })
})); }));
return;
} }
const existingTorrent = this._webTorrent.get(torrent); const existingTorrent = this._webTorrent.get(torrent);
if (existingTorrent) if (existingTorrent)
{ {
return this._sendFile(existingTorrent.magnetURI); store.dispatch(requestActions.notify(
{
text : intl.formatMessage({
id : 'filesharing.successfulFileShare',
defaultMessage : 'File successfully shared'
})
}));
store.dispatch(fileActions.addFile(
this._peerId,
existingTorrent.magnetURI
));
this._sendFile(existingTorrent.magnetURI);
return;
} }
this._webTorrent.seed( this._webTorrent.seed(
@ -873,7 +886,7 @@ export default class RoomClient
store.dispatch( store.dispatch(
lobbyPeerActions.addLobbyPeer(peer.peerId)); lobbyPeerActions.addLobbyPeer(peer.peerId));
store.dispatch( store.dispatch(
lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName)); lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.peerId));
store.dispatch( store.dispatch(
lobbyPeerActions.setLobbyPeerPicture(peer.picture)); lobbyPeerActions.setLobbyPeerPicture(peer.picture));
}); });

View File

@ -86,7 +86,7 @@ const DialogTitle = withStyles(styles)((props) =>
return ( return (
<MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}> <MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}>
{ window.config && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> } { window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
<Typography variant='h5'>{children}</Typography> <Typography variant='h5'>{children}</Typography>
</MuiDialogTitle> </MuiDialogTitle>
); );
@ -125,7 +125,7 @@ const ChooseRoom = ({
}} }}
> >
<DialogTitle> <DialogTitle>
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' } { window.config.title ? window.config.title : 'Multiparty meeting' }
<hr /> <hr />
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>

View File

@ -12,8 +12,8 @@ import { useIntl, FormattedMessage } from 'react-intl';
import VideoView from '../VideoContainers/VideoView'; import VideoView from '../VideoContainers/VideoView';
import Tooltip from '@material-ui/core/Tooltip'; import Tooltip from '@material-ui/core/Tooltip';
import Fab from '@material-ui/core/Fab'; import Fab from '@material-ui/core/Fab';
import MicIcon from '@material-ui/icons/Mic'; import VolumeUpIcon from '@material-ui/icons/VolumeUp';
import MicOffIcon from '@material-ui/icons/MicOff'; import VolumeOffIcon from '@material-ui/icons/VolumeOff';
import NewWindowIcon from '@material-ui/icons/OpenInNew'; import NewWindowIcon from '@material-ui/icons/OpenInNew';
import FullScreenIcon from '@material-ui/icons/Fullscreen'; import FullScreenIcon from '@material-ui/icons/Fullscreen';
import Volume from './Volume'; import Volume from './Volume';
@ -252,9 +252,9 @@ const Peer = (props) =>
}} }}
> >
{ micEnabled ? { micEnabled ?
<MicIcon /> <VolumeUpIcon />
: :
<MicOffIcon /> <VolumeOffIcon />
} }
</Fab> </Fab>
</div> </div>
@ -340,6 +340,7 @@ const Peer = (props) =>
videoMultiLayer={webcamConsumer && webcamConsumer.type !== 'simple'} videoMultiLayer={webcamConsumer && webcamConsumer.type !== 'simple'}
videoTrack={webcamConsumer && webcamConsumer.track} videoTrack={webcamConsumer && webcamConsumer.track}
videoVisible={videoVisible} videoVisible={videoVisible}
audioTrack={micConsumer && micConsumer.track}
audioCodec={micConsumer && micConsumer.codec} audioCodec={micConsumer && micConsumer.codec}
videoCodec={webcamConsumer && webcamConsumer.codec} videoCodec={webcamConsumer && webcamConsumer.codec}
audioScore={micConsumer ? micConsumer.score : null} audioScore={micConsumer ? micConsumer.score : null}

View File

@ -80,6 +80,10 @@ const styles = (theme) =>
{ {
margin : theme.spacing(1, 0), margin : theme.spacing(1, 0),
padding : theme.spacing(0, 1) padding : theme.spacing(0, 1)
},
green :
{
color : 'rgba(0, 153, 0, 1)'
} }
}); });
@ -136,6 +140,7 @@ const TopBar = (props) =>
openUsersTab, openUsersTab,
unread, unread,
canLock, canLock,
canPromote,
classes classes
} = props; } = props;
@ -194,14 +199,14 @@ const TopBar = (props) =>
<MenuIcon /> <MenuIcon />
</IconButton> </IconButton>
</PulsingBadge> </PulsingBadge>
{ window.config && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> } { window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
<Typography <Typography
className={classes.title} className={classes.title}
variant='h6' variant='h6'
color='inherit' color='inherit'
noWrap noWrap
> >
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' } { window.config.title ? window.config.title : 'Multiparty meeting' }
</Typography> </Typography>
<div className={classes.grow} /> <div className={classes.grow} />
<div className={classes.actionButtons}> <div className={classes.actionButtons}>
@ -305,6 +310,7 @@ const TopBar = (props) =>
defaultMessage : 'Show lobby' defaultMessage : 'Show lobby'
})} })}
color='inherit' color='inherit'
disabled={!canPromote}
onClick={() => setLockDialogOpen(!room.lockDialogOpen)} onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
> >
<PulsingBadge <PulsingBadge
@ -333,7 +339,7 @@ const TopBar = (props) =>
{ myPicture ? { myPicture ?
<Avatar src={myPicture} /> <Avatar src={myPicture} />
: :
<AccountCircle /> <AccountCircle className={loggedIn && classes.green} />
} }
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@ -380,6 +386,7 @@ TopBar.propTypes =
openUsersTab : PropTypes.func.isRequired, openUsersTab : PropTypes.func.isRequired,
unread : PropTypes.number.isRequired, unread : PropTypes.number.isRequired,
canLock : PropTypes.bool.isRequired, canLock : PropTypes.bool.isRequired,
canPromote : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired, classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired theme : PropTypes.object.isRequired
}; };
@ -397,7 +404,10 @@ const mapStateToProps = (state) =>
state.toolarea.unreadFiles, state.toolarea.unreadFiles,
canLock : canLock :
state.me.roles.some((role) => state.me.roles.some((role) =>
state.room.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)) state.room.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)),
canPromote :
state.me.roles.some((role) =>
state.room.permissionsFromRoles.PROMOTE_PEER.includes(role))
}); });
const mapDispatchToProps = (dispatch) => const mapDispatchToProps = (dispatch) =>

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../RoomContext'; import { withRoomContext } from '../RoomContext';
import classnames from 'classnames';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import * as settingsActions from '../actions/settingsActions'; import * as settingsActions from '../actions/settingsActions';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -128,9 +129,9 @@ const DialogTitle = withStyles(styles)((props) =>
return ( return (
<MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}> <MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}>
{ window.config && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> } { window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
<Typography variant='h5'>{children}</Typography> <Typography variant='h5'>{children}</Typography>
{ window.config && window.config.loginEnabled && { window.config.loginEnabled &&
<Tooltip <Tooltip
onClose={handleTooltipClose} onClose={handleTooltipClose}
onOpen={handleTooltipOpen} onOpen={handleTooltipOpen}
@ -147,7 +148,9 @@ const DialogTitle = withStyles(styles)((props) =>
{ myPicture ? { myPicture ?
<Avatar src={myPicture} className={classes.largeAvatar} /> <Avatar src={myPicture} className={classes.largeAvatar} />
: :
<AccountCircle className={classes.largeIcon} /> <AccountCircle
className={classnames(classes.largeIcon, loggedIn && classes.green)}
/>
} }
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@ -217,11 +220,11 @@ const JoinDialog = ({
myPicture={myPicture} myPicture={myPicture}
onLogin={() => onLogin={() =>
{ {
loggedIn ? roomClient.logout() : roomClient.login(); loggedIn ? roomClient.logout() : roomClient.login(roomId);
}} }}
loggedIn={loggedIn} loggedIn={loggedIn}
> >
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' } { window.config.title ? window.config.title : 'Multiparty meeting' }
<hr /> <hr />
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
@ -316,6 +319,7 @@ const JoinDialog = ({
className={classes.green} className={classes.green}
gutterBottom gutterBottom
variant='h6' variant='h6'
style={{ fontWeight: '600' }}
align='center' align='center'
> >
<FormattedMessage <FormattedMessage
@ -324,7 +328,11 @@ const JoinDialog = ({
/> />
</DialogContentText> </DialogContentText>
{ room.signInRequired ? { room.signInRequired ?
<DialogContentText gutterBottom> <DialogContentText
gutterBottom
variant='h5'
style={{ fontWeight: '600' }}
>
<FormattedMessage <FormattedMessage
id='room.emptyRequireLogin' id='room.emptyRequireLogin'
defaultMessage={ defaultMessage={
@ -334,7 +342,11 @@ const JoinDialog = ({
/> />
</DialogContentText> </DialogContentText>
: :
<DialogContentText gutterBottom> <DialogContentText
gutterBottom
variant='h5'
style={{ fontWeight: '600' }}
>
<FormattedMessage <FormattedMessage
id='room.locketWait' id='room.locketWait'
defaultMessage='The room is locked - hang on until somebody lets you in ...' defaultMessage='The room is locked - hang on until somebody lets you in ...'

View File

@ -36,7 +36,7 @@ const FileSharing = (props) =>
{ {
if (event.target.files.length > 0) if (event.target.files.length > 0)
{ {
props.roomClient.shareFiles(event.target.files); await props.roomClient.shareFiles(event.target.files);
} }
}; };
@ -65,6 +65,8 @@ const FileSharing = (props) =>
type='file' type='file'
disabled={!canShare} disabled={!canShare}
onChange={handleFileChange} onChange={handleFileChange}
// Need to reset to be able to share same file twice
onClick={(e) => (e.target.value = null)}
id='share-files-button' id='share-files-button'
/> />
<label htmlFor='share-files-button'> <label htmlFor='share-files-button'>

View File

@ -8,8 +8,10 @@ import * as appPropTypes from '../../appPropTypes';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import MicIcon from '@material-ui/icons/Mic'; import VideocamIcon from '@material-ui/icons/Videocam';
import MicOffIcon from '@material-ui/icons/MicOff'; import VideocamOffIcon from '@material-ui/icons/VideocamOff';
import VolumeUpIcon from '@material-ui/icons/VolumeUp';
import VolumeOffIcon from '@material-ui/icons/VolumeOff';
import ScreenIcon from '@material-ui/icons/ScreenShare'; import ScreenIcon from '@material-ui/icons/ScreenShare';
import ScreenOffIcon from '@material-ui/icons/StopScreenShare'; import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
import ExitIcon from '@material-ui/icons/ExitToApp'; import ExitIcon from '@material-ui/icons/ExitToApp';
@ -104,11 +106,18 @@ const ListPeer = (props) =>
isModerator, isModerator,
peer, peer,
micConsumer, micConsumer,
webcamConsumer,
screenConsumer, screenConsumer,
children, children,
classes classes
} = props; } = props;
const webcamEnabled = (
Boolean(webcamConsumer) &&
!webcamConsumer.locallyPaused &&
!webcamConsumer.remotelyPaused
);
const micEnabled = ( const micEnabled = (
Boolean(micConsumer) && Boolean(micConsumer) &&
!micConsumer.locallyPaused && !micConsumer.locallyPaused &&
@ -153,8 +162,10 @@ const ListPeer = (props) =>
})} })}
color={screenVisible ? 'primary' : 'secondary'} color={screenVisible ? 'primary' : 'secondary'}
disabled={peer.peerScreenInProgress} disabled={peer.peerScreenInProgress}
onClick={() => onClick={(e) =>
{ {
e.stopPropagation();
screenVisible ? screenVisible ?
roomClient.modifyPeerConsumer(peer.id, 'screen', true) : roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
roomClient.modifyPeerConsumer(peer.id, 'screen', false); roomClient.modifyPeerConsumer(peer.id, 'screen', false);
@ -167,6 +178,28 @@ const ListPeer = (props) =>
} }
</IconButton> </IconButton>
} }
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.muteParticipantVideo',
defaultMessage : 'Mute participant video'
})}
color={webcamEnabled ? 'primary' : 'secondary'}
disabled={peer.peerVideoInProgress}
onClick={(e) =>
{
e.stopPropagation();
webcamEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'webcam', true) :
roomClient.modifyPeerConsumer(peer.id, 'webcam', false);
}}
>
{ webcamEnabled ?
<VideocamIcon />
:
<VideocamOffIcon />
}
</IconButton>
<IconButton <IconButton
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'tooltip.muteParticipant', id : 'tooltip.muteParticipant',
@ -174,17 +207,19 @@ const ListPeer = (props) =>
})} })}
color={micEnabled ? 'primary' : 'secondary'} color={micEnabled ? 'primary' : 'secondary'}
disabled={peer.peerAudioInProgress} disabled={peer.peerAudioInProgress}
onClick={() => onClick={(e) =>
{ {
e.stopPropagation();
micEnabled ? micEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'mic', true) : roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false); roomClient.modifyPeerConsumer(peer.id, 'mic', false);
}} }}
> >
{ micEnabled ? { micEnabled ?
<MicIcon /> <VolumeUpIcon />
: :
<MicOffIcon /> <VolumeOffIcon />
} }
</IconButton> </IconButton>
{ isModerator && { isModerator &&
@ -194,8 +229,10 @@ const ListPeer = (props) =>
defaultMessage : 'Kick out participant' defaultMessage : 'Kick out participant'
})} })}
disabled={peer.peerKickInProgress} disabled={peer.peerKickInProgress}
onClick={() => onClick={(e) =>
{ {
e.stopPropagation();
roomClient.kickPeer(peer.id); roomClient.kickPeer(peer.id);
}} }}
> >

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { micConsumerSelector } from '../Selectors'; import { passiveMicConsumerSelector } from '../Selectors';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import PeerAudio from './PeerAudio'; import PeerAudio from './PeerAudio';
@ -37,7 +37,7 @@ AudioPeers.propTypes =
const mapStateToProps = (state) => const mapStateToProps = (state) =>
({ ({
micConsumers : micConsumerSelector(state), micConsumers : passiveMicConsumerSelector(state),
audioOutputDevice : state.settings.selectedAudioOutputDevice audioOutputDevice : state.settings.selectedAudioOutputDevice
}); });
@ -50,7 +50,9 @@ const AudioPeersContainer = connect(
{ {
return ( return (
prev.consumers === next.consumers && prev.consumers === next.consumers &&
prev.settings.selectedAudioOutputDevice === next.settings.selectedAudioOutputDevice prev.room.spotlights === next.room.spotlights &&
prev.settings.selectedAudioOutputDevice ===
next.settings.selectedAudioOutputDevice
); );
} }
} }

View File

@ -67,6 +67,15 @@ export const screenConsumerSelector = createSelector(
(consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen') (consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen')
); );
export const passiveMicConsumerSelector = createSelector(
spotlightsSelector,
consumersSelect,
(spotlights, consumers) =>
Object.values(consumers).filter(
(consumer) => consumer.source === 'mic' && !spotlights.includes(consumer.peerId)
)
);
export const spotlightsLengthSelector = createSelector( export const spotlightsLengthSelector = createSelector(
spotlightsSelector, spotlightsSelector,
(spotlights) => spotlights.length (spotlights) => spotlights.length

View File

@ -233,8 +233,7 @@ const Settings = ({
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
</form> </form>
{ { 'audioOutputSupportedBrowsers' in window.config &&
'audioOutputSupportedBrowsers' in window.config &&
window.config.audioOutputSupportedBrowsers.includes(me.browser.name) && window.config.audioOutputSupportedBrowsers.includes(me.browser.name) &&
<form className={classes.setting} autoComplete='off'> <form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}> <FormControl className={classes.formControl}>
@ -355,36 +354,41 @@ const Settings = ({
/> />
{ settings.advancedMode && { settings.advancedMode &&
<React.Fragment> <React.Fragment>
<form className={classes.setting} autoComplete='off'> { !window.config.lockLastN &&
<FormControl className={classes.formControl}> <form className={classes.setting} autoComplete='off'>
<Select <FormControl className={classes.formControl}>
value={settings.lastN || ''} <Select
onChange={(event) => value={settings.lastN || ''}
{ onChange={(event) =>
if (event.target.value) {
roomClient.changeMaxSpotlights(event.target.value); if (event.target.value)
}} roomClient.changeMaxSpotlights(event.target.value);
name='Last N' }}
autoWidth name='Last N'
className={classes.selectEmpty} autoWidth
> className={classes.selectEmpty}
{ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ].map((lastN) => >
{ { Array.from(
return ( { length: window.config.maxLastN || 10 },
<MenuItem key={lastN} value={lastN}> (_, i) => i + 1
{lastN} ).map((lastN) =>
</MenuItem> {
); return (
})} <MenuItem key={lastN} value={lastN}>
</Select> {lastN}
<FormHelperText> </MenuItem>
<FormattedMessage );
id='settings.lastn' })}
defaultMessage='Number of visible videos' </Select>
/> <FormHelperText>
</FormHelperText> <FormattedMessage
</FormControl> id='settings.lastn'
</form> defaultMessage='Number of visible videos'
/>
</FormHelperText>
</FormControl>
</form>
}
<FormControlLabel <FormControlLabel
className={classes.setting} className={classes.setting}
control={<Checkbox checked={settings.permanentTopBar} onChange={onTogglePermanentTopBar} value='permanentTopBar' />} control={<Checkbox checked={settings.permanentTopBar} onChange={onTogglePermanentTopBar} value='permanentTopBar' />}

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import EditableInput from '../Controls/EditableInput'; import EditableInput from '../Controls/EditableInput';
import Logger from '../../Logger';
import { green, yellow, orange, red } from '@material-ui/core/colors'; import { green, yellow, orange, red } from '@material-ui/core/colors';
import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff'; import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff';
import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar'; import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar';
@ -11,6 +12,8 @@ import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar';
import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar'; import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar';
import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt'; import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt';
const logger = new Logger('VideoView');
const styles = (theme) => const styles = (theme) =>
({ ({
root : root :
@ -134,6 +137,10 @@ class VideoView extends React.PureComponent
videoHeight : null videoHeight : null
}; };
// Latest received audio track
// @type {MediaStreamTrack}
this._audioTrack = null;
// Latest received video track. // Latest received video track.
// @type {MediaStreamTrack} // @type {MediaStreamTrack}
this._videoTrack = null; this._videoTrack = null;
@ -292,7 +299,7 @@ class VideoView extends React.PureComponent
</div> </div>
<video <video
ref='video' ref='videoElement'
className={classnames(classes.video, { className={classnames(classes.video, {
hidden : !videoVisible, hidden : !videoVisible,
'isMe' : isMe && !isScreen, 'isMe' : isMe && !isScreen,
@ -300,6 +307,16 @@ class VideoView extends React.PureComponent
})} })}
autoPlay autoPlay
playsInline playsInline
muted
controls={false}
/>
<audio
ref='audioElement'
autoPlay
playsInline
muted={isMe}
controls={false}
/> />
{children} {children}
@ -309,52 +326,84 @@ class VideoView extends React.PureComponent
componentDidMount() componentDidMount()
{ {
const { videoTrack } = this.props; const { videoTrack, audioTrack } = this.props;
this._setTracks(videoTrack); this._setTracks(videoTrack, audioTrack);
} }
componentWillUnmount() componentWillUnmount()
{ {
clearInterval(this._videoResolutionTimer); clearInterval(this._videoResolutionTimer);
const { videoElement } = this.refs;
if (videoElement)
{
videoElement.oncanplay = null;
videoElement.onplay = null;
videoElement.onpause = null;
}
} }
// eslint-disable-next-line camelcase componentWillUpdate()
UNSAFE_componentWillReceiveProps(nextProps)
{ {
const { videoTrack } = nextProps; const { videoTrack, audioTrack } = this.props;
this._setTracks(videoTrack);
this._setTracks(videoTrack, audioTrack);
} }
_setTracks(videoTrack) _setTracks(videoTrack, audioTrack)
{ {
if (this._videoTrack === videoTrack) if (this._videoTrack === videoTrack && this._audioTrack === audioTrack)
return; return;
this._videoTrack = videoTrack; this._videoTrack = videoTrack;
this._audioTrack = audioTrack;
clearInterval(this._videoResolutionTimer); clearInterval(this._videoResolutionTimer);
this._hideVideoResolution(); this._hideVideoResolution();
const { video } = this.refs; const { videoElement, audioElement } = this.refs;
if (videoTrack) if (videoTrack)
{ {
const stream = new MediaStream(); const stream = new MediaStream();
if (videoTrack) stream.addTrack(videoTrack);
stream.addTrack(videoTrack);
video.srcObject = stream; videoElement.srcObject = stream;
if (videoTrack) videoElement.oncanplay = () => this.setState({ videoCanPlay: true });
this._showVideoResolution();
videoElement.onplay = () =>
{
audioElement.play()
.catch((error) => logger.warn('audioElement.play() [error:"%o]', error));
};
videoElement.play()
.catch((error) => logger.warn('videoElement.play() [error:"%o]', error));
this._showVideoResolution();
} }
else else
{ {
video.srcObject = null; videoElement.srcObject = null;
}
if (audioTrack)
{
const stream = new MediaStream();
stream.addTrack(audioTrack);
audioElement.srcObject = stream;
audioElement.play()
.catch((error) => logger.warn('audioElement.play() [error:"%o]', error));
}
else
{
audioElement.srcObject = null;
} }
} }
@ -363,16 +412,19 @@ class VideoView extends React.PureComponent
this._videoResolutionTimer = setInterval(() => this._videoResolutionTimer = setInterval(() =>
{ {
const { videoWidth, videoHeight } = this.state; const { videoWidth, videoHeight } = this.state;
const { video } = this.refs; const { videoElement } = this.refs;
// Don't re-render if nothing changed. // Don't re-render if nothing changed.
if (video.videoWidth === videoWidth && video.videoHeight === videoHeight) if (
videoElement.videoWidth === videoWidth &&
videoElement.videoHeight === videoHeight
)
return; return;
this.setState( this.setState(
{ {
videoWidth : video.videoWidth, videoWidth : videoElement.videoWidth,
videoHeight : video.videoHeight videoHeight : videoElement.videoHeight
}); });
}, 1000); }, 1000);
} }
@ -392,6 +444,7 @@ VideoView.propTypes =
videoContain : PropTypes.bool, videoContain : PropTypes.bool,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
videoTrack : PropTypes.any, videoTrack : PropTypes.any,
audioTrack : PropTypes.any,
videoVisible : PropTypes.bool.isRequired, videoVisible : PropTypes.bool.isRequired,
consumerSpatialLayers : PropTypes.number, consumerSpatialLayers : PropTypes.number,
consumerTemporalLayers : PropTypes.number, consumerTemporalLayers : PropTypes.number,

View File

@ -37,6 +37,7 @@ import messagesCroatian from './translations/hr';
import messagesCzech from './translations/cs'; import messagesCzech from './translations/cs';
import messagesItalian from './translations/it'; import messagesItalian from './translations/it';
import messagesUkrainian from './translations/uk'; import messagesUkrainian from './translations/uk';
import messagesTurkish from './translations/tr';
import './index.css'; import './index.css';
@ -61,7 +62,8 @@ const messages =
'hr' : messagesCroatian, 'hr' : messagesCroatian,
'cs' : messagesCzech, 'cs' : messagesCzech,
'it' : messagesItalian, 'it' : messagesItalian,
'uk' : messagesUkrainian 'uk' : messagesUkrainian,
'tr' : messagesTurkish
}; };
const locale = navigator.language.split(/[-_]/)[0]; // language without region code const locale = navigator.language.split(/[-_]/)[0]; // language without region code

View File

@ -1,19 +1,22 @@
const initialState = const initialState =
{ {
name : '', name : '',
state : 'new', // new/connecting/connected/disconnected/closed, // new/connecting/connected/disconnected/closed,
state : 'new',
locked : false, locked : false,
inLobby : false, inLobby : false,
signInRequired : false, signInRequired : false,
accessCode : '', // access code to the room if locked and joinByAccessCode == true // access code to the room if locked and joinByAccessCode == true
joinByAccessCode : true, // if true: accessCode is a possibility to open the room accessCode : '',
// if true: accessCode is a possibility to open the room
joinByAccessCode : true,
activeSpeakerId : null, activeSpeakerId : null,
torrentSupport : false, torrentSupport : false,
showSettings : false, showSettings : false,
fullScreenConsumer : null, // ConsumerID fullScreenConsumer : null, // ConsumerID
windowConsumer : null, // ConsumerID windowConsumer : null, // ConsumerID
toolbarsVisible : true, toolbarsVisible : true,
mode : 'democratic', mode : window.config.defaultLayout || 'democratic',
selectedPeerId : null, selectedPeerId : null,
spotlights : [], spotlights : [],
settingsOpen : false, settingsOpen : false,

View File

@ -4,7 +4,8 @@ const initialState =
selectedWebcam : null, selectedWebcam : null,
selectedAudioDevice : null, selectedAudioDevice : null,
advancedMode : false, advancedMode : false,
resolution : 'medium', // low, medium, high, veryhigh, ultra // low, medium, high, veryhigh, ultra
resolution : window.config.defaultResolution || 'medium',
lastN : 4, lastN : 4,
permanentTopBar : true permanentTopBar : true
}; };

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "显示设置", "tooltip.settings": "显示设置",
"tooltip.participants": "显示参加者", "tooltip.participants": "显示参加者",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "房间名称", "label.roomName": "房间名称",
"label.chooseRoomButton": "继续", "label.chooseRoomButton": "继续",

View File

@ -54,6 +54,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -66,6 +67,10 @@
"tooltip.leaveFullscreen": "Vypnout režim celé obrazovky (fullscreen)", "tooltip.leaveFullscreen": "Vypnout režim celé obrazovky (fullscreen)",
"tooltip.lobby": "Ukázat Přijímací místnost", "tooltip.lobby": "Ukázat Přijímací místnost",
"tooltip.settings": "Zobrazit nastavení", "tooltip.settings": "Zobrazit nastavení",
"tooltip.participants": null,
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Jméno místnosti", "label.roomName": "Jméno místnosti",
"label.chooseRoomButton": "Pokračovat", "label.chooseRoomButton": "Pokračovat",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung", "room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung",
"room.moderatoractions": null,
"me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen", "me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen",
@ -69,6 +70,8 @@
"tooltip.settings": "Einstellungen", "tooltip.settings": "Einstellungen",
"tooltip.participants": "Teilnehmer", "tooltip.participants": "Teilnehmer",
"tooltip.kickParticipant": "Teilnehmer rauswerfen", "tooltip.kickParticipant": "Teilnehmer rauswerfen",
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Name des Raums", "label.roomName": "Name des Raums",
"label.chooseRoomButton": "Weiter", "label.chooseRoomButton": "Weiter",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "Vis indstillinger", "tooltip.settings": "Vis indstillinger",
"tooltip.participants": "Vis deltagere", "tooltip.participants": "Vis deltagere",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Værelsesnavn", "label.roomName": "Værelsesnavn",
"label.chooseRoomButton": "Fortsæt", "label.chooseRoomButton": "Fortsæt",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "Εμφάνιση ρυθμίσεων", "tooltip.settings": "Εμφάνιση ρυθμίσεων",
"tooltip.participants": "Εμφάνιση συμμετεχόντων", "tooltip.participants": "Εμφάνιση συμμετεχόντων",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Όνομα δωματίου", "label.roomName": "Όνομα δωματίου",
"label.chooseRoomButton": "Συνέχεια", "label.chooseRoomButton": "Συνέχεια",

View File

@ -55,6 +55,7 @@
"room.clearChat": "Clear chat", "room.clearChat": "Clear chat",
"room.clearFileSharing": "Clear files", "room.clearFileSharing": "Clear files",
"room.speechUnsupported": "Your browser does not support speech recognition", "room.speechUnsupported": "Your browser does not support speech recognition",
"room.moderatoractions": "Moderator actions",
"me.mutedPTT": "You are muted, hold down SPACE-BAR to talk", "me.mutedPTT": "You are muted, hold down SPACE-BAR to talk",
@ -69,6 +70,8 @@
"tooltip.settings": "Show settings", "tooltip.settings": "Show settings",
"tooltip.participants": "Show participants", "tooltip.participants": "Show participants",
"tooltip.kickParticipant": "Kick out participant", "tooltip.kickParticipant": "Kick out participant",
"tooltip.muteParticipant": "Mute participant",
"tooltip.muteParticipantVideo": "Mute participant video",
"label.roomName": "Room name", "label.roomName": "Room name",
"label.chooseRoomButton": "Continue", "label.chooseRoomButton": "Continue",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "Mostrar ajustes", "tooltip.settings": "Mostrar ajustes",
"tooltip.participants": "Mostrar participantes", "tooltip.participants": "Mostrar participantes",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Nombre de la sala", "label.roomName": "Nombre de la sala",
"label.chooseRoomButton": "Continuar", "label.chooseRoomButton": "Continuar",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "Afficher les paramètres", "tooltip.settings": "Afficher les paramètres",
"tooltip.participants": "Afficher les participants", "tooltip.participants": "Afficher les participants",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Nom de la salle", "label.roomName": "Nom de la salle",
"label.chooseRoomButton": "Continuer", "label.chooseRoomButton": "Continuer",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora", "room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora",
"room.moderatoractions": null,
"me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor", "me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor",
@ -69,6 +70,8 @@
"tooltip.settings": "Prikaži postavke", "tooltip.settings": "Prikaži postavke",
"tooltip.participants": "Pokažite sudionike", "tooltip.participants": "Pokažite sudionike",
"tooltip.kickParticipant": "Izbaci sudionika", "tooltip.kickParticipant": "Izbaci sudionika",
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Naziv sobe", "label.roomName": "Naziv sobe",
"label.chooseRoomButton": "Nastavi", "label.chooseRoomButton": "Nastavi",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "Beállítások", "tooltip.settings": "Beállítások",
"tooltip.participants": "Résztvevők", "tooltip.participants": "Résztvevők",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Konferencia", "label.roomName": "Konferencia",
"label.chooseRoomButton": "Tovább", "label.chooseRoomButton": "Tovább",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -68,6 +69,8 @@
"tooltip.lobby": "Mostra lobby", "tooltip.lobby": "Mostra lobby",
"tooltip.settings": "Mostra impostazioni", "tooltip.settings": "Mostra impostazioni",
"tooltip.participants": "Mostra partecipanti", "tooltip.participants": "Mostra partecipanti",
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Nome della stanza", "label.roomName": "Nome della stanza",
"label.chooseRoomButton": "Continua", "label.chooseRoomButton": "Continua",

View File

@ -55,6 +55,7 @@
"room.clearChat": "Tøm chat", "room.clearChat": "Tøm chat",
"room.clearFileSharing": "Fjern filer", "room.clearFileSharing": "Fjern filer",
"room.speechUnsupported": "Din nettleser støtter ikke stemmegjenkjenning", "room.speechUnsupported": "Din nettleser støtter ikke stemmegjenkjenning",
"room.moderatoractions": "Moderatorhandlinger",
"me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke", "me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke",
@ -69,6 +70,8 @@
"tooltip.settings": "Vis innstillinger", "tooltip.settings": "Vis innstillinger",
"tooltip.participants": "Vis deltakere", "tooltip.participants": "Vis deltakere",
"tooltip.kickParticipant": "Spark ut deltaker", "tooltip.kickParticipant": "Spark ut deltaker",
"tooltip.muteParticipant": "Demp deltaker",
"tooltip.muteParticipantVideo": "Demp deltakervideo",
"label.roomName": "Møtenavn", "label.roomName": "Møtenavn",
"label.chooseRoomButton": "Fortsett", "label.chooseRoomButton": "Fortsett",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "Pokaż ustawienia", "tooltip.settings": "Pokaż ustawienia",
"tooltip.participants": "Pokaż uczestników", "tooltip.participants": "Pokaż uczestników",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Nazwa konferencji", "label.roomName": "Nazwa konferencji",
"label.chooseRoomButton": "Kontynuuj", "label.chooseRoomButton": "Kontynuuj",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "Apresentar definições", "tooltip.settings": "Apresentar definições",
"tooltip.participants": "Apresentar participantes", "tooltip.participants": "Apresentar participantes",
"tooltip.kickParticipant": null, "tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Nome da sala", "label.roomName": "Nome da sala",
"label.chooseRoomButton": "Continuar", "label.chooseRoomButton": "Continuar",

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null, "me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "Arată setăile", "tooltip.settings": "Arată setăile",
"tooltip.participants": null, "tooltip.participants": null,
"tooltip.kickParticipant": null, "tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Numele camerei", "label.roomName": "Numele camerei",
"label.chooseRoomButton": "Continuare", "label.chooseRoomButton": "Continuare",

View File

@ -0,0 +1,150 @@
{
"socket.disconnected": "Bağlantınız Kesildi",
"socket.reconnecting": "Bağlantınız kesildi, yeniden bağlanmaya çalışılıyor",
"socket.reconnected": "Yeniden bağlandınız",
"socket.requestError": "Sunucu isteğinde hata",
"room.chooseRoom": "Katılmak istediğiniz odanın adını seçin",
"room.cookieConsent": "Bu web sayfası kullanıcı deneyimini geliştirmek için çerezleri kullanmaktadır",
"room.consentUnderstand": "Anladım",
"room.joined": "Odaya katıldın",
"room.cantJoin": "Odaya katılamadın",
"room.youLocked": "Odayı kilitledin",
"room.cantLock": "Oda kilitlenemiyor",
"room.youUnLocked": "Odanın kilidini açtın",
"room.cantUnLock": "Odanın kilidi açılamıyor",
"room.locked": "Oda kilitlendi",
"room.unlocked": "Oda kilidi açıldı",
"room.newLobbyPeer": "Lobiye yeni katılımcı girdi",
"room.lobbyPeerLeft": "Lobiden katılımcı ayrıldı",
"room.lobbyPeerChangedDisplayName": "Lobideki katılımcı adını {displayName} olarak değiştirdi",
"room.lobbyPeerChangedPicture": "Lobideki katılımcı resim değiştirdi",
"room.setAccessCode": "Oda için erişim kodu güncellendi",
"room.accessCodeOn": "Oda erişim kodu etkinleştirildi",
"room.accessCodeOff": "Oda erişim kodu devre dışı",
"room.peerChangedDisplayName": "{oldDisplayName}, {displayName} olarak değiştirildi",
"room.newPeer": "{displayName} odaya katıldı",
"room.newFile": "Yeni dosya mevcut",
"room.toggleAdvancedMode": "Gelişmiş moda geçiş",
"room.setDemocraticView": "Demokratik görünüme geçtiniz",
"room.setFilmStripView": "Filmşeridi görünümüne geçtiniz",
"room.loggedIn": "Giriş yaptınız",
"room.loggedOut": ıkış yaptınız",
"room.changedDisplayName": "Adınız {displayName} olarak değiştirildi",
"room.changeDisplayNameError": "Adınız değiştirilirken bir hata oluştu",
"room.chatError": "Sohbet mesajı gönderilemiyor",
"room.aboutToJoin": "Toplantıya katılmak üzeresiniz",
"room.roomId": "Oda ID: {roomName}",
"room.setYourName": "Katılım için adınızı belirleyin ve nasıl katılmak istediğinizi seçin:",
"room.audioOnly": "Sadece ses",
"room.audioVideo": "Ses ve Video",
"room.youAreReady": "Tamam, hazırsın",
"room.emptyRequireLogin": "Oda boş! Toplantıyı başlatmak için oturum açabilirsiniz veya toplantı sahibi katılana kadar bekleyebilirsiniz",
"room.locketWait": "Oda kilitli - birisi içeri alana kadar bekleyiniz ...",
"room.lobbyAdministration": "Lobi Yöneticisi",
"room.peersInLobby": "Lobideki katılımcılar",
"room.lobbyEmpty": "Lobide katılımcı yok",
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
"room.me": "Ben",
"room.spotlights": "Gündemdeki Katılımcılar",
"room.passive": "Pasif Katılımcılar",
"room.videoPaused": "Video duraklatıldı",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"tooltip.login": "Giriş",
"tooltip.logout": ıkış",
"tooltip.admitFromLobby": "Lobiden içeri al",
"tooltip.lockRoom": "Oda kilitle",
"tooltip.unLockRoom": "Oda kilidini aç",
"tooltip.enterFullscreen": "Tam Ekrana Geç",
"tooltip.leaveFullscreen": "Tam Ekrandan Çık",
"tooltip.lobby": "Lobiyi göster",
"tooltip.settings": "Ayarları göster",
"tooltip.participants": "Katılımcıları göster",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Oda adı",
"label.chooseRoomButton": "Devam",
"label.yourName": "Adınız",
"label.newWindow": "Yeni pencere",
"label.fullscreen": "Tam Ekran",
"label.openDrawer": "Çiziciyi aç",
"label.leave": "Ayrıl",
"label.chatInput": "Sohbet mesajı gir...",
"label.chat": "Sohbet",
"label.filesharing": "Dosya paylaşım",
"label.participants": "Katılımcı",
"label.shareFile": "Dosya paylaş",
"label.fileSharingUnsupported": "Dosya paylaşımı desteklenmiyor",
"label.unknown": "Bilinmeyen",
"label.democratic": "Demokratik görünüm",
"label.filmstrip": "Filmşeridi görünüm",
"label.low": "Düşük",
"label.medium": "Orta",
"label.high": "Yüksek (HD)",
"label.veryHigh": "Çok Yüksek (FHD)",
"label.ultra": "Ultra (UHD)",
"label.close": "Kapat",
"settings.settings": "Ayarlar",
"settings.camera": "Kamera",
"settings.selectCamera": "Video aygıtını seç",
"settings.cantSelectCamera": "Video aygıtı seçilemiyor",
"settings.audio": "Ses aygıtı",
"settings.selectAudio": "Ses aygıtını seç",
"settings.cantSelectAudio": "Ses aygıtı seçilemiyor",
"settings.resolution": "Video çözünürlüğü ayarla",
"settings.layout": "Oda düzeni",
"settings.selectRoomLayout": "Oda düzeni seç",
"settings.advancedMode": "Detaylı mod",
"settings.permanentTopBar": "Üst barı kalıcı yap",
"settings.lastn": "İzlenebilir video sayısı",
"filesharing.saveFileError": "Dosya kaydedilemiyor",
"filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor",
"filesharing.successfulFileShare": "Dosya başarıyla paylaşıldı",
"filesharing.unableToShare": "Dosya paylaşılamıyor",
"filesharing.error": "Dosya paylaşım hatası",
"filesharing.finished": "Dosya indirilmesi tamamlandı",
"filesharing.save": "Kaydet",
"filesharing.sharedFile": "{displayName} bir dosya paylaştı",
"filesharing.download": "İndir",
"filesharing.missingSeeds": "İşlem uzun zaman alıyorsa, bu torrent'i paylaşan kimse olmayabilir. İlgili dosyayı yeniden yüklemesini isteyin.",
"devices.devicesChanged": "Cihazlarınız değişti, ayarlar kutusundan cihazlarınızı yapılandırın",
"device.audioUnsupported": "Ses desteklenmiyor",
"device.activateAudio": "Sesi aktif et",
"device.muteAudio": "Sesi kıs",
"device.unMuteAudio": "Sesi aç",
"device.videoUnsupported": "Video desteklenmiyor",
"device.startVideo": "Video başlat",
"device.stopVideo": "Video durdur",
"device.screenSharingUnsupported": "Ekran paylaşımı desteklenmiyor",
"device.startScreenSharing": "Ekran paylaşımını başlat",
"device.stopScreenSharing": "Ekran paylaşımını durdur",
"devices.microphoneDisconnected": "Mikrofon bağlı değil",
"devices.microphoneError": "Mikrofononuza erişilirken bir hata oluştu",
"devices.microPhoneMute": "Mikrofonumu kıs",
"devices.micophoneUnMute": "Mikrofonumu aç",
"devices.microphoneEnable": "Mikrofonumu aktif et",
"devices.microphoneMuteError": "Mikrofonunuz kısılamıyor",
"devices.microphoneUnMuteError": "Mikrofonunuz açılamıyor",
"devices.screenSharingDisconnected" : "Ekran paylaşımı bağlı değil",
"devices.screenSharingError": "Ekranınıza erişilirken bir hata oluştu",
"devices.cameraDisconnected": "Kamera bağlı değil",
"devices.cameraError": "Kameranıza erişilirken bir hata oluştu"
}

View File

@ -55,6 +55,7 @@
"room.clearChat": null, "room.clearChat": null,
"room.clearFileSharing": null, "room.clearFileSharing": null,
"room.speechUnsupported": null, "room.speechUnsupported": null,
"room.moderatoractions": null,
"tooltip.login": "Увійти", "tooltip.login": "Увійти",
"tooltip.logout": "Вихід", "tooltip.logout": "Вихід",
@ -66,6 +67,9 @@
"tooltip.lobby": "Показати зал очікувань", "tooltip.lobby": "Показати зал очікувань",
"tooltip.settings": "Показати налаштування", "tooltip.settings": "Показати налаштування",
"tooltip.participants": "Показати учасників", "tooltip.participants": "Показати учасників",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Назва кімнати", "label.roomName": "Назва кімнати",
"label.chooseRoomButton": "Продовжити", "label.chooseRoomButton": "Продовжити",

View File

@ -36,14 +36,14 @@ module.exports =
}, },
*/ */
// URI and key for requesting geoip-based TURN server closest to the client // URI and key for requesting geoip-based TURN server closest to the client
turnAPIKey : 'examplekey', turnAPIKey : 'examplekey',
turnAPIURI : 'https://example.com/api/turn', turnAPIURI : 'https://example.com/api/turn',
turnAPIparams : { turnAPIparams : {
'uri_schema' : 'turn', 'uri_schema' : 'turn',
'transport' : 'tcp', 'transport' : 'tcp',
'ip_ver' : 'ipv4', 'ip_ver' : 'ipv4',
'servercount' : '2' 'servercount' : '2'
}, },
// Backup turnservers if REST fails or is not configured // Backup turnservers if REST fails or is not configured
backupTurnServers : [ backupTurnServers : [
@ -60,14 +60,16 @@ module.exports =
// session cookie secret // session cookie secret
cookieSecret : 'T0P-S3cR3t_cook!e', cookieSecret : 'T0P-S3cR3t_cook!e',
cookieName : 'multiparty-meeting.sid', cookieName : 'multiparty-meeting.sid',
// if you use encrypted private key the set the passphrase
tls : tls :
{ {
cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`, cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`,
// passphrase: 'key_password'
key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem` key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem`
}, },
// listening Host or IP // listening Host or IP
// If omitted listens on every IP. ("0.0.0.0" and "::") // If omitted listens on every IP. ("0.0.0.0" and "::")
//listeningHost: 'localhost', // listeningHost: 'localhost',
// Listening port for https server. // Listening port for https server.
listeningPort : 443, listeningPort : 443,
// Any http request is redirected to https. // Any http request is redirected to https.
@ -77,6 +79,12 @@ module.exports =
// listeningRedirectPort disabled // listeningRedirectPort disabled
// use case: loadbalancer backend // use case: loadbalancer backend
httpOnly : false, httpOnly : false,
// WebServer/Express trust proxy config for httpOnly mode
// You can find more info:
// - https://expressjs.com/en/guide/behind-proxies.html
// - https://www.npmjs.com/package/proxy-addr
// use case: loadbalancer backend
trustProxy : '',
// This logger class will have the log function // This logger class will have the log function
// called every time there is a room created or destroyed, // called every time there is a room created or destroyed,
// or peer created or destroyed. This would then be able // or peer created or destroyed. This would then be able
@ -109,12 +117,6 @@ module.exports =
}); });
} }
}, */ }, */
// WebServer/Express trust proxy config for httpOnly mode
// You can find more info:
// - https://expressjs.com/en/guide/behind-proxies.html
// - https://www.npmjs.com/package/proxy-addr
// use case: loadbalancer backend
trustProxy : '',
// This function will be called on successful login through oidc. // This function will be called on successful login through oidc.
// Use this function to map your oidc userinfo to the Peer object. // Use this function to map your oidc userinfo to the Peer object.
// The roomId is equal to the room name. // The roomId is equal to the room name.
@ -211,7 +213,7 @@ module.exports =
// //
// Example: // Example:
// [ userRoles.MODERATOR, userRoles.AUTHENTICATED ] // [ userRoles.MODERATOR, userRoles.AUTHENTICATED ]
accessFromRoles : { accessFromRoles : {
// The role(s) will gain access to the room // The role(s) will gain access to the room
// even if it is locked (!) // even if it is locked (!)
BYPASS_ROOM_LOCK : [ userRoles.ADMIN ], BYPASS_ROOM_LOCK : [ userRoles.ADMIN ],
@ -222,7 +224,7 @@ module.exports =
// function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ] // function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ]
BYPASS_LOBBY : [ userRoles.NORMAL ] BYPASS_LOBBY : [ userRoles.NORMAL ]
}, },
permissionsFromRoles : { permissionsFromRoles : {
// The role(s) have permission to lock/unlock a room // The role(s) have permission to lock/unlock a room
CHANGE_ROOM_LOCK : [ userRoles.NORMAL ], CHANGE_ROOM_LOCK : [ userRoles.NORMAL ],
// The role(s) have permission to promote a peer from the lobby // The role(s) have permission to promote a peer from the lobby
@ -242,9 +244,9 @@ module.exports =
}, },
// When truthy, the room will be open to all users when as long as there // When truthy, the room will be open to all users when as long as there
// are allready users in the room // are allready users in the room
activateOnHostJoin : true, activateOnHostJoin : true,
// Mediasoup settings // Mediasoup settings
mediasoup : mediasoup :
{ {
numWorkers : Object.keys(os.cpus()).length, numWorkers : Object.keys(os.cpus()).length,
// mediasoup Worker settings. // mediasoup Worker settings.
@ -325,11 +327,12 @@ module.exports =
{ {
listenIps : listenIps :
[ [
// change ip to your servers IP address! // change 192.0.2.1 IPv4 to your server's IPv4 address!!
{ ip: '0.0.0.0', announcedIp: null } { ip: '192.0.2.1', announcedIp: null }
// Can have multiple listening interfaces // Can have multiple listening interfaces
// { ip: '::/0', announcedIp: null } // change 2001:DB8::1 IPv6 to your server's IPv6 address!!
// { ip: '2001:DB8::1', announcedIp: null }
], ],
initialAvailableOutgoingBitrate : 1000000, initialAvailableOutgoingBitrate : 1000000,
minimumAvailableOutgoingBitrate : 600000, minimumAvailableOutgoingBitrate : 600000,

View File

@ -9,6 +9,27 @@ const config = require('../config/config');
const logger = new Logger('Room'); const logger = new Logger('Room');
// In case they are not configured properly
const accessFromRoles =
{
BYPASS_ROOM_LOCK : [ userRoles.ADMIN ],
BYPASS_LOBBY : [ userRoles.NORMAL ],
...config.accessFromRoles
};
const permissionsFromRoles =
{
CHANGE_ROOM_LOCK : [ userRoles.NORMAL ],
PROMOTE_PEER : [ userRoles.NORMAL ],
SEND_CHAT : [ userRoles.NORMAL ],
MODERATE_CHAT : [ userRoles.MODERATOR ],
SHARE_SCREEN : [ userRoles.NORMAL ],
SHARE_FILE : [ userRoles.NORMAL ],
MODERATE_FILES : [ userRoles.MODERATOR ],
MODERATE_ROOM : [ userRoles.MODERATOR ],
...config.permissionsFromRoles
};
class Room extends EventEmitter class Room extends EventEmitter
{ {
/** /**
@ -152,7 +173,7 @@ class Room extends EventEmitter
if (returning) if (returning)
this._peerJoining(peer, true); this._peerJoining(peer, true);
else if ( // Has a role that is allowed to bypass room lock else if ( // Has a role that is allowed to bypass room lock
peer.roles.some((role) => config.accessFromRoles.BYPASS_ROOM_LOCK.includes(role)) peer.roles.some((role) => accessFromRoles.BYPASS_ROOM_LOCK.includes(role))
) )
this._peerJoining(peer); this._peerJoining(peer);
else if (this._locked) else if (this._locked)
@ -160,7 +181,7 @@ class Room extends EventEmitter
else else
{ {
// Has a role that is allowed to bypass lobby // Has a role that is allowed to bypass lobby
peer.roles.some((role) => config.accessFromRoles.BYPASS_LOBBY.includes(role)) ? peer.roles.some((role) => accessFromRoles.BYPASS_LOBBY.includes(role)) ?
this._peerJoining(peer) : this._peerJoining(peer) :
this._handleGuest(peer); this._handleGuest(peer);
} }
@ -187,7 +208,11 @@ class Room extends EventEmitter
this._peerJoining(promotedPeer); this._peerJoining(promotedPeer);
for (const peer of this._getJoinedPeers()) for (
const peer of this._getPeersWithPermission({
permission : permissionsFromRoles.PROMOTE_PEER
})
)
{ {
this._notification(peer.socket, 'lobby:promotedPeer', { peerId: id }); this._notification(peer.socket, 'lobby:promotedPeer', { peerId: id });
} }
@ -196,7 +221,7 @@ class Room extends EventEmitter
this._lobby.on('peerRolesChanged', (peer) => this._lobby.on('peerRolesChanged', (peer) =>
{ {
if ( // Has a role that is allowed to bypass room lock if ( // Has a role that is allowed to bypass room lock
peer.roles.some((role) => config.accessFromRoles.BYPASS_ROOM_LOCK.includes(role)) peer.roles.some((role) => accessFromRoles.BYPASS_ROOM_LOCK.includes(role))
) )
{ {
this._lobby.promotePeer(peer.id); this._lobby.promotePeer(peer.id);
@ -206,7 +231,7 @@ class Room extends EventEmitter
if ( // Has a role that is allowed to bypass lobby if ( // Has a role that is allowed to bypass lobby
!this._locked && !this._locked &&
peer.roles.some((role) => config.accessFromRoles.BYPASS_LOBBY.includes(role)) peer.roles.some((role) => accessFromRoles.BYPASS_LOBBY.includes(role))
) )
{ {
this._lobby.promotePeer(peer.id); this._lobby.promotePeer(peer.id);
@ -219,7 +244,11 @@ class Room extends EventEmitter
{ {
const { id, displayName } = changedPeer; const { id, displayName } = changedPeer;
for (const peer of this._getJoinedPeers()) for (
const peer of this._getPeersWithPermission({
permission : permissionsFromRoles.PROMOTE_PEER
})
)
{ {
this._notification(peer.socket, 'lobby:changeDisplayName', { peerId: id, displayName }); this._notification(peer.socket, 'lobby:changeDisplayName', { peerId: id, displayName });
} }
@ -229,7 +258,11 @@ class Room extends EventEmitter
{ {
const { id, picture } = changedPeer; const { id, picture } = changedPeer;
for (const peer of this._getJoinedPeers()) for (
const peer of this._getPeersWithPermission({
permission : permissionsFromRoles.PROMOTE_PEER
})
)
{ {
this._notification(peer.socket, 'lobby:changePicture', { peerId: id, picture }); this._notification(peer.socket, 'lobby:changePicture', { peerId: id, picture });
} }
@ -241,7 +274,11 @@ class Room extends EventEmitter
const { id } = closedPeer; const { id } = closedPeer;
for (const peer of this._getJoinedPeers()) for (
const peer of this._getPeersWithPermission({
permission : permissionsFromRoles.PROMOTE_PEER
})
)
{ {
this._notification(peer.socket, 'lobby:peerClosed', { peerId: id }); this._notification(peer.socket, 'lobby:peerClosed', { peerId: id });
} }
@ -344,7 +381,11 @@ class Room extends EventEmitter
{ {
this._lobby.parkPeer(parkPeer); this._lobby.parkPeer(parkPeer);
for (const peer of this._getJoinedPeers()) for (
const peer of this._getPeersWithPermission({
permission : permissionsFromRoles.PROMOTE_PEER
})
)
{ {
this._notification(peer.socket, 'parkedPeer', { peerId: parkPeer.id }); this._notification(peer.socket, 'parkedPeer', { peerId: parkPeer.id });
} }
@ -384,8 +425,8 @@ class Room extends EventEmitter
{ {
params : { params : {
...config.turnAPIparams, ...config.turnAPIparams,
'api_key' : config.turnAPIKey, 'api_key' : config.turnAPIKey,
'ip' : peer.socket.request.connection.remoteAddress 'ip' : peer.socket.request.connection.remoteAddress
} }
}); });
@ -550,7 +591,7 @@ class Room extends EventEmitter
peers : peerInfos, peers : peerInfos,
tracker : config.fileTracker, tracker : config.fileTracker,
authenticated : peer.authenticated, authenticated : peer.authenticated,
permissionsFromRoles : config.permissionsFromRoles, permissionsFromRoles : permissionsFromRoles,
userRoles : userRoles userRoles : userRoles
}); });
@ -682,9 +723,10 @@ class Room extends EventEmitter
if ( if (
appData.source === 'screen' && appData.source === 'screen' &&
!peer.roles.some((role) => config.permissionsFromRoles.SHARE_SCREEN.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.SHARE_SCREEN.includes(role))
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
// Ensure the Peer is joined. // Ensure the Peer is joined.
if (!peer.joined) if (!peer.joined)
@ -985,7 +1027,7 @@ class Room extends EventEmitter
case 'chatMessage': case 'chatMessage':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.SEND_CHAT.includes(role)) !peer.roles.some((role) => permissionsFromRoles.SEND_CHAT.includes(role))
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1008,7 +1050,9 @@ class Room extends EventEmitter
case 'moderator:clearChat': case 'moderator:clearChat':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.MODERATE_CHAT.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.MODERATE_CHAT.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1046,7 +1090,9 @@ class Room extends EventEmitter
case 'lockRoom': case 'lockRoom':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1066,7 +1112,9 @@ class Room extends EventEmitter
case 'unlockRoom': case 'unlockRoom':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1126,7 +1174,9 @@ class Room extends EventEmitter
case 'promotePeer': case 'promotePeer':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.PROMOTE_PEER.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.PROMOTE_PEER.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1143,7 +1193,9 @@ class Room extends EventEmitter
case 'promoteAllPeers': case 'promoteAllPeers':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.PROMOTE_PEER.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.PROMOTE_PEER.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1158,7 +1210,9 @@ class Room extends EventEmitter
case 'sendFile': case 'sendFile':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.SHARE_FILE.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.SHARE_FILE.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1181,7 +1235,9 @@ class Room extends EventEmitter
case 'moderator:clearFileSharing': case 'moderator:clearFileSharing':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.MODERATE_FILES.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.MODERATE_FILES.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1217,7 +1273,9 @@ class Room extends EventEmitter
case 'moderator:muteAll': case 'moderator:muteAll':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.MODERATE_ROOM.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.MODERATE_ROOM.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1232,7 +1290,9 @@ class Room extends EventEmitter
case 'moderator:stopAllVideo': case 'moderator:stopAllVideo':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.MODERATE_ROOM.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.MODERATE_ROOM.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1247,7 +1307,9 @@ class Room extends EventEmitter
case 'moderator:closeMeeting': case 'moderator:closeMeeting':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.MODERATE_ROOM.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.MODERATE_ROOM.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1264,7 +1326,9 @@ class Room extends EventEmitter
case 'moderator:kickPeer': case 'moderator:kickPeer':
{ {
if ( if (
!peer.roles.some((role) => config.permissionsFromRoles.MODERATE_ROOM.includes(role)) !peer.roles.some(
(role) => permissionsFromRoles.MODERATE_ROOM.includes(role)
)
) )
throw new Error('peer not authorized'); throw new Error('peer not authorized');
@ -1452,6 +1516,19 @@ class Room extends EventEmitter
.filter((peer) => peer.joined && peer !== excludePeer); .filter((peer) => peer.joined && peer !== excludePeer);
} }
_getPeersWithPermission({ permission = null, excludePeer = undefined, joined = true })
{
return Object.values(this._peers)
.filter(
(peer) =>
peer.joined === joined &&
peer !== excludePeer &&
peer.roles.some(
(role) => permission.includes(role)
)
);
}
_timeoutCallback(callback) _timeoutCallback(callback)
{ {
let called = false; let called = false;

View File

@ -8,7 +8,8 @@
"main": "lib/index.js", "main": "lib/index.js",
"scripts": { "scripts": {
"start": "DEBUG=${DEBUG:='*mediasoup* *INFO* *WARN* *ERROR*'} INTERACTIVE=${INTERACTIVE:='true'} node server.js", "start": "DEBUG=${DEBUG:='*mediasoup* *INFO* *WARN* *ERROR*'} INTERACTIVE=${INTERACTIVE:='true'} node server.js",
"connect": "node connect.js" "connect": "node connect.js",
"lint": "eslint -c .eslintrc.json --ext .js *.js lib/"
}, },
"dependencies": { "dependencies": {
"awaitqueue": "^1.0.0", "awaitqueue": "^1.0.0",
@ -36,5 +37,8 @@
"socket.io": "^2.3.0", "socket.io": "^2.3.0",
"spdy": "^4.0.1", "spdy": "^4.0.1",
"uuid": "^7.0.2" "uuid": "^7.0.2"
},
"devDependencies": {
"eslint": "6.8.0"
} }
} }

View File

@ -203,7 +203,7 @@ function setupLTI(ltiConfig)
if (lti.user_id && lti.custom_room) if (lti.user_id && lti.custom_room)
{ {
user.id = lti.user_id; user.id = lti.user_id;
user._userinfo = { "lti" : lti }; user._userinfo = { 'lti': lti };
} }
if (lti.custom_room) if (lti.custom_room)
@ -244,7 +244,18 @@ function setupOIDC(oidcIssuer)
// redirect_uri defaults to client.redirect_uris[0] // redirect_uri defaults to client.redirect_uris[0]
// response type defaults to client.response_types[0], then 'code' // response type defaults to client.response_types[0], then 'code'
// scope defaults to 'openid' // scope defaults to 'openid'
const params = config.auth.oidc.clientOptions;
/* eslint-disable camelcase */
const params = (({
client_id,
redirect_uri,
scope
}) => ({
client_id,
redirect_uri,
scope
}))(config.auth.oidc.clientOptions);
/* eslint-enable camelcase */
// optional, defaults to false, when true req is passed as a first // optional, defaults to false, when true req is passed as a first
// argument to verify fn // argument to verify fn
@ -259,7 +270,9 @@ function setupOIDC(oidcIssuer)
{ client: oidcClient, params, passReqToCallback, usePKCE }, { client: oidcClient, params, passReqToCallback, usePKCE },
(tokenset, userinfo, done) => (tokenset, userinfo, done) =>
{ {
if (userinfo && tokenset) { if (userinfo && tokenset)
{
// eslint-disable-next-line camelcase
userinfo._tokenset_claims = tokenset.claims(); userinfo._tokenset_claims = tokenset.claims();
} }
@ -434,14 +447,14 @@ async function runHttpsServer()
// http // http
const redirectListener = http.createServer(app); const redirectListener = http.createServer(app);
if(config.listeningHost) if (config.listeningHost)
redirectListener.listen(config.listeningRedirectPort, config.listeningHost); redirectListener.listen(config.listeningRedirectPort, config.listeningHost);
else else
redirectListener.listen(config.listeningRedirectPort); redirectListener.listen(config.listeningRedirectPort);
} }
// https or http // https or http
if(config.listeningHost) if (config.listeningHost)
mainListener.listen(config.listeningPort, config.listeningHost); mainListener.listen(config.listeningPort, config.listeningHost);
else else
mainListener.listen(config.listeningPort); mainListener.listen(config.listeningPort);

View File

@ -7,5 +7,5 @@ module.exports = {
// Don't change anything after this point // Don't change anything after this point
// All users have this role by default, do not change or remove this role // All users have this role by default, do not change or remove this role
NORMAL : 'normal' NORMAL : 'normal'
}; };