Add the ability for a peer to have several video producers in a room.

This commit is contained in:
Håvar Aambø Fosstveit
2020-05-04 00:16:23 +02:00
parent a0cd4416d8
commit 08e2c425c6
27 changed files with 962 additions and 191 deletions
+122 -14
View File
@@ -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) =>
+157
View File
@@ -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,
+167
View File
@@ -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)));
+230 -168
View File
@@ -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));
+5
View File
@@ -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>
);
}
+43 -6
View File
@@ -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 };
}
);
};
+3 -3
View File
@@ -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' ]),