Added support for muting audio and video from participants
parent
e105670fd9
commit
c18d982c1b
|
|
@ -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()
|
||||
{
|
||||
logger.debug('enableAudioOnly()');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import * as appPropTypes from './appPropTypes';
|
||||
import * as requestActions from '../redux/requestActions';
|
||||
import PeerView from './PeerView';
|
||||
|
||||
const Peer = (props) =>
|
||||
|
|
@ -9,7 +12,11 @@ const Peer = (props) =>
|
|||
peer,
|
||||
micConsumer,
|
||||
webcamConsumer,
|
||||
screenConsumer
|
||||
screenConsumer,
|
||||
onMuteMic,
|
||||
onUnmuteMic,
|
||||
onDisableWebcam,
|
||||
onEnableWebcam
|
||||
} = props;
|
||||
|
||||
const micEnabled = (
|
||||
|
|
@ -42,19 +49,33 @@ const Peer = (props) =>
|
|||
|
||||
return (
|
||||
<div data-component='Peer'>
|
||||
<div className='indicators'>
|
||||
{peer.raiseHandState ?
|
||||
<div className='icon raise-hand' />
|
||||
:null
|
||||
}
|
||||
{!micEnabled ?
|
||||
<div className='icon mic-off' />
|
||||
:null
|
||||
}
|
||||
{!videoVisible ?
|
||||
<div className='icon webcam-off' />
|
||||
:null
|
||||
}
|
||||
<div className='controls'>
|
||||
<div
|
||||
className={classnames('button', 'mic', {
|
||||
on : micEnabled,
|
||||
off : !micEnabled,
|
||||
disabled : peer.peerAudioInProgress
|
||||
})}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
micEnabled ? onMuteMic(peer.name) : onUnmuteMic(peer.name);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={classnames('button', 'webcam', {
|
||||
on : videoVisible,
|
||||
off : !videoVisible,
|
||||
disabled : peer.peerVideoInProgress
|
||||
})}
|
||||
onClick={(e) =>
|
||||
{
|
||||
e.stopPropagation();
|
||||
videoVisible ?
|
||||
onDisableWebcam(peer.name) : onEnableWebcam(peer.name);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{videoVisible && !webcamConsumer.supported ?
|
||||
|
|
@ -86,7 +107,11 @@ Peer.propTypes =
|
|||
peer : appPropTypes.Peer.isRequired,
|
||||
micConsumer : 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 }) =>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ PeerView.propTypes =
|
|||
screenTrack : PropTypes.any,
|
||||
videoVisible : PropTypes.bool.isRequired,
|
||||
videoProfile : PropTypes.string,
|
||||
screenVisible : PropTypes.bool.isRequired,
|
||||
screenVisible : PropTypes.bool,
|
||||
screenProfile : PropTypes.string,
|
||||
audioCodec : PropTypes.string,
|
||||
videoCodec : PropTypes.string,
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@ class Peers extends React.Component
|
|||
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)
|
||||
{
|
||||
|
|
@ -62,6 +63,11 @@ class Peers extends React.Component
|
|||
window.removeEventListener('resize', this.updateDimensions.bind(this));
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps)
|
||||
{
|
||||
this.updateDimensions(nextProps);
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
const {
|
||||
|
|
@ -77,8 +83,6 @@ class Peers extends React.Component
|
|||
'height' : peerHeight
|
||||
};
|
||||
|
||||
this.updateDimensions();
|
||||
|
||||
return (
|
||||
<div data-component='Peers' ref='peers'>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,6 +34,32 @@ const peers = (state = initialState, action) =>
|
|||
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':
|
||||
{
|
||||
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 = () =>
|
||||
{
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -102,6 +102,42 @@ export default ({ dispatch, getState }) => (next) =>
|
|||
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':
|
||||
{
|
||||
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) =>
|
||||
{
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,87 @@
|
|||
height: 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() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -11,47 +92,6 @@
|
|||
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 {
|
||||
position: absolute;
|
||||
z-index: 2
|
||||
|
|
|
|||
Loading…
Reference in New Issue