Merge branch 'feature-mute-participants' into develop
commit
668fa0f37f
|
|
@ -523,6 +523,150 @@ export default class RoomClient
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutePeerAudio(peerName)
|
||||||
|
{
|
||||||
|
logger.debug('mutePeerAudio() [peerName:"%s"]', peerName);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerAudioInProgress(peerName, true));
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
for (const peer of this._room.peers)
|
||||||
|
{
|
||||||
|
if (peer.name === peerName)
|
||||||
|
{
|
||||||
|
for (const consumer of peer.consumers)
|
||||||
|
{
|
||||||
|
if (consumer.kind !== 'audio')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
consumer.pause('mute-audio');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerAudioInProgress(peerName, false));
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
{
|
||||||
|
logger.error('mutePeerAudio() failed: %o', error);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerAudioInProgress(peerName, false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unmutePeerAudio(peerName)
|
||||||
|
{
|
||||||
|
logger.debug('unmutePeerAudio() [peerName:"%s"]', peerName);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerAudioInProgress(peerName, true));
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
for (const peer of this._room.peers)
|
||||||
|
{
|
||||||
|
if (peer.name === peerName)
|
||||||
|
{
|
||||||
|
for (const consumer of peer.consumers)
|
||||||
|
{
|
||||||
|
if (consumer.kind !== 'audio' || !consumer.supported)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
consumer.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerAudioInProgress(peerName, false));
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
{
|
||||||
|
logger.error('unmutePeerAudio() failed: %o', error);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerAudioInProgress(peerName, false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pausePeerVideo(peerName)
|
||||||
|
{
|
||||||
|
logger.debug('pausePeerVideo() [peerName:"%s"]', peerName);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerVideoInProgress(peerName, true));
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
for (const peer of this._room.peers)
|
||||||
|
{
|
||||||
|
if (peer.name === peerName)
|
||||||
|
{
|
||||||
|
for (const consumer of peer.consumers)
|
||||||
|
{
|
||||||
|
if (consumer.kind !== 'video')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
consumer.pause('pause-video');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerVideoInProgress(peerName, false));
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
{
|
||||||
|
logger.error('pausePeerVideo() failed: %o', error);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerVideoInProgress(peerName, false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resumePeerVideo(peerName)
|
||||||
|
{
|
||||||
|
logger.debug('resumePeerVideo() [peerName:"%s"]', peerName);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerVideoInProgress(peerName, true));
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
for (const peer of this._room.peers)
|
||||||
|
{
|
||||||
|
if (peer.name === peerName)
|
||||||
|
{
|
||||||
|
for (const consumer of peer.consumers)
|
||||||
|
{
|
||||||
|
if (consumer.kind !== 'video' || !consumer.supported)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
consumer.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerVideoInProgress(peerName, false));
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
{
|
||||||
|
logger.error('resumePeerVideo() failed: %o', error);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setPeerVideoInProgress(peerName, false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
enableAudioOnly()
|
enableAudioOnly()
|
||||||
{
|
{
|
||||||
logger.debug('enableAudioOnly()');
|
logger.debug('enableAudioOnly()');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
import * as appPropTypes from './appPropTypes';
|
import * as appPropTypes from './appPropTypes';
|
||||||
|
import * as requestActions from '../redux/requestActions';
|
||||||
import PeerView from './PeerView';
|
import PeerView from './PeerView';
|
||||||
|
|
||||||
const Peer = (props) =>
|
const Peer = (props) =>
|
||||||
|
|
@ -9,7 +12,11 @@ const Peer = (props) =>
|
||||||
peer,
|
peer,
|
||||||
micConsumer,
|
micConsumer,
|
||||||
webcamConsumer,
|
webcamConsumer,
|
||||||
screenConsumer
|
screenConsumer,
|
||||||
|
onMuteMic,
|
||||||
|
onUnmuteMic,
|
||||||
|
onDisableWebcam,
|
||||||
|
onEnableWebcam
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const micEnabled = (
|
const micEnabled = (
|
||||||
|
|
@ -42,19 +49,33 @@ const Peer = (props) =>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component='Peer'>
|
<div data-component='Peer'>
|
||||||
<div className='indicators'>
|
<div className='controls'>
|
||||||
{peer.raiseHandState ?
|
<div
|
||||||
<div className='icon raise-hand' />
|
className={classnames('button', 'mic', {
|
||||||
:null
|
on : micEnabled,
|
||||||
}
|
off : !micEnabled,
|
||||||
{!micEnabled ?
|
disabled : peer.peerAudioInProgress
|
||||||
<div className='icon mic-off' />
|
})}
|
||||||
:null
|
onClick={(e) =>
|
||||||
}
|
{
|
||||||
{!videoVisible ?
|
e.stopPropagation();
|
||||||
<div className='icon webcam-off' />
|
micEnabled ? onMuteMic(peer.name) : onUnmuteMic(peer.name);
|
||||||
:null
|
}}
|
||||||
}
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classnames('button', 'webcam', {
|
||||||
|
on : videoVisible,
|
||||||
|
off : !videoVisible,
|
||||||
|
disabled : peer.peerVideoInProgress
|
||||||
|
})}
|
||||||
|
onClick={(e) =>
|
||||||
|
{
|
||||||
|
e.stopPropagation();
|
||||||
|
videoVisible ?
|
||||||
|
onDisableWebcam(peer.name) : onEnableWebcam(peer.name);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{videoVisible && !webcamConsumer.supported ?
|
{videoVisible && !webcamConsumer.supported ?
|
||||||
|
|
@ -83,10 +104,14 @@ const Peer = (props) =>
|
||||||
|
|
||||||
Peer.propTypes =
|
Peer.propTypes =
|
||||||
{
|
{
|
||||||
peer : appPropTypes.Peer.isRequired,
|
peer : appPropTypes.Peer.isRequired,
|
||||||
micConsumer : appPropTypes.Consumer,
|
micConsumer : appPropTypes.Consumer,
|
||||||
webcamConsumer : appPropTypes.Consumer,
|
webcamConsumer : appPropTypes.Consumer,
|
||||||
screenConsumer : appPropTypes.Consumer
|
screenConsumer : appPropTypes.Consumer,
|
||||||
|
onMuteMic : PropTypes.func.isRequired,
|
||||||
|
onUnmuteMic : PropTypes.func.isRequired,
|
||||||
|
onEnableWebcam : PropTypes.func.isRequired,
|
||||||
|
onDisableWebcam : PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, { name }) =>
|
const mapStateToProps = (state, { name }) =>
|
||||||
|
|
@ -109,6 +134,32 @@ const mapStateToProps = (state, { name }) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const PeerContainer = connect(mapStateToProps)(Peer);
|
const mapDispatchToProps = (dispatch) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
onMuteMic : (peerName) =>
|
||||||
|
{
|
||||||
|
dispatch(requestActions.mutePeerAudio(peerName));
|
||||||
|
},
|
||||||
|
onUnmuteMic : (peerName) =>
|
||||||
|
{
|
||||||
|
dispatch(requestActions.unmutePeerAudio(peerName));
|
||||||
|
},
|
||||||
|
onEnableWebcam : (peerName) =>
|
||||||
|
{
|
||||||
|
|
||||||
|
dispatch(requestActions.resumePeerVideo(peerName));
|
||||||
|
},
|
||||||
|
onDisableWebcam : (peerName) =>
|
||||||
|
{
|
||||||
|
dispatch(requestActions.pausePeerVideo(peerName));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const PeerContainer = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Peer);
|
||||||
|
|
||||||
export default PeerContainer;
|
export default PeerContainer;
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,7 @@ PeerView.propTypes =
|
||||||
screenTrack : PropTypes.any,
|
screenTrack : PropTypes.any,
|
||||||
videoVisible : PropTypes.bool.isRequired,
|
videoVisible : PropTypes.bool.isRequired,
|
||||||
videoProfile : PropTypes.string,
|
videoProfile : PropTypes.string,
|
||||||
screenVisible : PropTypes.bool.isRequired,
|
screenVisible : PropTypes.bool,
|
||||||
screenProfile : PropTypes.string,
|
screenProfile : PropTypes.string,
|
||||||
audioCodec : PropTypes.string,
|
audioCodec : PropTypes.string,
|
||||||
videoCodec : PropTypes.string,
|
videoCodec : PropTypes.string,
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,10 @@ class Peers extends React.Component
|
||||||
ratio : 1.334
|
ratio : 1.334
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
updateDimensions()
|
|
||||||
|
updateDimensions(nextProps = null)
|
||||||
{
|
{
|
||||||
const n = this.props.peers.length;
|
const n = nextProps ? nextProps.peers.length : this.props.peers.length;
|
||||||
|
|
||||||
if (n == 0)
|
if (n == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -62,6 +63,11 @@ class Peers extends React.Component
|
||||||
window.removeEventListener('resize', this.updateDimensions.bind(this));
|
window.removeEventListener('resize', this.updateDimensions.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps)
|
||||||
|
{
|
||||||
|
this.updateDimensions(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
|
|
@ -77,8 +83,6 @@ class Peers extends React.Component
|
||||||
'height' : peerHeight
|
'height' : peerHeight
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateDimensions();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component='Peers' ref='peers'>
|
<div data-component='Peers' ref='peers'>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,32 @@ const peers = (state = initialState, action) =>
|
||||||
return { ...state, [newPeer.name]: newPeer };
|
return { ...state, [newPeer.name]: newPeer };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'SET_PEER_VIDEO_IN_PROGRESS':
|
||||||
|
{
|
||||||
|
const { peerName, flag } = action.payload;
|
||||||
|
const peer = state[peerName];
|
||||||
|
|
||||||
|
if (!peer)
|
||||||
|
throw new Error('no Peer found');
|
||||||
|
|
||||||
|
const newPeer = { ...peer, peerVideoInProgress: flag };
|
||||||
|
|
||||||
|
return { ...state, [newPeer.name]: newPeer };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_PEER_AUDIO_IN_PROGRESS':
|
||||||
|
{
|
||||||
|
const { peerName, flag } = action.payload;
|
||||||
|
const peer = state[peerName];
|
||||||
|
|
||||||
|
if (!peer)
|
||||||
|
throw new Error('no Peer found');
|
||||||
|
|
||||||
|
const newPeer = { ...peer, peerAudioInProgress: flag };
|
||||||
|
|
||||||
|
return { ...state, [newPeer.name]: newPeer };
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_PEER_RAISE_HAND_STATE':
|
case 'SET_PEER_RAISE_HAND_STATE':
|
||||||
{
|
{
|
||||||
const { peerName, raiseHandState } = action.payload;
|
const { peerName, raiseHandState } = action.payload;
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,38 @@ export const disableAudioOnly = () =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mutePeerAudio = (peerName) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'MUTE_PEER_AUDIO',
|
||||||
|
payload : { peerName }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unmutePeerAudio = (peerName) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'UNMUTE_PEER_AUDIO',
|
||||||
|
payload : { peerName }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pausePeerVideo = (peerName) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'PAUSE_PEER_VIDEO',
|
||||||
|
payload : { peerName }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resumePeerVideo = (peerName) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'RESUME_PEER_VIDEO',
|
||||||
|
payload : { peerName }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const raiseHand = () =>
|
export const raiseHand = () =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,42 @@ export default ({ dispatch, getState }) => (next) =>
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'MUTE_PEER_AUDIO':
|
||||||
|
{
|
||||||
|
const { peerName } = action.payload;
|
||||||
|
|
||||||
|
client.mutePeerAudio(peerName);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'UNMUTE_PEER_AUDIO':
|
||||||
|
{
|
||||||
|
const { peerName } = action.payload;
|
||||||
|
|
||||||
|
client.unmutePeerAudio(peerName);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'PAUSE_PEER_VIDEO':
|
||||||
|
{
|
||||||
|
const { peerName } = action.payload;
|
||||||
|
|
||||||
|
client.pausePeerVideo(peerName);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'RESUME_PEER_VIDEO':
|
||||||
|
{
|
||||||
|
const { peerName } = action.payload;
|
||||||
|
|
||||||
|
client.resumePeerVideo(peerName);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'RAISE_HAND':
|
case 'RAISE_HAND':
|
||||||
{
|
{
|
||||||
client.sendRaiseHandState(true);
|
client.sendRaiseHandState(true);
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,22 @@ export const setAudioOnlyInProgress = (flag) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setPeerVideoInProgress = (peerName, flag) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_PEER_VIDEO_IN_PROGRESS',
|
||||||
|
payload : { peerName, flag }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setPeerAudioInProgress = (peerName, flag) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_PEER_AUDIO_IN_PROGRESS',
|
||||||
|
payload : { peerName, flag }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const setMyRaiseHandState = (flag) =>
|
export const setMyRaiseHandState = (flag) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,87 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
> .controls {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction:; row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> .button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-position: center;
|
||||||
|
background-size: 75%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-color: rgba(#000, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
transition-property: opacity, background-color;
|
||||||
|
transition-duration: 0.15s;
|
||||||
|
|
||||||
|
+desktop() {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
opacity: 0.85;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+mobile() {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unsupported {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.on {
|
||||||
|
background-color: rgba(#fff, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mic {
|
||||||
|
&.on {
|
||||||
|
background-image: url('/resources/images/icon_mic_black_on.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.off {
|
||||||
|
background-image: url('/resources/images/icon_mic_white_off.svg');
|
||||||
|
background-color: rgba(#d42241, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unsupported {
|
||||||
|
background-image: url('/resources/images/icon_mic_white_unsupported.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.webcam {
|
||||||
|
&.on {
|
||||||
|
background-image: url('/resources/images/icon_webcam_black_on.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.off {
|
||||||
|
background-image: url('/resources/images/icon_webcam_white_on.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unsupported {
|
||||||
|
background-image: url('/resources/images/icon_webcam_white_unsupported.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+mobile() {
|
+mobile() {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -11,47 +92,6 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .indicators {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction:; row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> .icon {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
margin: 4px;
|
|
||||||
margin-left: 0;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
background-position: center;
|
|
||||||
background-size: 75%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
transition-property: opacity;
|
|
||||||
transition-duration: 0.15s;
|
|
||||||
|
|
||||||
+desktop() {
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.raise-hand {
|
|
||||||
background-image: url('/resources/images/icon_remote_raise_hand.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mic-off {
|
|
||||||
background-image: url('/resources/images/icon_remote_mic_white_off.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
&.webcam-off {
|
|
||||||
background-image: url('/resources/images/icon_remote_webcam_white_off.svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.incompatible-video {
|
.incompatible-video {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2
|
z-index: 2
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue