Add the ability for a peer to have several video producers in a room.
This commit is contained in:
@@ -150,6 +150,7 @@ const Me = (props) =>
|
||||
micProducer,
|
||||
webcamProducer,
|
||||
screenProducer,
|
||||
extraVideoProducers,
|
||||
canShareScreen,
|
||||
classes
|
||||
} = props;
|
||||
@@ -467,6 +468,112 @@ const Me = (props) =>
|
||||
</VideoView>
|
||||
</div>
|
||||
</div>
|
||||
{ extraVideoProducers.map((producer) =>
|
||||
{
|
||||
return (
|
||||
<div key={producer.id}
|
||||
className={
|
||||
classnames(
|
||||
classes.root,
|
||||
'webcam',
|
||||
hover ? 'hover' : null,
|
||||
activeSpeaker ? 'active-speaker' : null
|
||||
)
|
||||
}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
{
|
||||
if (touchTimeout)
|
||||
clearTimeout(touchTimeout);
|
||||
|
||||
setHover(true);
|
||||
}}
|
||||
onTouchEnd={() =>
|
||||
{
|
||||
if (touchTimeout)
|
||||
clearTimeout(touchTimeout);
|
||||
|
||||
touchTimeout = setTimeout(() =>
|
||||
{
|
||||
setHover(false);
|
||||
}, 2000);
|
||||
}}
|
||||
style={spacingStyle}
|
||||
>
|
||||
<div className={classes.viewContainer} style={style}>
|
||||
<div
|
||||
className={classnames(
|
||||
classes.controls,
|
||||
settings.hiddenControls ? 'hide' : null,
|
||||
hover ? 'hover' : null
|
||||
)}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
{
|
||||
if (touchTimeout)
|
||||
clearTimeout(touchTimeout);
|
||||
|
||||
setHover(true);
|
||||
}}
|
||||
onTouchEnd={() =>
|
||||
{
|
||||
if (touchTimeout)
|
||||
clearTimeout(touchTimeout);
|
||||
|
||||
touchTimeout = setTimeout(() =>
|
||||
{
|
||||
setHover(false);
|
||||
}, 2000);
|
||||
}}
|
||||
>
|
||||
<p className={hover ? 'hover' : null}>
|
||||
<FormattedMessage
|
||||
id='room.me'
|
||||
defaultMessage='ME'
|
||||
/>
|
||||
</p>
|
||||
|
||||
<Tooltip title={webcamTip} placement='left'>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'device.stopVideo',
|
||||
defaultMessage : 'Stop video'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!me.canSendWebcam || me.webcamInProgress}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
roomClient.disableExtraVideo(producer.id);
|
||||
}}
|
||||
>
|
||||
<VideoIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<VideoView
|
||||
isMe
|
||||
advancedMode={advancedMode}
|
||||
peer={me}
|
||||
displayName={settings.displayName}
|
||||
showPeerInfo
|
||||
videoTrack={producer && producer.track}
|
||||
videoVisible={videoVisible}
|
||||
videoCodec={producer && producer.codec}
|
||||
onChangeDisplayName={(displayName) =>
|
||||
{
|
||||
roomClient.changeDisplayName(displayName);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{ screenProducer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||
@@ -545,20 +652,21 @@ const Me = (props) =>
|
||||
|
||||
Me.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object,
|
||||
activeSpeaker : PropTypes.bool,
|
||||
micProducer : appPropTypes.Producer,
|
||||
webcamProducer : appPropTypes.Producer,
|
||||
screenProducer : appPropTypes.Producer,
|
||||
spacing : PropTypes.number,
|
||||
style : PropTypes.object,
|
||||
smallButtons : PropTypes.bool,
|
||||
canShareScreen : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
roomClient : PropTypes.any.isRequired,
|
||||
advancedMode : PropTypes.bool,
|
||||
me : appPropTypes.Me.isRequired,
|
||||
settings : PropTypes.object,
|
||||
activeSpeaker : PropTypes.bool,
|
||||
micProducer : appPropTypes.Producer,
|
||||
webcamProducer : appPropTypes.Producer,
|
||||
screenProducer : appPropTypes.Producer,
|
||||
extraVideoProducers : PropTypes.arrayOf(appPropTypes.Producer),
|
||||
spacing : PropTypes.number,
|
||||
style : PropTypes.object,
|
||||
smallButtons : PropTypes.bool,
|
||||
canShareScreen : PropTypes.bool.isRequired,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
|
||||
@@ -125,6 +125,7 @@ const Peer = (props) =>
|
||||
micConsumer,
|
||||
webcamConsumer,
|
||||
screenConsumer,
|
||||
extraVideoConsumers,
|
||||
toggleConsumerFullscreen,
|
||||
toggleConsumerWindow,
|
||||
spacing,
|
||||
@@ -351,6 +352,161 @@ const Peer = (props) =>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ extraVideoConsumers.map((consumer) =>
|
||||
{
|
||||
return (
|
||||
<div key={consumer.id}
|
||||
className={
|
||||
classnames(
|
||||
classes.root,
|
||||
'webcam',
|
||||
hover ? 'hover' : null,
|
||||
activeSpeaker ? 'active-speaker' : null
|
||||
)
|
||||
}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
{
|
||||
if (touchTimeout)
|
||||
clearTimeout(touchTimeout);
|
||||
|
||||
setHover(true);
|
||||
}}
|
||||
onTouchEnd={() =>
|
||||
{
|
||||
if (touchTimeout)
|
||||
clearTimeout(touchTimeout);
|
||||
|
||||
touchTimeout = setTimeout(() =>
|
||||
{
|
||||
setHover(false);
|
||||
}, 2000);
|
||||
}}
|
||||
style={rootStyle}
|
||||
>
|
||||
<div className={classnames(classes.viewContainer)}>
|
||||
{ !videoVisible &&
|
||||
<div className={classes.videoInfo}>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='room.videoPaused'
|
||||
defaultMessage='This video is paused'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div
|
||||
className={classnames(classes.controls, hover ? 'hover' : null)}
|
||||
onMouseOver={() => setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onTouchStart={() =>
|
||||
{
|
||||
if (touchTimeout)
|
||||
clearTimeout(touchTimeout);
|
||||
|
||||
setHover(true);
|
||||
}}
|
||||
onTouchEnd={() =>
|
||||
{
|
||||
if (touchTimeout)
|
||||
clearTimeout(touchTimeout);
|
||||
|
||||
touchTimeout = setTimeout(() =>
|
||||
{
|
||||
setHover(false);
|
||||
}, 2000);
|
||||
}}
|
||||
>
|
||||
{ browser.platform !== 'mobile' &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
defaultMessage : 'New window'
|
||||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.newWindow',
|
||||
defaultMessage : 'New window'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={
|
||||
!videoVisible ||
|
||||
(windowConsumer === consumer.id)
|
||||
}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerWindow(consumer);
|
||||
}}
|
||||
>
|
||||
<NewWindowIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
defaultMessage : 'Fullscreen'
|
||||
})}
|
||||
placement={smallScreen ? 'top' : 'left'}
|
||||
>
|
||||
<div>
|
||||
<Fab
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.fullscreen',
|
||||
defaultMessage : 'Fullscreen'
|
||||
})}
|
||||
className={classes.fab}
|
||||
disabled={!videoVisible}
|
||||
size={smallButtons ? 'small' : 'large'}
|
||||
onClick={() =>
|
||||
{
|
||||
toggleConsumerFullscreen(consumer);
|
||||
}}
|
||||
>
|
||||
<FullScreenIcon />
|
||||
</Fab>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<VideoView
|
||||
advancedMode={advancedMode}
|
||||
peer={peer}
|
||||
displayName={peer.displayName}
|
||||
showPeerInfo
|
||||
consumerSpatialLayers={consumer ? consumer.spatialLayers : null}
|
||||
consumerTemporalLayers={consumer ? consumer.temporalLayers : null}
|
||||
consumerCurrentSpatialLayer={
|
||||
consumer ? consumer.currentSpatialLayer : null
|
||||
}
|
||||
consumerCurrentTemporalLayer={
|
||||
consumer ? consumer.currentTemporalLayer : null
|
||||
}
|
||||
consumerPreferredSpatialLayer={
|
||||
consumer ? consumer.preferredSpatialLayer : null
|
||||
}
|
||||
consumerPreferredTemporalLayer={
|
||||
consumer ? consumer.preferredTemporalLayer : null
|
||||
}
|
||||
videoMultiLayer={consumer && consumer.type !== 'simple'}
|
||||
videoTrack={consumer && consumer.track}
|
||||
videoVisible={videoVisible}
|
||||
videoCodec={consumer && consumer.codec}
|
||||
videoScore={consumer ? consumer.score : null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{ screenConsumer &&
|
||||
<div
|
||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||
@@ -508,6 +664,7 @@ Peer.propTypes =
|
||||
micConsumer : appPropTypes.Consumer,
|
||||
webcamConsumer : appPropTypes.Consumer,
|
||||
screenConsumer : appPropTypes.Consumer,
|
||||
extraVideoConsumers : PropTypes.arrayOf(appPropTypes.Consumer),
|
||||
windowConsumer : PropTypes.string,
|
||||
activeSpeaker : PropTypes.bool,
|
||||
browser : PropTypes.object.isRequired,
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withRoomContext } from '../../RoomContext';
|
||||
import * as roomActions from '../../actions/roomActions';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Select from '@material-ui/core/Select';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
dialogPaper :
|
||||
{
|
||||
width : '30vw',
|
||||
[theme.breakpoints.down('lg')] :
|
||||
{
|
||||
width : '40vw'
|
||||
},
|
||||
[theme.breakpoints.down('md')] :
|
||||
{
|
||||
width : '50vw'
|
||||
},
|
||||
[theme.breakpoints.down('sm')] :
|
||||
{
|
||||
width : '70vw'
|
||||
},
|
||||
[theme.breakpoints.down('xs')] :
|
||||
{
|
||||
width : '90vw'
|
||||
}
|
||||
},
|
||||
setting :
|
||||
{
|
||||
padding : theme.spacing(2)
|
||||
},
|
||||
formControl :
|
||||
{
|
||||
display : 'flex'
|
||||
}
|
||||
});
|
||||
|
||||
const ExtraVideo = ({
|
||||
roomClient,
|
||||
extraVideoOpen,
|
||||
webcamDevices,
|
||||
handleCloseExtraVideo,
|
||||
classes
|
||||
}) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const [ videoDevice, setVideoDevice ] = React.useState('');
|
||||
|
||||
const handleChange = (event) =>
|
||||
{
|
||||
setVideoDevice(event.target.value);
|
||||
};
|
||||
|
||||
let videoDevices;
|
||||
|
||||
if (webcamDevices)
|
||||
videoDevices = Object.values(webcamDevices);
|
||||
else
|
||||
videoDevices = [];
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={extraVideoOpen}
|
||||
onClose={() => handleCloseExtraVideo(false)}
|
||||
classes={{
|
||||
paper : classes.dialogPaper
|
||||
}}
|
||||
>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
<FormattedMessage
|
||||
id='room.extraVideo'
|
||||
defaultMessage='Extra video'
|
||||
/>
|
||||
</DialogTitle>
|
||||
<form className={classes.setting} autoComplete='off'>
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={videoDevice}
|
||||
displayEmpty
|
||||
name={intl.formatMessage({
|
||||
id : 'settings.camera',
|
||||
defaultMessage : 'Camera'
|
||||
})}
|
||||
autoWidth
|
||||
className={classes.selectEmpty}
|
||||
disabled={videoDevices.length === 0}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{ videoDevices.map((webcam, index) =>
|
||||
{
|
||||
return (
|
||||
<MenuItem key={index} value={webcam.deviceId}>{webcam.label}</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
<FormHelperText>
|
||||
{ videoDevices.length > 0 ?
|
||||
intl.formatMessage({
|
||||
id : 'settings.selectCamera',
|
||||
defaultMessage : 'Select video device'
|
||||
})
|
||||
:
|
||||
intl.formatMessage({
|
||||
id : 'settings.cantSelectCamera',
|
||||
defaultMessage : 'Unable to select video device'
|
||||
})
|
||||
}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</form>
|
||||
<DialogActions>
|
||||
<Button onClick={() => roomClient.addExtraVideo(videoDevice)} color='primary'>
|
||||
<FormattedMessage
|
||||
id='label.addVideo'
|
||||
defaultMessage='Add video'
|
||||
/>
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
ExtraVideo.propTypes =
|
||||
{
|
||||
roomClient : PropTypes.object.isRequired,
|
||||
extraVideoOpen : PropTypes.bool.isRequired,
|
||||
webcamDevices : PropTypes.object,
|
||||
handleCloseExtraVideo : PropTypes.func.isRequired,
|
||||
classes : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) =>
|
||||
({
|
||||
webcamDevices : state.me.webcamDevices,
|
||||
extraVideoOpen : state.room.extraVideoOpen
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
handleCloseExtraVideo : roomActions.setExtraVideoOpen
|
||||
};
|
||||
|
||||
export default withRoomContext(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
null,
|
||||
{
|
||||
areStatesEqual : (next, prev) =>
|
||||
{
|
||||
return (
|
||||
prev.me.webcamDevices === next.me.webcamDevices &&
|
||||
prev.room.extraVideoOpen === next.room.extraVideoOpen
|
||||
);
|
||||
}
|
||||
}
|
||||
)(withStyles(styles)(ExtraVideo)));
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
@@ -14,11 +14,14 @@ import * as toolareaActions from '../../actions/toolareaActions';
|
||||
import { useIntl, FormattedMessage } from 'react-intl';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import Menu from '@material-ui/core/Menu';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Badge from '@material-ui/core/Badge';
|
||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
||||
@@ -27,6 +30,7 @@ import SecurityIcon from '@material-ui/icons/Security';
|
||||
import PeopleIcon from '@material-ui/icons/People';
|
||||
import LockIcon from '@material-ui/icons/Lock';
|
||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||
import VideoCallIcon from '@material-ui/icons/VideoCall';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
@@ -89,6 +93,10 @@ const styles = (theme) =>
|
||||
green :
|
||||
{
|
||||
color : 'rgba(0, 153, 0, 1)'
|
||||
},
|
||||
moreAction :
|
||||
{
|
||||
margin : theme.spacing(0, 0, 0, 1)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -127,6 +135,18 @@ const TopBar = (props) =>
|
||||
{
|
||||
const intl = useIntl();
|
||||
|
||||
const [ moreActionsElement, setMoreActionsElement ] = useState(null);
|
||||
|
||||
const handleMoreActionsOpen = (event) =>
|
||||
{
|
||||
setMoreActionsElement(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMoreActionsClose = () =>
|
||||
{
|
||||
setMoreActionsElement(null);
|
||||
};
|
||||
|
||||
const {
|
||||
roomClient,
|
||||
room,
|
||||
@@ -140,6 +160,7 @@ const TopBar = (props) =>
|
||||
fullscreen,
|
||||
onFullscreen,
|
||||
setSettingsOpen,
|
||||
setExtraVideoOpen,
|
||||
setLockDialogOpen,
|
||||
toggleToolArea,
|
||||
openUsersTab,
|
||||
@@ -149,6 +170,8 @@ const TopBar = (props) =>
|
||||
classes
|
||||
} = props;
|
||||
|
||||
const isMoreActionsMenuOpen = Boolean(moreActionsElement);
|
||||
|
||||
const lockTooltip = room.locked ?
|
||||
intl.formatMessage({
|
||||
id : 'tooltip.unLockRoom',
|
||||
@@ -183,196 +206,230 @@ const TopBar = (props) =>
|
||||
});
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
position='fixed'
|
||||
className={room.toolbarsVisible || permanentTopBar ? classes.show : classes.hide}
|
||||
>
|
||||
<Toolbar>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={unread}
|
||||
onClick={() => toggleToolArea()}
|
||||
>
|
||||
<IconButton
|
||||
<React.Fragment>
|
||||
<AppBar
|
||||
position='fixed'
|
||||
className={room.toolbarsVisible || permanentTopBar ? classes.show : classes.hide}
|
||||
>
|
||||
<Toolbar>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={unread}
|
||||
onClick={() => toggleToolArea()}
|
||||
>
|
||||
<IconButton
|
||||
color='inherit'
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.openDrawer',
|
||||
defaultMessage : 'Open drawer'
|
||||
})}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</PulsingBadge>
|
||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||
<Typography
|
||||
className={classes.title}
|
||||
variant='h6'
|
||||
color='inherit'
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.openDrawer',
|
||||
defaultMessage : 'Open drawer'
|
||||
})}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</PulsingBadge>
|
||||
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
|
||||
<Typography
|
||||
className={classes.title}
|
||||
variant='h6'
|
||||
color='inherit'
|
||||
noWrap
|
||||
>
|
||||
{ window.config.title ? window.config.title : 'Multiparty meeting' }
|
||||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div className={classes.actionButtons}>
|
||||
{ fullscreenEnabled &&
|
||||
<Tooltip title={fullscreenTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.enterFullscreen',
|
||||
defaultMessage : 'Enter fullscreen'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={onFullscreen}
|
||||
>
|
||||
{ fullscreen ?
|
||||
<FullScreenExitIcon />
|
||||
:
|
||||
<FullScreenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.participants',
|
||||
defaultMessage : 'Show participants'
|
||||
})}
|
||||
noWrap
|
||||
>
|
||||
{ window.config.title ? window.config.title : 'Multiparty meeting' }
|
||||
</Typography>
|
||||
<div className={classes.grow} />
|
||||
<div className={classes.actionButtons}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.participants',
|
||||
defaultMessage : 'Show participants'
|
||||
})}
|
||||
aria-haspopup='true'
|
||||
onClick={handleMoreActionsOpen}
|
||||
color='inherit'
|
||||
onClick={() => openUsersTab()}
|
||||
>
|
||||
<Badge
|
||||
color='primary'
|
||||
badgeContent={peersLength + 1}
|
||||
>
|
||||
<PeopleIcon />
|
||||
</Badge>
|
||||
<ExtensionIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() => setSettingsOpen(!room.settingsOpen)}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={lockTooltip}>
|
||||
<span className={classes.disabledButton}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lockRoom',
|
||||
defaultMessage : 'Lock room'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
disabled={!canLock}
|
||||
onClick={() =>
|
||||
{
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ room.locked ?
|
||||
<LockIcon />
|
||||
:
|
||||
<LockOpenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{ lobbyPeers.length > 0 &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
})}
|
||||
>
|
||||
<span className={classes.disabledButton}>
|
||||
{ fullscreenEnabled &&
|
||||
<Tooltip title={fullscreenTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
id : 'tooltip.enterFullscreen',
|
||||
defaultMessage : 'Enter fullscreen'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
disabled={!canPromote}
|
||||
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
|
||||
onClick={onFullscreen}
|
||||
>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={lobbyPeers.length}
|
||||
>
|
||||
<SecurityIcon />
|
||||
</PulsingBadge>
|
||||
{ fullscreen ?
|
||||
<FullScreenExitIcon />
|
||||
:
|
||||
<FullScreenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
{ loginEnabled &&
|
||||
<Tooltip title={loginTooltip}>
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.participants',
|
||||
defaultMessage : 'Show participants'
|
||||
})}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.login',
|
||||
defaultMessage : 'Log in'
|
||||
id : 'tooltip.participants',
|
||||
defaultMessage : 'Show participants'
|
||||
})}
|
||||
color='inherit'
|
||||
onClick={() => openUsersTab()}
|
||||
>
|
||||
<Badge
|
||||
color='primary'
|
||||
badgeContent={peersLength + 1}
|
||||
>
|
||||
<PeopleIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() =>
|
||||
{
|
||||
loggedIn ? roomClient.logout() : roomClient.login();
|
||||
}}
|
||||
onClick={() => setSettingsOpen(!room.settingsOpen)}
|
||||
>
|
||||
{ myPicture ?
|
||||
<Avatar src={myPicture} />
|
||||
:
|
||||
<AccountCircle className={loggedIn ? classes.green : null} />
|
||||
}
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<div className={classes.divider} />
|
||||
<Button
|
||||
<Tooltip title={lockTooltip}>
|
||||
<span className={classes.disabledButton}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lockRoom',
|
||||
defaultMessage : 'Lock room'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
disabled={!canLock}
|
||||
onClick={() =>
|
||||
{
|
||||
if (room.locked)
|
||||
{
|
||||
roomClient.unlockRoom();
|
||||
}
|
||||
else
|
||||
{
|
||||
roomClient.lockRoom();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{ room.locked ?
|
||||
<LockIcon />
|
||||
:
|
||||
<LockOpenIcon />
|
||||
}
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{ lobbyPeers.length > 0 &&
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
})}
|
||||
>
|
||||
<span className={classes.disabledButton}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.lobby',
|
||||
defaultMessage : 'Show lobby'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
disabled={!canPromote}
|
||||
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
|
||||
>
|
||||
<PulsingBadge
|
||||
color='secondary'
|
||||
badgeContent={lobbyPeers.length}
|
||||
>
|
||||
<SecurityIcon />
|
||||
</PulsingBadge>
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
{ loginEnabled &&
|
||||
<Tooltip title={loginTooltip}>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'tooltip.login',
|
||||
defaultMessage : 'Log in'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
color='inherit'
|
||||
onClick={() =>
|
||||
{
|
||||
loggedIn ? roomClient.logout() : roomClient.login();
|
||||
}}
|
||||
>
|
||||
{ myPicture ?
|
||||
<Avatar src={myPicture} />
|
||||
:
|
||||
<AccountCircle className={loggedIn ? classes.green : null} />
|
||||
}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<div className={classes.divider} />
|
||||
<Button
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.leave',
|
||||
defaultMessage : 'Leave'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
onClick={() => roomClient.close()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='label.leave'
|
||||
defaultMessage='Leave'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Menu
|
||||
anchorEl={moreActionsElement}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||
open={isMoreActionsMenuOpen}
|
||||
onClose={handleMoreActionsClose}
|
||||
getContentAnchorEl={null}
|
||||
>
|
||||
<MenuItem
|
||||
dense
|
||||
onClick={() =>
|
||||
{
|
||||
handleMoreActionsClose();
|
||||
setExtraVideoOpen(!room.extraVideoOpen);
|
||||
}}
|
||||
>
|
||||
<VideoCallIcon
|
||||
aria-label={intl.formatMessage({
|
||||
id : 'label.leave',
|
||||
defaultMessage : 'Leave'
|
||||
id : 'tooltip.settings',
|
||||
defaultMessage : 'Show settings'
|
||||
})}
|
||||
className={classes.actionButton}
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
onClick={() => roomClient.close()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='label.leave'
|
||||
defaultMessage='Leave'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
/>
|
||||
<p className={classes.moreAction}>Add video</p>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -391,6 +448,7 @@ TopBar.propTypes =
|
||||
onFullscreen : PropTypes.func.isRequired,
|
||||
setToolbarsVisible : PropTypes.func.isRequired,
|
||||
setSettingsOpen : PropTypes.func.isRequired,
|
||||
setExtraVideoOpen : PropTypes.func.isRequired,
|
||||
setLockDialogOpen : PropTypes.func.isRequired,
|
||||
toggleToolArea : PropTypes.func.isRequired,
|
||||
openUsersTab : PropTypes.func.isRequired,
|
||||
@@ -430,6 +488,10 @@ const mapDispatchToProps = (dispatch) =>
|
||||
{
|
||||
dispatch(roomActions.setSettingsOpen(settingsOpen));
|
||||
},
|
||||
setExtraVideoOpen : (extraVideoOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setExtraVideoOpen(extraVideoOpen));
|
||||
},
|
||||
setLockDialogOpen : (lockDialogOpen) =>
|
||||
{
|
||||
dispatch(roomActions.setLockDialogOpen(lockDialogOpen));
|
||||
|
||||
@@ -24,6 +24,7 @@ import LockDialog from './AccessControl/LockDialog/LockDialog';
|
||||
import Settings from './Settings/Settings';
|
||||
import TopBar from './Controls/TopBar';
|
||||
import WakeLock from 'react-wakelock-react16';
|
||||
import ExtraVideo from './Controls/ExtraVideo';
|
||||
|
||||
const TIMEOUT = 5 * 1000;
|
||||
|
||||
@@ -217,6 +218,10 @@ class Room extends React.PureComponent
|
||||
{ room.settingsOpen &&
|
||||
<Settings />
|
||||
}
|
||||
|
||||
{ room.extraVideoOpen &&
|
||||
<ExtraVideo />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ export const screenProducersSelector = createSelector(
|
||||
(producers) => Object.values(producers).filter((producer) => producer.source === 'screen')
|
||||
);
|
||||
|
||||
export const extraVideoProducersSelector = createSelector(
|
||||
producersSelect,
|
||||
(producers) => Object.values(producers).filter((producer) => producer.source === 'extravideo')
|
||||
);
|
||||
|
||||
export const micProducerSelector = createSelector(
|
||||
producersSelect,
|
||||
(producers) => Object.values(producers).find((producer) => producer.source === 'mic')
|
||||
@@ -67,6 +72,24 @@ export const screenConsumerSelector = createSelector(
|
||||
(consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen')
|
||||
);
|
||||
|
||||
export const spotlightScreenConsumerSelector = createSelector(
|
||||
spotlightsSelector,
|
||||
consumersSelect,
|
||||
(spotlights, consumers) =>
|
||||
Object.values(consumers).filter(
|
||||
(consumer) => consumer.source === 'screen' && spotlights.includes(consumer.peerId)
|
||||
)
|
||||
);
|
||||
|
||||
export const spotlightExtraVideoConsumerSelector = createSelector(
|
||||
spotlightsSelector,
|
||||
consumersSelect,
|
||||
(spotlights, consumers) =>
|
||||
Object.values(consumers).filter(
|
||||
(consumer) => consumer.source === 'extravideo' && spotlights.includes(consumer.peerId)
|
||||
)
|
||||
);
|
||||
|
||||
export const passiveMicConsumerSelector = createSelector(
|
||||
spotlightsSelector,
|
||||
consumersSelect,
|
||||
@@ -114,21 +137,33 @@ export const raisedHandsSelector = createSelector(
|
||||
export const videoBoxesSelector = createSelector(
|
||||
spotlightsLengthSelector,
|
||||
screenProducersSelector,
|
||||
screenConsumerSelector,
|
||||
(spotlightsLength, screenProducers, screenConsumers) =>
|
||||
spotlightsLength + 1 + screenProducers.length + screenConsumers.length
|
||||
spotlightScreenConsumerSelector,
|
||||
extraVideoProducersSelector,
|
||||
spotlightExtraVideoConsumerSelector,
|
||||
(
|
||||
spotlightsLength,
|
||||
screenProducers,
|
||||
screenConsumers,
|
||||
extraVideoProducers,
|
||||
extraVideoConsumers
|
||||
) =>
|
||||
spotlightsLength + 1 + screenProducers.length +
|
||||
screenConsumers.length + extraVideoProducers.length +
|
||||
extraVideoConsumers.length
|
||||
);
|
||||
|
||||
export const meProducersSelector = createSelector(
|
||||
micProducerSelector,
|
||||
webcamProducerSelector,
|
||||
screenProducerSelector,
|
||||
(micProducer, webcamProducer, screenProducer) =>
|
||||
extraVideoProducersSelector,
|
||||
(micProducer, webcamProducer, screenProducer, extraVideoProducers) =>
|
||||
{
|
||||
return {
|
||||
micProducer,
|
||||
webcamProducer,
|
||||
screenProducer
|
||||
screenProducer,
|
||||
extraVideoProducers
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -151,8 +186,10 @@ export const makePeerConsumerSelector = () =>
|
||||
consumersArray.find((consumer) => consumer.source === 'webcam');
|
||||
const screenConsumer =
|
||||
consumersArray.find((consumer) => consumer.source === 'screen');
|
||||
const extraVideoConsumers =
|
||||
consumersArray.filter((consumer) => consumer.source === 'extravideo');
|
||||
|
||||
return { micConsumer, webcamConsumer, screenConsumer };
|
||||
return { micConsumer, webcamConsumer, screenConsumer, extraVideoConsumers };
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,9 +18,9 @@ export const Me = PropTypes.shape(
|
||||
export const Producer = PropTypes.shape(
|
||||
{
|
||||
id : PropTypes.string.isRequired,
|
||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen', 'extravideo' ]).isRequired,
|
||||
deviceLabel : PropTypes.string,
|
||||
type : PropTypes.oneOf([ 'front', 'back', 'screen' ]),
|
||||
type : PropTypes.oneOf([ 'front', 'back', 'screen', 'extravideo' ]),
|
||||
paused : PropTypes.bool.isRequired,
|
||||
track : PropTypes.any,
|
||||
codec : PropTypes.string.isRequired
|
||||
@@ -37,7 +37,7 @@ export const Consumer = PropTypes.shape(
|
||||
{
|
||||
id : PropTypes.string.isRequired,
|
||||
peerId : PropTypes.string.isRequired,
|
||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen', 'extravideo' ]).isRequired,
|
||||
locallyPaused : PropTypes.bool.isRequired,
|
||||
remotelyPaused : PropTypes.bool.isRequired,
|
||||
profile : PropTypes.oneOf([ 'none', 'default', 'low', 'medium', 'high' ]),
|
||||
|
||||
Reference in New Issue
Block a user