Add the ability for a peer to have several video producers in a room.
parent
a0cd4416d8
commit
08e2c425c6
|
|
@ -233,6 +233,9 @@ export default class RoomClient
|
|||
// Local webcam mediasoup Producer.
|
||||
this._webcamProducer = null;
|
||||
|
||||
// Extra videos being produced
|
||||
this._extraVideoProducers = new Map();
|
||||
|
||||
// Map of webcam MediaDeviceInfos indexed by deviceId.
|
||||
// @type {Map<String, MediaDeviceInfos>}
|
||||
this._webcams = {};
|
||||
|
|
@ -3005,6 +3008,159 @@ export default class RoomClient
|
|||
}
|
||||
}
|
||||
|
||||
async addExtraVideo(videoDeviceId)
|
||||
{
|
||||
logger.debug(
|
||||
'addExtraVideo() [videoDeviceId:"%s"]',
|
||||
videoDeviceId
|
||||
);
|
||||
|
||||
store.dispatch(
|
||||
roomActions.setExtraVideoOpen(false));
|
||||
|
||||
if (!this._mediasoupDevice.canProduce('video'))
|
||||
{
|
||||
logger.error('enableWebcam() | cannot produce video');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let track;
|
||||
|
||||
store.dispatch(
|
||||
meActions.setWebcamInProgress(true));
|
||||
|
||||
try
|
||||
{
|
||||
const device = this._webcams[videoDeviceId];
|
||||
const resolution = store.getState().settings.resolution;
|
||||
|
||||
if (!device)
|
||||
throw new Error('no webcam devices');
|
||||
|
||||
logger.debug(
|
||||
'addExtraVideo() | new selected webcam [device:%o]',
|
||||
device);
|
||||
|
||||
logger.debug('_setWebcamProducer() | calling getUserMedia()');
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
{
|
||||
video :
|
||||
{
|
||||
deviceId : { ideal: videoDeviceId },
|
||||
...VIDEO_CONSTRAINS[resolution]
|
||||
}
|
||||
});
|
||||
|
||||
track = stream.getVideoTracks()[0];
|
||||
|
||||
let producer;
|
||||
|
||||
if (this._useSimulcast)
|
||||
{
|
||||
// If VP9 is the only available video codec then use SVC.
|
||||
const firstVideoCodec = this._mediasoupDevice
|
||||
.rtpCapabilities
|
||||
.codecs
|
||||
.find((c) => c.kind === 'video');
|
||||
|
||||
let encodings;
|
||||
|
||||
if (firstVideoCodec.mimeType.toLowerCase() === 'video/vp9')
|
||||
encodings = VIDEO_KSVC_ENCODINGS;
|
||||
else if ('simulcastEncodings' in window.config)
|
||||
encodings = window.config.simulcastEncodings;
|
||||
else
|
||||
encodings = VIDEO_SIMULCAST_ENCODINGS;
|
||||
|
||||
producer = await this._sendTransport.produce(
|
||||
{
|
||||
track,
|
||||
encodings,
|
||||
codecOptions :
|
||||
{
|
||||
videoGoogleStartBitrate : 1000
|
||||
},
|
||||
appData :
|
||||
{
|
||||
source : 'extravideo'
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
producer = await this._sendTransport.produce({
|
||||
track,
|
||||
appData :
|
||||
{
|
||||
source : 'extravideo'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._extraVideoProducers.set(producer.id, producer);
|
||||
|
||||
store.dispatch(producerActions.addProducer(
|
||||
{
|
||||
id : producer.id,
|
||||
deviceLabel : device.label,
|
||||
source : 'extravideo',
|
||||
paused : producer.paused,
|
||||
track : producer.track,
|
||||
rtpParameters : producer.rtpParameters,
|
||||
codec : producer.rtpParameters.codecs[0].mimeType.split('/')[1]
|
||||
}));
|
||||
|
||||
// store.dispatch(settingsActions.setSelectedWebcamDevice(deviceId));
|
||||
|
||||
await this._updateWebcams();
|
||||
|
||||
producer.on('transportclose', () =>
|
||||
{
|
||||
this._extraVideoProducers.delete(producer.id);
|
||||
|
||||
producer = null;
|
||||
});
|
||||
|
||||
producer.on('trackended', () =>
|
||||
{
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
type : 'error',
|
||||
text : intl.formatMessage({
|
||||
id : 'devices.cameraDisconnected',
|
||||
defaultMessage : 'Camera disconnected'
|
||||
})
|
||||
}));
|
||||
|
||||
this.disableExtraVideo(producer.id)
|
||||
.catch(() => {});
|
||||
});
|
||||
|
||||
logger.debug('addExtraVideo() succeeded');
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('addExtraVideo() failed:%o', error);
|
||||
|
||||
store.dispatch(requestActions.notify(
|
||||
{
|
||||
type : 'error',
|
||||
text : intl.formatMessage({
|
||||
id : 'devices.cameraError',
|
||||
defaultMessage : 'An error occurred while accessing your camera'
|
||||
})
|
||||
}));
|
||||
|
||||
if (track)
|
||||
track.stop();
|
||||
}
|
||||
|
||||
store.dispatch(
|
||||
meActions.setWebcamInProgress(false));
|
||||
}
|
||||
|
||||
async enableMic()
|
||||
{
|
||||
if (this._micProducer)
|
||||
|
|
@ -3505,6 +3661,37 @@ export default class RoomClient
|
|||
meActions.setWebcamInProgress(false));
|
||||
}
|
||||
|
||||
async disableExtraVideo(id)
|
||||
{
|
||||
logger.debug('disableExtraVideo()');
|
||||
|
||||
const producer = this._extraVideoProducers.get(id);
|
||||
|
||||
if (!producer)
|
||||
return;
|
||||
|
||||
store.dispatch(meActions.setWebcamInProgress(true));
|
||||
|
||||
producer.close();
|
||||
|
||||
store.dispatch(
|
||||
producerActions.removeProducer(id));
|
||||
|
||||
try
|
||||
{
|
||||
await this.sendRequest(
|
||||
'closeProducer', { producerId: id });
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('disableWebcam() [error:"%o"]', error);
|
||||
}
|
||||
|
||||
this._extraVideoProducers.delete(id);
|
||||
|
||||
store.dispatch(meActions.setWebcamInProgress(false));
|
||||
}
|
||||
|
||||
async disableWebcam()
|
||||
{
|
||||
logger.debug('disableWebcam()');
|
||||
|
|
|
|||
|
|
@ -58,6 +58,12 @@ export const setSettingsOpen = (settingsOpen) =>
|
|||
payload : { settingsOpen }
|
||||
});
|
||||
|
||||
export const setExtraVideoOpen = (extraVideoOpen) =>
|
||||
({
|
||||
type : 'SET_EXTRA_VIDEO_OPEN',
|
||||
payload : { extraVideoOpen }
|
||||
});
|
||||
|
||||
export const setSettingsTab = (tab) =>
|
||||
({
|
||||
type : 'SET_SETTINGS_TAB',
|
||||
|
|
|
|||
|
|
@ -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' ]),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const initialState =
|
|||
selectedPeerId : null,
|
||||
spotlights : [],
|
||||
settingsOpen : false,
|
||||
extraVideoOpen : false,
|
||||
currentSettingsTab : 'media', // media, appearence, advanced
|
||||
lockDialogOpen : false,
|
||||
joined : false,
|
||||
|
|
@ -114,6 +115,13 @@ const room = (state = initialState, action) =>
|
|||
return { ...state, settingsOpen };
|
||||
}
|
||||
|
||||
case 'SET_EXTRA_VIDEO_OPEN':
|
||||
{
|
||||
const { extraVideoOpen } = action.payload;
|
||||
|
||||
return { ...state, extraVideoOpen };
|
||||
}
|
||||
|
||||
case 'SET_SETTINGS_TAB':
|
||||
{
|
||||
const { tab } = action.payload;
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "设置",
|
||||
"settings.camera": "视频设备",
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -103,6 +104,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Nastavení",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen",
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Einstellungen",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Indstillinger",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Ρυθμίσεις",
|
||||
"settings.camera": "Κάμερα",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": "Moderator actions",
|
||||
"room.raisedHand": "{displayName} raised their hand",
|
||||
"room.loweredHand": "{displayName} put their hand down",
|
||||
"room.extraVideo": "Extra video",
|
||||
|
||||
"me.mutedPTT": "You are muted, hold down SPACE-BAR to talk",
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": "Media",
|
||||
"label.appearence": "Appearence",
|
||||
"label.advanced": "Advanced",
|
||||
"label.addVideo": "Add video",
|
||||
|
||||
"settings.settings": "Settings",
|
||||
"settings.camera": "Camera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Ajustes",
|
||||
"settings.camera": "Cámara",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Paramètres",
|
||||
"settings.camera": "Caméra",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor",
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Postavke",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Beállítások",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -103,6 +104,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Impostazioni",
|
||||
"settings.camera": "Videocamera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": "Moderatorhandlinger",
|
||||
"room.raisedHand": "{displayName} rakk opp hånden",
|
||||
"room.loweredHand": "{displayName} tok ned hånden",
|
||||
"room.extraVideo": "Ekstra video",
|
||||
|
||||
"me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke",
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": "Media",
|
||||
"label.appearence": "Utseende",
|
||||
"label.advanced": "Avansert",
|
||||
"label.addVideo": "Legg til video",
|
||||
|
||||
"settings.settings": "Innstillinger",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Ustawienia",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Definições",
|
||||
"settings.camera": "Camera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Setări",
|
||||
"settings.camera": "Cameră video",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Ayarlar",
|
||||
"settings.camera": "Kamera",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"room.moderatoractions": null,
|
||||
"room.raisedHand": null,
|
||||
"room.loweredHand": null,
|
||||
"room.extraVideo": null,
|
||||
|
||||
"me.mutedPTT": null,
|
||||
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
"label.media": null,
|
||||
"label.appearence": null,
|
||||
"label.advanced": null,
|
||||
"label.addVideo": null,
|
||||
|
||||
"settings.settings": "Налаштування",
|
||||
"settings.camera": "Камера",
|
||||
|
|
|
|||
Loading…
Reference in New Issue