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
### App config
mm/configs/app/config.js
``` js
multipartyServer : 'meet.example.com',
```
### Server config
mm/configs/server/config.js

View File

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

View File

@ -1,9 +1,9 @@
// eslint-disable-next-line
var config =
{
loginEnabled : false,
developmentPort : 3443,
productionPort : 443,
loginEnabled : false,
developmentPort : 3443,
productionPort : 443,
/**
* If defaultResolution is set, it will override user settings when joining:
@ -25,6 +25,7 @@ var config =
{ scaleResolutionDownBy: 2 },
{ scaleResolutionDownBy: 1 }
],
/**
* White listing browsers that support audio output device selection.
* It is not yet fully implemented in Firefox.
@ -41,13 +42,18 @@ var config =
{
tcp : true
},
lastN : 4,
mobileLastN : 1,
background : 'images/background.jpg',
defaultLayout : 'democratic', // democratic, filmstrip
lastN : 4,
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
// logo : 'images/logo.svg',
title : 'Multiparty meeting',
theme :
title : 'Multiparty meeting',
theme :
{
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,
transportOptions,
lastN,
mobileLastN,
defaultResolution;
mobileLastN;
if (process.env.NODE_ENV !== 'test')
{
@ -40,8 +39,7 @@ if (process.env.NODE_ENV !== 'test')
requestTimeout,
transportOptions,
lastN,
mobileLastN,
defaultResolution
mobileLastN
} = window.config);
}
@ -205,9 +203,6 @@ export default class RoomClient
// Our WebTorrent client
this._webTorrent = null;
if (defaultResolution)
store.dispatch(settingsActions.setVideoResolution(defaultResolution));
// Max spotlights
if (device.platform === 'desktop')
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');
}
@ -534,11 +529,6 @@ export default class RoomClient
}
}
notify(text)
{
store.dispatch(requestActions.notify({ text: text }));
}
timeoutCallback(callback)
{
let called = false;
@ -684,7 +674,7 @@ export default class RoomClient
{
if (err)
{
return store.dispatch(requestActions.notify(
store.dispatch(requestActions.notify(
{
type : 'error',
text : intl.formatMessage({
@ -692,6 +682,8 @@ export default class RoomClient
defaultMessage : 'Unable to save file'
})
}));
return;
}
saveAs(blob, file.name);
@ -708,7 +700,9 @@ export default class RoomClient
if (existingTorrent)
{
// Never add duplicate torrents, use the existing one instead.
return this._handleTorrent(existingTorrent);
this._handleTorrent(existingTorrent);
return;
}
this._webTorrent.add(magnetUri, this._handleTorrent);
@ -720,11 +714,13 @@ export default class RoomClient
// same file was sent multiple times.
if (torrent.progress === 1)
{
return store.dispatch(
store.dispatch(
fileActions.setFileDone(
torrent.magnetURI,
torrent.files
));
return;
}
let lastMove = 0;
@ -767,7 +763,7 @@ export default class RoomClient
{
if (err)
{
return store.dispatch(requestActions.notify(
store.dispatch(requestActions.notify(
{
type : 'error',
text : intl.formatMessage({
@ -775,13 +771,30 @@ export default class RoomClient
defaultMessage : 'Unable to share file'
})
}));
return;
}
const existingTorrent = this._webTorrent.get(torrent);
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(
@ -873,7 +886,7 @@ export default class RoomClient
store.dispatch(
lobbyPeerActions.addLobbyPeer(peer.peerId));
store.dispatch(
lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName));
lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.peerId));
store.dispatch(
lobbyPeerActions.setLobbyPeerPicture(peer.picture));
});

View File

@ -86,7 +86,7 @@ const DialogTitle = withStyles(styles)((props) =>
return (
<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>
</MuiDialogTitle>
);
@ -125,7 +125,7 @@ const ChooseRoom = ({
}}
>
<DialogTitle>
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' }
{ window.config.title ? window.config.title : 'Multiparty meeting' }
<hr />
</DialogTitle>
<DialogContent>

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../RoomContext';
import classnames from 'classnames';
import isElectron from 'is-electron';
import * as settingsActions from '../actions/settingsActions';
import PropTypes from 'prop-types';
@ -128,9 +129,9 @@ const DialogTitle = withStyles(styles)((props) =>
return (
<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>
{ window.config && window.config.loginEnabled &&
{ window.config.loginEnabled &&
<Tooltip
onClose={handleTooltipClose}
onOpen={handleTooltipOpen}
@ -147,7 +148,9 @@ const DialogTitle = withStyles(styles)((props) =>
{ myPicture ?
<Avatar src={myPicture} className={classes.largeAvatar} />
:
<AccountCircle className={classes.largeIcon} />
<AccountCircle
className={classnames(classes.largeIcon, loggedIn && classes.green)}
/>
}
</IconButton>
</Tooltip>
@ -217,11 +220,11 @@ const JoinDialog = ({
myPicture={myPicture}
onLogin={() =>
{
loggedIn ? roomClient.logout() : roomClient.login();
loggedIn ? roomClient.logout() : roomClient.login(roomId);
}}
loggedIn={loggedIn}
>
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' }
{ window.config.title ? window.config.title : 'Multiparty meeting' }
<hr />
</DialogTitle>
<DialogContent>
@ -316,6 +319,7 @@ const JoinDialog = ({
className={classes.green}
gutterBottom
variant='h6'
style={{ fontWeight: '600' }}
align='center'
>
<FormattedMessage
@ -324,7 +328,11 @@ const JoinDialog = ({
/>
</DialogContentText>
{ room.signInRequired ?
<DialogContentText gutterBottom>
<DialogContentText
gutterBottom
variant='h5'
style={{ fontWeight: '600' }}
>
<FormattedMessage
id='room.emptyRequireLogin'
defaultMessage={
@ -334,7 +342,11 @@ const JoinDialog = ({
/>
</DialogContentText>
:
<DialogContentText gutterBottom>
<DialogContentText
gutterBottom
variant='h5'
style={{ fontWeight: '600' }}
>
<FormattedMessage
id='room.locketWait'
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)
{
props.roomClient.shareFiles(event.target.files);
await props.roomClient.shareFiles(event.target.files);
}
};
@ -65,6 +65,8 @@ const FileSharing = (props) =>
type='file'
disabled={!canShare}
onChange={handleFileChange}
// Need to reset to be able to share same file twice
onClick={(e) => (e.target.value = null)}
id='share-files-button'
/>
<label htmlFor='share-files-button'>

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { micConsumerSelector } from '../Selectors';
import { passiveMicConsumerSelector } from '../Selectors';
import PropTypes from 'prop-types';
import PeerAudio from './PeerAudio';
@ -37,7 +37,7 @@ AudioPeers.propTypes =
const mapStateToProps = (state) =>
({
micConsumers : micConsumerSelector(state),
micConsumers : passiveMicConsumerSelector(state),
audioOutputDevice : state.settings.selectedAudioOutputDevice
});
@ -50,7 +50,9 @@ const AudioPeersContainer = connect(
{
return (
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')
);
export const passiveMicConsumerSelector = createSelector(
spotlightsSelector,
consumersSelect,
(spotlights, consumers) =>
Object.values(consumers).filter(
(consumer) => consumer.source === 'mic' && !spotlights.includes(consumer.peerId)
)
);
export const spotlightsLengthSelector = createSelector(
spotlightsSelector,
(spotlights) => spotlights.length

View File

@ -233,8 +233,7 @@ const Settings = ({
</FormHelperText>
</FormControl>
</form>
{
'audioOutputSupportedBrowsers' in window.config &&
{ 'audioOutputSupportedBrowsers' in window.config &&
window.config.audioOutputSupportedBrowsers.includes(me.browser.name) &&
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
@ -355,36 +354,41 @@ const Settings = ({
/>
{ settings.advancedMode &&
<React.Fragment>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={settings.lastN || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.changeMaxSpotlights(event.target.value);
}}
name='Last N'
autoWidth
className={classes.selectEmpty}
>
{ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ].map((lastN) =>
{
return (
<MenuItem key={lastN} value={lastN}>
{lastN}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.lastn'
defaultMessage='Number of visible videos'
/>
</FormHelperText>
</FormControl>
</form>
{ !window.config.lockLastN &&
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={settings.lastN || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.changeMaxSpotlights(event.target.value);
}}
name='Last N'
autoWidth
className={classes.selectEmpty}
>
{ Array.from(
{ length: window.config.maxLastN || 10 },
(_, i) => i + 1
).map((lastN) =>
{
return (
<MenuItem key={lastN} value={lastN}>
{lastN}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.lastn'
defaultMessage='Number of visible videos'
/>
</FormHelperText>
</FormControl>
</form>
}
<FormControlLabel
className={classes.setting}
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 { 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 SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff';
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 SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt';
const logger = new Logger('VideoView');
const styles = (theme) =>
({
root :
@ -134,6 +137,10 @@ class VideoView extends React.PureComponent
videoHeight : null
};
// Latest received audio track
// @type {MediaStreamTrack}
this._audioTrack = null;
// Latest received video track.
// @type {MediaStreamTrack}
this._videoTrack = null;
@ -292,7 +299,7 @@ class VideoView extends React.PureComponent
</div>
<video
ref='video'
ref='videoElement'
className={classnames(classes.video, {
hidden : !videoVisible,
'isMe' : isMe && !isScreen,
@ -300,6 +307,16 @@ class VideoView extends React.PureComponent
})}
autoPlay
playsInline
muted
controls={false}
/>
<audio
ref='audioElement'
autoPlay
playsInline
muted={isMe}
controls={false}
/>
{children}
@ -309,52 +326,84 @@ class VideoView extends React.PureComponent
componentDidMount()
{
const { videoTrack } = this.props;
const { videoTrack, audioTrack } = this.props;
this._setTracks(videoTrack);
this._setTracks(videoTrack, audioTrack);
}
componentWillUnmount()
{
clearInterval(this._videoResolutionTimer);
const { videoElement } = this.refs;
if (videoElement)
{
videoElement.oncanplay = null;
videoElement.onplay = null;
videoElement.onpause = null;
}
}
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(nextProps)
componentWillUpdate()
{
const { videoTrack } = nextProps;
this._setTracks(videoTrack);
const { videoTrack, audioTrack } = this.props;
this._setTracks(videoTrack, audioTrack);
}
_setTracks(videoTrack)
_setTracks(videoTrack, audioTrack)
{
if (this._videoTrack === videoTrack)
if (this._videoTrack === videoTrack && this._audioTrack === audioTrack)
return;
this._videoTrack = videoTrack;
this._audioTrack = audioTrack;
clearInterval(this._videoResolutionTimer);
this._hideVideoResolution();
const { video } = this.refs;
const { videoElement, audioElement } = this.refs;
if (videoTrack)
{
const stream = new MediaStream();
if (videoTrack)
stream.addTrack(videoTrack);
stream.addTrack(videoTrack);
video.srcObject = stream;
videoElement.srcObject = stream;
if (videoTrack)
this._showVideoResolution();
videoElement.oncanplay = () => this.setState({ videoCanPlay: true });
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
{
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(() =>
{
const { videoWidth, videoHeight } = this.state;
const { video } = this.refs;
const { videoElement } = this.refs;
// Don't re-render if nothing changed.
if (video.videoWidth === videoWidth && video.videoHeight === videoHeight)
if (
videoElement.videoWidth === videoWidth &&
videoElement.videoHeight === videoHeight
)
return;
this.setState(
{
videoWidth : video.videoWidth,
videoHeight : video.videoHeight
videoWidth : videoElement.videoWidth,
videoHeight : videoElement.videoHeight
});
}, 1000);
}
@ -392,6 +444,7 @@ VideoView.propTypes =
videoContain : PropTypes.bool,
advancedMode : PropTypes.bool,
videoTrack : PropTypes.any,
audioTrack : PropTypes.any,
videoVisible : PropTypes.bool.isRequired,
consumerSpatialLayers : PropTypes.number,
consumerTemporalLayers : PropTypes.number,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -55,6 +55,7 @@
"room.clearChat": null,
"room.clearFileSharing": null,
"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",
@ -69,6 +70,8 @@
"tooltip.settings": "Prikaži postavke",
"tooltip.participants": "Pokažite sudionike",
"tooltip.kickParticipant": "Izbaci sudionika",
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Naziv sobe",
"label.chooseRoomButton": "Nastavi",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -55,6 +55,7 @@
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"me.mutedPTT": null,
@ -69,6 +70,8 @@
"tooltip.settings": "Arată setăile",
"tooltip.participants": null,
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Numele camerei",
"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.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"tooltip.login": "Увійти",
"tooltip.logout": "Вихід",
@ -66,6 +67,9 @@
"tooltip.lobby": "Показати зал очікувань",
"tooltip.settings": "Показати налаштування",
"tooltip.participants": "Показати учасників",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"label.roomName": "Назва кімнати",
"label.chooseRoomButton": "Продовжити",

View File

@ -36,14 +36,14 @@ module.exports =
},
*/
// URI and key for requesting geoip-based TURN server closest to the client
turnAPIKey : 'examplekey',
turnAPIURI : 'https://example.com/api/turn',
turnAPIparams : {
'uri_schema' : 'turn',
'transport' : 'tcp',
'ip_ver' : 'ipv4',
'servercount' : '2'
},
turnAPIKey : 'examplekey',
turnAPIURI : 'https://example.com/api/turn',
turnAPIparams : {
'uri_schema' : 'turn',
'transport' : 'tcp',
'ip_ver' : 'ipv4',
'servercount' : '2'
},
// Backup turnservers if REST fails or is not configured
backupTurnServers : [
@ -60,14 +60,16 @@ module.exports =
// session cookie secret
cookieSecret : 'T0P-S3cR3t_cook!e',
cookieName : 'multiparty-meeting.sid',
// if you use encrypted private key the set the passphrase
tls :
{
cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`,
// passphrase: 'key_password'
key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem`
},
// listening Host or IP
// If omitted listens on every IP. ("0.0.0.0" and "::")
//listeningHost: 'localhost',
// listeningHost: 'localhost',
// Listening port for https server.
listeningPort : 443,
// Any http request is redirected to https.
@ -77,6 +79,12 @@ module.exports =
// listeningRedirectPort disabled
// use case: loadbalancer backend
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
// called every time there is a room created or destroyed,
// 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.
// Use this function to map your oidc userinfo to the Peer object.
// The roomId is equal to the room name.
@ -211,7 +213,7 @@ module.exports =
//
// Example:
// [ userRoles.MODERATOR, userRoles.AUTHENTICATED ]
accessFromRoles : {
accessFromRoles : {
// The role(s) will gain access to the room
// even if it is locked (!)
BYPASS_ROOM_LOCK : [ userRoles.ADMIN ],
@ -222,7 +224,7 @@ module.exports =
// function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ]
BYPASS_LOBBY : [ userRoles.NORMAL ]
},
permissionsFromRoles : {
permissionsFromRoles : {
// The role(s) have permission to lock/unlock a room
CHANGE_ROOM_LOCK : [ userRoles.NORMAL ],
// 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
// are allready users in the room
activateOnHostJoin : true,
activateOnHostJoin : true,
// Mediasoup settings
mediasoup :
mediasoup :
{
numWorkers : Object.keys(os.cpus()).length,
// mediasoup Worker settings.
@ -325,11 +327,12 @@ module.exports =
{
listenIps :
[
// change ip to your servers IP address!
{ ip: '0.0.0.0', announcedIp: null }
// change 192.0.2.1 IPv4 to your server's IPv4 address!!
{ ip: '192.0.2.1', announcedIp: null }
// 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,
minimumAvailableOutgoingBitrate : 600000,

View File

@ -9,6 +9,27 @@ const config = require('../config/config');
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
{
/**
@ -152,7 +173,7 @@ class Room extends EventEmitter
if (returning)
this._peerJoining(peer, true);
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);
else if (this._locked)
@ -160,7 +181,7 @@ class Room extends EventEmitter
else
{
// 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._handleGuest(peer);
}
@ -187,7 +208,11 @@ class Room extends EventEmitter
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 });
}
@ -196,7 +221,7 @@ class Room extends EventEmitter
this._lobby.on('peerRolesChanged', (peer) =>
{
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);
@ -206,7 +231,7 @@ class Room extends EventEmitter
if ( // Has a role that is allowed to bypass lobby
!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);
@ -219,7 +244,11 @@ class Room extends EventEmitter
{
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 });
}
@ -229,7 +258,11 @@ class Room extends EventEmitter
{
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 });
}
@ -241,7 +274,11 @@ class Room extends EventEmitter
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 });
}
@ -344,7 +381,11 @@ class Room extends EventEmitter
{
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 });
}
@ -384,8 +425,8 @@ class Room extends EventEmitter
{
params : {
...config.turnAPIparams,
'api_key' : config.turnAPIKey,
'ip' : peer.socket.request.connection.remoteAddress
'api_key' : config.turnAPIKey,
'ip' : peer.socket.request.connection.remoteAddress
}
});
@ -550,7 +591,7 @@ class Room extends EventEmitter
peers : peerInfos,
tracker : config.fileTracker,
authenticated : peer.authenticated,
permissionsFromRoles : config.permissionsFromRoles,
permissionsFromRoles : permissionsFromRoles,
userRoles : userRoles
});
@ -682,9 +723,10 @@ class Room extends EventEmitter
if (
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.
if (!peer.joined)
@ -985,7 +1027,7 @@ class Room extends EventEmitter
case 'chatMessage':
{
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');
@ -1008,7 +1050,9 @@ class Room extends EventEmitter
case 'moderator:clearChat':
{
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');
@ -1046,7 +1090,9 @@ class Room extends EventEmitter
case 'lockRoom':
{
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');
@ -1066,7 +1112,9 @@ class Room extends EventEmitter
case 'unlockRoom':
{
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');
@ -1126,7 +1174,9 @@ class Room extends EventEmitter
case 'promotePeer':
{
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');
@ -1143,7 +1193,9 @@ class Room extends EventEmitter
case 'promoteAllPeers':
{
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');
@ -1158,7 +1210,9 @@ class Room extends EventEmitter
case 'sendFile':
{
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');
@ -1181,7 +1235,9 @@ class Room extends EventEmitter
case 'moderator:clearFileSharing':
{
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');
@ -1217,7 +1273,9 @@ class Room extends EventEmitter
case 'moderator:muteAll':
{
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');
@ -1232,7 +1290,9 @@ class Room extends EventEmitter
case 'moderator:stopAllVideo':
{
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');
@ -1247,7 +1307,9 @@ class Room extends EventEmitter
case 'moderator:closeMeeting':
{
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');
@ -1264,7 +1326,9 @@ class Room extends EventEmitter
case 'moderator:kickPeer':
{
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');
@ -1452,6 +1516,19 @@ class Room extends EventEmitter
.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)
{
let called = false;

View File

@ -8,7 +8,8 @@
"main": "lib/index.js",
"scripts": {
"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": {
"awaitqueue": "^1.0.0",
@ -36,5 +37,8 @@
"socket.io": "^2.3.0",
"spdy": "^4.0.1",
"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)
{
user.id = lti.user_id;
user._userinfo = { "lti" : lti };
user._userinfo = { 'lti': lti };
}
if (lti.custom_room)
@ -244,7 +244,18 @@ function setupOIDC(oidcIssuer)
// redirect_uri defaults to client.redirect_uris[0]
// response type defaults to client.response_types[0], then 'code'
// 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
// argument to verify fn
@ -259,7 +270,9 @@ function setupOIDC(oidcIssuer)
{ client: oidcClient, params, passReqToCallback, usePKCE },
(tokenset, userinfo, done) =>
{
if (userinfo && tokenset) {
if (userinfo && tokenset)
{
// eslint-disable-next-line camelcase
userinfo._tokenset_claims = tokenset.claims();
}
@ -434,14 +447,14 @@ async function runHttpsServer()
// http
const redirectListener = http.createServer(app);
if(config.listeningHost)
if (config.listeningHost)
redirectListener.listen(config.listeningRedirectPort, config.listeningHost);
else
redirectListener.listen(config.listeningRedirectPort);
}
// https or http
if(config.listeningHost)
if (config.listeningHost)
mainListener.listen(config.listeningPort, config.listeningHost);
else
mainListener.listen(config.listeningPort);

View File

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