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.
|
// Local webcam mediasoup Producer.
|
||||||
this._webcamProducer = null;
|
this._webcamProducer = null;
|
||||||
|
|
||||||
|
// Extra videos being produced
|
||||||
|
this._extraVideoProducers = new Map();
|
||||||
|
|
||||||
// Map of webcam MediaDeviceInfos indexed by deviceId.
|
// Map of webcam MediaDeviceInfos indexed by deviceId.
|
||||||
// @type {Map<String, MediaDeviceInfos>}
|
// @type {Map<String, MediaDeviceInfos>}
|
||||||
this._webcams = {};
|
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()
|
async enableMic()
|
||||||
{
|
{
|
||||||
if (this._micProducer)
|
if (this._micProducer)
|
||||||
|
|
@ -3505,6 +3661,37 @@ export default class RoomClient
|
||||||
meActions.setWebcamInProgress(false));
|
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()
|
async disableWebcam()
|
||||||
{
|
{
|
||||||
logger.debug('disableWebcam()');
|
logger.debug('disableWebcam()');
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,12 @@ export const setSettingsOpen = (settingsOpen) =>
|
||||||
payload : { settingsOpen }
|
payload : { settingsOpen }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setExtraVideoOpen = (extraVideoOpen) =>
|
||||||
|
({
|
||||||
|
type : 'SET_EXTRA_VIDEO_OPEN',
|
||||||
|
payload : { extraVideoOpen }
|
||||||
|
});
|
||||||
|
|
||||||
export const setSettingsTab = (tab) =>
|
export const setSettingsTab = (tab) =>
|
||||||
({
|
({
|
||||||
type : 'SET_SETTINGS_TAB',
|
type : 'SET_SETTINGS_TAB',
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,7 @@ const Me = (props) =>
|
||||||
micProducer,
|
micProducer,
|
||||||
webcamProducer,
|
webcamProducer,
|
||||||
screenProducer,
|
screenProducer,
|
||||||
|
extraVideoProducers,
|
||||||
canShareScreen,
|
canShareScreen,
|
||||||
classes
|
classes
|
||||||
} = props;
|
} = props;
|
||||||
|
|
@ -467,6 +468,112 @@ const Me = (props) =>
|
||||||
</VideoView>
|
</VideoView>
|
||||||
</div>
|
</div>
|
||||||
</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 &&
|
{ screenProducer &&
|
||||||
<div
|
<div
|
||||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||||
|
|
@ -545,20 +652,21 @@ const Me = (props) =>
|
||||||
|
|
||||||
Me.propTypes =
|
Me.propTypes =
|
||||||
{
|
{
|
||||||
roomClient : PropTypes.any.isRequired,
|
roomClient : PropTypes.any.isRequired,
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
me : appPropTypes.Me.isRequired,
|
me : appPropTypes.Me.isRequired,
|
||||||
settings : PropTypes.object,
|
settings : PropTypes.object,
|
||||||
activeSpeaker : PropTypes.bool,
|
activeSpeaker : PropTypes.bool,
|
||||||
micProducer : appPropTypes.Producer,
|
micProducer : appPropTypes.Producer,
|
||||||
webcamProducer : appPropTypes.Producer,
|
webcamProducer : appPropTypes.Producer,
|
||||||
screenProducer : appPropTypes.Producer,
|
screenProducer : appPropTypes.Producer,
|
||||||
spacing : PropTypes.number,
|
extraVideoProducers : PropTypes.arrayOf(appPropTypes.Producer),
|
||||||
style : PropTypes.object,
|
spacing : PropTypes.number,
|
||||||
smallButtons : PropTypes.bool,
|
style : PropTypes.object,
|
||||||
canShareScreen : PropTypes.bool.isRequired,
|
smallButtons : PropTypes.bool,
|
||||||
classes : PropTypes.object.isRequired,
|
canShareScreen : PropTypes.bool.isRequired,
|
||||||
theme : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired,
|
||||||
|
theme : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ const Peer = (props) =>
|
||||||
micConsumer,
|
micConsumer,
|
||||||
webcamConsumer,
|
webcamConsumer,
|
||||||
screenConsumer,
|
screenConsumer,
|
||||||
|
extraVideoConsumers,
|
||||||
toggleConsumerFullscreen,
|
toggleConsumerFullscreen,
|
||||||
toggleConsumerWindow,
|
toggleConsumerWindow,
|
||||||
spacing,
|
spacing,
|
||||||
|
|
@ -351,6 +352,161 @@ const Peer = (props) =>
|
||||||
</div>
|
</div>
|
||||||
</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 &&
|
{ screenConsumer &&
|
||||||
<div
|
<div
|
||||||
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
|
||||||
|
|
@ -508,6 +664,7 @@ Peer.propTypes =
|
||||||
micConsumer : appPropTypes.Consumer,
|
micConsumer : appPropTypes.Consumer,
|
||||||
webcamConsumer : appPropTypes.Consumer,
|
webcamConsumer : appPropTypes.Consumer,
|
||||||
screenConsumer : appPropTypes.Consumer,
|
screenConsumer : appPropTypes.Consumer,
|
||||||
|
extraVideoConsumers : PropTypes.arrayOf(appPropTypes.Consumer),
|
||||||
windowConsumer : PropTypes.string,
|
windowConsumer : PropTypes.string,
|
||||||
activeSpeaker : PropTypes.bool,
|
activeSpeaker : PropTypes.bool,
|
||||||
browser : PropTypes.object.isRequired,
|
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 { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
|
|
@ -14,11 +14,14 @@ import * as toolareaActions from '../../actions/toolareaActions';
|
||||||
import { useIntl, FormattedMessage } from 'react-intl';
|
import { useIntl, FormattedMessage } from 'react-intl';
|
||||||
import AppBar from '@material-ui/core/AppBar';
|
import AppBar from '@material-ui/core/AppBar';
|
||||||
import Toolbar from '@material-ui/core/Toolbar';
|
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 Typography from '@material-ui/core/Typography';
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import MenuIcon from '@material-ui/icons/Menu';
|
import MenuIcon from '@material-ui/icons/Menu';
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
import Badge from '@material-ui/core/Badge';
|
import Badge from '@material-ui/core/Badge';
|
||||||
|
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||||
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
import FullScreenIcon from '@material-ui/icons/Fullscreen';
|
||||||
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
|
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 PeopleIcon from '@material-ui/icons/People';
|
||||||
import LockIcon from '@material-ui/icons/Lock';
|
import LockIcon from '@material-ui/icons/Lock';
|
||||||
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
import LockOpenIcon from '@material-ui/icons/LockOpen';
|
||||||
|
import VideoCallIcon from '@material-ui/icons/VideoCall';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Tooltip from '@material-ui/core/Tooltip';
|
import Tooltip from '@material-ui/core/Tooltip';
|
||||||
|
|
||||||
|
|
@ -89,6 +93,10 @@ const styles = (theme) =>
|
||||||
green :
|
green :
|
||||||
{
|
{
|
||||||
color : 'rgba(0, 153, 0, 1)'
|
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 intl = useIntl();
|
||||||
|
|
||||||
|
const [ moreActionsElement, setMoreActionsElement ] = useState(null);
|
||||||
|
|
||||||
|
const handleMoreActionsOpen = (event) =>
|
||||||
|
{
|
||||||
|
setMoreActionsElement(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMoreActionsClose = () =>
|
||||||
|
{
|
||||||
|
setMoreActionsElement(null);
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
roomClient,
|
roomClient,
|
||||||
room,
|
room,
|
||||||
|
|
@ -140,6 +160,7 @@ const TopBar = (props) =>
|
||||||
fullscreen,
|
fullscreen,
|
||||||
onFullscreen,
|
onFullscreen,
|
||||||
setSettingsOpen,
|
setSettingsOpen,
|
||||||
|
setExtraVideoOpen,
|
||||||
setLockDialogOpen,
|
setLockDialogOpen,
|
||||||
toggleToolArea,
|
toggleToolArea,
|
||||||
openUsersTab,
|
openUsersTab,
|
||||||
|
|
@ -149,6 +170,8 @@ const TopBar = (props) =>
|
||||||
classes
|
classes
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const isMoreActionsMenuOpen = Boolean(moreActionsElement);
|
||||||
|
|
||||||
const lockTooltip = room.locked ?
|
const lockTooltip = room.locked ?
|
||||||
intl.formatMessage({
|
intl.formatMessage({
|
||||||
id : 'tooltip.unLockRoom',
|
id : 'tooltip.unLockRoom',
|
||||||
|
|
@ -183,196 +206,230 @@ const TopBar = (props) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar
|
<React.Fragment>
|
||||||
position='fixed'
|
<AppBar
|
||||||
className={room.toolbarsVisible || permanentTopBar ? classes.show : classes.hide}
|
position='fixed'
|
||||||
>
|
className={room.toolbarsVisible || permanentTopBar ? classes.show : classes.hide}
|
||||||
<Toolbar>
|
>
|
||||||
<PulsingBadge
|
<Toolbar>
|
||||||
color='secondary'
|
<PulsingBadge
|
||||||
badgeContent={unread}
|
color='secondary'
|
||||||
onClick={() => toggleToolArea()}
|
badgeContent={unread}
|
||||||
>
|
onClick={() => toggleToolArea()}
|
||||||
<IconButton
|
>
|
||||||
|
<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'
|
color='inherit'
|
||||||
aria-label={intl.formatMessage({
|
noWrap
|
||||||
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'
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
|
{ window.config.title ? window.config.title : 'Multiparty meeting' }
|
||||||
|
</Typography>
|
||||||
|
<div className={classes.grow} />
|
||||||
|
<div className={classes.actionButtons}>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={intl.formatMessage({
|
aria-haspopup='true'
|
||||||
id : 'tooltip.participants',
|
onClick={handleMoreActionsOpen}
|
||||||
defaultMessage : 'Show participants'
|
|
||||||
})}
|
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={() => openUsersTab()}
|
|
||||||
>
|
>
|
||||||
<Badge
|
<ExtensionIcon />
|
||||||
color='primary'
|
|
||||||
badgeContent={peersLength + 1}
|
|
||||||
>
|
|
||||||
<PeopleIcon />
|
|
||||||
</Badge>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
{ fullscreenEnabled &&
|
||||||
<Tooltip
|
<Tooltip title={fullscreenTooltip}>
|
||||||
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}>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={intl.formatMessage({
|
aria-label={intl.formatMessage({
|
||||||
id : 'tooltip.lobby',
|
id : 'tooltip.enterFullscreen',
|
||||||
defaultMessage : 'Show lobby'
|
defaultMessage : 'Enter fullscreen'
|
||||||
})}
|
})}
|
||||||
className={classes.actionButton}
|
className={classes.actionButton}
|
||||||
color='inherit'
|
color='inherit'
|
||||||
disabled={!canPromote}
|
onClick={onFullscreen}
|
||||||
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
|
|
||||||
>
|
>
|
||||||
<PulsingBadge
|
{ fullscreen ?
|
||||||
color='secondary'
|
<FullScreenExitIcon />
|
||||||
badgeContent={lobbyPeers.length}
|
:
|
||||||
>
|
<FullScreenIcon />
|
||||||
<SecurityIcon />
|
}
|
||||||
</PulsingBadge>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</Tooltip>
|
||||||
</Tooltip>
|
}
|
||||||
}
|
<Tooltip
|
||||||
{ loginEnabled &&
|
title={intl.formatMessage({
|
||||||
<Tooltip title={loginTooltip}>
|
id : 'tooltip.participants',
|
||||||
|
defaultMessage : 'Show participants'
|
||||||
|
})}
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={intl.formatMessage({
|
aria-label={intl.formatMessage({
|
||||||
id : 'tooltip.login',
|
id : 'tooltip.participants',
|
||||||
defaultMessage : 'Log in'
|
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}
|
className={classes.actionButton}
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={() =>
|
onClick={() => setSettingsOpen(!room.settingsOpen)}
|
||||||
{
|
|
||||||
loggedIn ? roomClient.logout() : roomClient.login();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{ myPicture ?
|
<SettingsIcon />
|
||||||
<Avatar src={myPicture} />
|
|
||||||
:
|
|
||||||
<AccountCircle className={loggedIn ? classes.green : null} />
|
|
||||||
}
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
<Tooltip title={lockTooltip}>
|
||||||
<div className={classes.divider} />
|
<span className={classes.disabledButton}>
|
||||||
<Button
|
<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({
|
aria-label={intl.formatMessage({
|
||||||
id : 'label.leave',
|
id : 'tooltip.settings',
|
||||||
defaultMessage : 'Leave'
|
defaultMessage : 'Show settings'
|
||||||
})}
|
})}
|
||||||
className={classes.actionButton}
|
/>
|
||||||
variant='contained'
|
<p className={classes.moreAction}>Add video</p>
|
||||||
color='secondary'
|
</MenuItem>
|
||||||
onClick={() => roomClient.close()}
|
</Menu>
|
||||||
>
|
</React.Fragment>
|
||||||
<FormattedMessage
|
|
||||||
id='label.leave'
|
|
||||||
defaultMessage='Leave'
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -391,6 +448,7 @@ TopBar.propTypes =
|
||||||
onFullscreen : PropTypes.func.isRequired,
|
onFullscreen : PropTypes.func.isRequired,
|
||||||
setToolbarsVisible : PropTypes.func.isRequired,
|
setToolbarsVisible : PropTypes.func.isRequired,
|
||||||
setSettingsOpen : PropTypes.func.isRequired,
|
setSettingsOpen : PropTypes.func.isRequired,
|
||||||
|
setExtraVideoOpen : PropTypes.func.isRequired,
|
||||||
setLockDialogOpen : PropTypes.func.isRequired,
|
setLockDialogOpen : PropTypes.func.isRequired,
|
||||||
toggleToolArea : PropTypes.func.isRequired,
|
toggleToolArea : PropTypes.func.isRequired,
|
||||||
openUsersTab : PropTypes.func.isRequired,
|
openUsersTab : PropTypes.func.isRequired,
|
||||||
|
|
@ -430,6 +488,10 @@ const mapDispatchToProps = (dispatch) =>
|
||||||
{
|
{
|
||||||
dispatch(roomActions.setSettingsOpen(settingsOpen));
|
dispatch(roomActions.setSettingsOpen(settingsOpen));
|
||||||
},
|
},
|
||||||
|
setExtraVideoOpen : (extraVideoOpen) =>
|
||||||
|
{
|
||||||
|
dispatch(roomActions.setExtraVideoOpen(extraVideoOpen));
|
||||||
|
},
|
||||||
setLockDialogOpen : (lockDialogOpen) =>
|
setLockDialogOpen : (lockDialogOpen) =>
|
||||||
{
|
{
|
||||||
dispatch(roomActions.setLockDialogOpen(lockDialogOpen));
|
dispatch(roomActions.setLockDialogOpen(lockDialogOpen));
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import LockDialog from './AccessControl/LockDialog/LockDialog';
|
||||||
import Settings from './Settings/Settings';
|
import Settings from './Settings/Settings';
|
||||||
import TopBar from './Controls/TopBar';
|
import TopBar from './Controls/TopBar';
|
||||||
import WakeLock from 'react-wakelock-react16';
|
import WakeLock from 'react-wakelock-react16';
|
||||||
|
import ExtraVideo from './Controls/ExtraVideo';
|
||||||
|
|
||||||
const TIMEOUT = 5 * 1000;
|
const TIMEOUT = 5 * 1000;
|
||||||
|
|
||||||
|
|
@ -217,6 +218,10 @@ class Room extends React.PureComponent
|
||||||
{ room.settingsOpen &&
|
{ room.settingsOpen &&
|
||||||
<Settings />
|
<Settings />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ room.extraVideoOpen &&
|
||||||
|
<ExtraVideo />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@ export const screenProducersSelector = createSelector(
|
||||||
(producers) => Object.values(producers).filter((producer) => producer.source === 'screen')
|
(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(
|
export const micProducerSelector = createSelector(
|
||||||
producersSelect,
|
producersSelect,
|
||||||
(producers) => Object.values(producers).find((producer) => producer.source === 'mic')
|
(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')
|
(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(
|
export const passiveMicConsumerSelector = createSelector(
|
||||||
spotlightsSelector,
|
spotlightsSelector,
|
||||||
consumersSelect,
|
consumersSelect,
|
||||||
|
|
@ -114,21 +137,33 @@ export const raisedHandsSelector = createSelector(
|
||||||
export const videoBoxesSelector = createSelector(
|
export const videoBoxesSelector = createSelector(
|
||||||
spotlightsLengthSelector,
|
spotlightsLengthSelector,
|
||||||
screenProducersSelector,
|
screenProducersSelector,
|
||||||
screenConsumerSelector,
|
spotlightScreenConsumerSelector,
|
||||||
(spotlightsLength, screenProducers, screenConsumers) =>
|
extraVideoProducersSelector,
|
||||||
spotlightsLength + 1 + screenProducers.length + screenConsumers.length
|
spotlightExtraVideoConsumerSelector,
|
||||||
|
(
|
||||||
|
spotlightsLength,
|
||||||
|
screenProducers,
|
||||||
|
screenConsumers,
|
||||||
|
extraVideoProducers,
|
||||||
|
extraVideoConsumers
|
||||||
|
) =>
|
||||||
|
spotlightsLength + 1 + screenProducers.length +
|
||||||
|
screenConsumers.length + extraVideoProducers.length +
|
||||||
|
extraVideoConsumers.length
|
||||||
);
|
);
|
||||||
|
|
||||||
export const meProducersSelector = createSelector(
|
export const meProducersSelector = createSelector(
|
||||||
micProducerSelector,
|
micProducerSelector,
|
||||||
webcamProducerSelector,
|
webcamProducerSelector,
|
||||||
screenProducerSelector,
|
screenProducerSelector,
|
||||||
(micProducer, webcamProducer, screenProducer) =>
|
extraVideoProducersSelector,
|
||||||
|
(micProducer, webcamProducer, screenProducer, extraVideoProducers) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
micProducer,
|
micProducer,
|
||||||
webcamProducer,
|
webcamProducer,
|
||||||
screenProducer
|
screenProducer,
|
||||||
|
extraVideoProducers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -151,8 +186,10 @@ export const makePeerConsumerSelector = () =>
|
||||||
consumersArray.find((consumer) => consumer.source === 'webcam');
|
consumersArray.find((consumer) => consumer.source === 'webcam');
|
||||||
const screenConsumer =
|
const screenConsumer =
|
||||||
consumersArray.find((consumer) => consumer.source === 'screen');
|
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(
|
export const Producer = PropTypes.shape(
|
||||||
{
|
{
|
||||||
id : PropTypes.string.isRequired,
|
id : PropTypes.string.isRequired,
|
||||||
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen', 'extravideo' ]).isRequired,
|
||||||
deviceLabel : PropTypes.string,
|
deviceLabel : PropTypes.string,
|
||||||
type : PropTypes.oneOf([ 'front', 'back', 'screen' ]),
|
type : PropTypes.oneOf([ 'front', 'back', 'screen', 'extravideo' ]),
|
||||||
paused : PropTypes.bool.isRequired,
|
paused : PropTypes.bool.isRequired,
|
||||||
track : PropTypes.any,
|
track : PropTypes.any,
|
||||||
codec : PropTypes.string.isRequired
|
codec : PropTypes.string.isRequired
|
||||||
|
|
@ -37,7 +37,7 @@ export const Consumer = PropTypes.shape(
|
||||||
{
|
{
|
||||||
id : PropTypes.string.isRequired,
|
id : PropTypes.string.isRequired,
|
||||||
peerId : 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,
|
locallyPaused : PropTypes.bool.isRequired,
|
||||||
remotelyPaused : PropTypes.bool.isRequired,
|
remotelyPaused : PropTypes.bool.isRequired,
|
||||||
profile : PropTypes.oneOf([ 'none', 'default', 'low', 'medium', 'high' ]),
|
profile : PropTypes.oneOf([ 'none', 'default', 'low', 'medium', 'high' ]),
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ const initialState =
|
||||||
selectedPeerId : null,
|
selectedPeerId : null,
|
||||||
spotlights : [],
|
spotlights : [],
|
||||||
settingsOpen : false,
|
settingsOpen : false,
|
||||||
|
extraVideoOpen : false,
|
||||||
currentSettingsTab : 'media', // media, appearence, advanced
|
currentSettingsTab : 'media', // media, appearence, advanced
|
||||||
lockDialogOpen : false,
|
lockDialogOpen : false,
|
||||||
joined : false,
|
joined : false,
|
||||||
|
|
@ -114,6 +115,13 @@ const room = (state = initialState, action) =>
|
||||||
return { ...state, settingsOpen };
|
return { ...state, settingsOpen };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'SET_EXTRA_VIDEO_OPEN':
|
||||||
|
{
|
||||||
|
const { extraVideoOpen } = action.payload;
|
||||||
|
|
||||||
|
return { ...state, extraVideoOpen };
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_SETTINGS_TAB':
|
case 'SET_SETTINGS_TAB':
|
||||||
{
|
{
|
||||||
const { tab } = action.payload;
|
const { tab } = action.payload;
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "设置",
|
"settings.settings": "设置",
|
||||||
"settings.camera": "视频设备",
|
"settings.camera": "视频设备",
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -103,6 +104,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Nastavení",
|
"settings.settings": "Nastavení",
|
||||||
"settings.camera": "Kamera",
|
"settings.camera": "Kamera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen",
|
"me.mutedPTT": "Du bist stummgeschalted, Halte die SPACE-Taste um zu sprechen",
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Einstellungen",
|
"settings.settings": "Einstellungen",
|
||||||
"settings.camera": "Kamera",
|
"settings.camera": "Kamera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Indstillinger",
|
"settings.settings": "Indstillinger",
|
||||||
"settings.camera": "Kamera",
|
"settings.camera": "Kamera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Ρυθμίσεις",
|
"settings.settings": "Ρυθμίσεις",
|
||||||
"settings.camera": "Κάμερα",
|
"settings.camera": "Κάμερα",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": "Moderator actions",
|
"room.moderatoractions": "Moderator actions",
|
||||||
"room.raisedHand": "{displayName} raised their hand",
|
"room.raisedHand": "{displayName} raised their hand",
|
||||||
"room.loweredHand": "{displayName} put their hand down",
|
"room.loweredHand": "{displayName} put their hand down",
|
||||||
|
"room.extraVideo": "Extra video",
|
||||||
|
|
||||||
"me.mutedPTT": "You are muted, hold down SPACE-BAR to talk",
|
"me.mutedPTT": "You are muted, hold down SPACE-BAR to talk",
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": "Media",
|
"label.media": "Media",
|
||||||
"label.appearence": "Appearence",
|
"label.appearence": "Appearence",
|
||||||
"label.advanced": "Advanced",
|
"label.advanced": "Advanced",
|
||||||
|
"label.addVideo": "Add video",
|
||||||
|
|
||||||
"settings.settings": "Settings",
|
"settings.settings": "Settings",
|
||||||
"settings.camera": "Camera",
|
"settings.camera": "Camera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Ajustes",
|
"settings.settings": "Ajustes",
|
||||||
"settings.camera": "Cámara",
|
"settings.camera": "Cámara",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Paramètres",
|
"settings.settings": "Paramètres",
|
||||||
"settings.camera": "Caméra",
|
"settings.camera": "Caméra",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor",
|
"me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor",
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Postavke",
|
"settings.settings": "Postavke",
|
||||||
"settings.camera": "Kamera",
|
"settings.camera": "Kamera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Beállítások",
|
"settings.settings": "Beállítások",
|
||||||
"settings.camera": "Kamera",
|
"settings.camera": "Kamera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -103,6 +104,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Impostazioni",
|
"settings.settings": "Impostazioni",
|
||||||
"settings.camera": "Videocamera",
|
"settings.camera": "Videocamera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": "Moderatorhandlinger",
|
"room.moderatoractions": "Moderatorhandlinger",
|
||||||
"room.raisedHand": "{displayName} rakk opp hånden",
|
"room.raisedHand": "{displayName} rakk opp hånden",
|
||||||
"room.loweredHand": "{displayName} tok ned hånden",
|
"room.loweredHand": "{displayName} tok ned hånden",
|
||||||
|
"room.extraVideo": "Ekstra video",
|
||||||
|
|
||||||
"me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke",
|
"me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke",
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": "Media",
|
"label.media": "Media",
|
||||||
"label.appearence": "Utseende",
|
"label.appearence": "Utseende",
|
||||||
"label.advanced": "Avansert",
|
"label.advanced": "Avansert",
|
||||||
|
"label.addVideo": "Legg til video",
|
||||||
|
|
||||||
"settings.settings": "Innstillinger",
|
"settings.settings": "Innstillinger",
|
||||||
"settings.camera": "Kamera",
|
"settings.camera": "Kamera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Ustawienia",
|
"settings.settings": "Ustawienia",
|
||||||
"settings.camera": "Kamera",
|
"settings.camera": "Kamera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Definições",
|
"settings.settings": "Definições",
|
||||||
"settings.camera": "Camera",
|
"settings.camera": "Camera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Setări",
|
"settings.settings": "Setări",
|
||||||
"settings.camera": "Cameră video",
|
"settings.camera": "Cameră video",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Ayarlar",
|
"settings.settings": "Ayarlar",
|
||||||
"settings.camera": "Kamera",
|
"settings.camera": "Kamera",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"room.moderatoractions": null,
|
"room.moderatoractions": null,
|
||||||
"room.raisedHand": null,
|
"room.raisedHand": null,
|
||||||
"room.loweredHand": null,
|
"room.loweredHand": null,
|
||||||
|
"room.extraVideo": null,
|
||||||
|
|
||||||
"me.mutedPTT": null,
|
"me.mutedPTT": null,
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@
|
||||||
"label.media": null,
|
"label.media": null,
|
||||||
"label.appearence": null,
|
"label.appearence": null,
|
||||||
"label.advanced": null,
|
"label.advanced": null,
|
||||||
|
"label.addVideo": null,
|
||||||
|
|
||||||
"settings.settings": "Налаштування",
|
"settings.settings": "Налаштування",
|
||||||
"settings.camera": "Камера",
|
"settings.camera": "Камера",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue