Show remote audio levels
parent
f1658f1b3c
commit
2645dc619a
|
|
@ -28,6 +28,9 @@ export default class Client extends events.EventEmitter
|
||||||
super();
|
super();
|
||||||
this.setMaxListeners(Infinity);
|
this.setMaxListeners(Infinity);
|
||||||
|
|
||||||
|
// TODO: TMP
|
||||||
|
global.CLIENT = this;
|
||||||
|
|
||||||
let url = urlFactory.getProtooUrl(peerId, roomId);
|
let url = urlFactory.getProtooUrl(peerId, roomId);
|
||||||
let transport = new protooClient.WebSocketTransport(url);
|
let transport = new protooClient.WebSocketTransport(url);
|
||||||
|
|
||||||
|
|
@ -708,6 +711,16 @@ export default class Client extends events.EventEmitter
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'activespeaker':
|
||||||
|
{
|
||||||
|
let data = request.data;
|
||||||
|
|
||||||
|
this.emit('activespeaker', data.peer, data.level);
|
||||||
|
accept();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
logger.error('unknown method');
|
logger.error('unknown method');
|
||||||
|
|
@ -776,7 +789,6 @@ export default class Client extends events.EventEmitter
|
||||||
this._peerconnection = new RTCPeerConnection({ iceServers: [] });
|
this._peerconnection = new RTCPeerConnection({ iceServers: [] });
|
||||||
|
|
||||||
// TODO: TMP
|
// TODO: TMP
|
||||||
global.CLIENT = this;
|
|
||||||
global.PC = this._peerconnection;
|
global.PC = this._peerconnection;
|
||||||
|
|
||||||
if (this._localStream)
|
if (this._localStream)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import IconButton from 'material-ui/IconButton/IconButton';
|
||||||
import MicOffIcon from 'material-ui/svg-icons/av/mic-off';
|
import MicOffIcon from 'material-ui/svg-icons/av/mic-off';
|
||||||
import VideoCamOffIcon from 'material-ui/svg-icons/av/videocam-off';
|
import VideoCamOffIcon from 'material-ui/svg-icons/av/videocam-off';
|
||||||
import ChangeVideoCamIcon from 'material-ui/svg-icons/av/repeat';
|
import ChangeVideoCamIcon from 'material-ui/svg-icons/av/repeat';
|
||||||
|
import classnames from 'classnames';
|
||||||
import Video from './Video';
|
import Video from './Video';
|
||||||
import Logger from '../Logger';
|
import Logger from '../Logger';
|
||||||
|
|
||||||
|
|
@ -30,7 +31,12 @@ export default class LocalVideo extends React.Component
|
||||||
let state = this.state;
|
let state = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component='LocalVideo' className={`state-${props.connectionState}`}>
|
<div
|
||||||
|
data-component='LocalVideo'
|
||||||
|
className={classnames(`state-${props.connectionState}`, {
|
||||||
|
'active-speaker' : props.isActiveSpeaker
|
||||||
|
})}
|
||||||
|
>
|
||||||
{props.stream ?
|
{props.stream ?
|
||||||
<Video
|
<Video
|
||||||
stream={props.stream}
|
stream={props.stream}
|
||||||
|
|
@ -141,6 +147,7 @@ LocalVideo.propTypes =
|
||||||
multipleWebcams : React.PropTypes.bool.isRequired,
|
multipleWebcams : React.PropTypes.bool.isRequired,
|
||||||
webcamType : React.PropTypes.string,
|
webcamType : React.PropTypes.string,
|
||||||
connectionState : React.PropTypes.string,
|
connectionState : React.PropTypes.string,
|
||||||
|
isActiveSpeaker : React.PropTypes.bool.isRequired,
|
||||||
onMicMute : React.PropTypes.func.isRequired,
|
onMicMute : React.PropTypes.func.isRequired,
|
||||||
onWebcamToggle : React.PropTypes.func.isRequired,
|
onWebcamToggle : React.PropTypes.func.isRequired,
|
||||||
onWebcamChange : React.PropTypes.func.isRequired,
|
onWebcamChange : React.PropTypes.func.isRequired,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,10 @@ export default class RemoteVideo extends React.Component
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-component='RemoteVideo'
|
data-component='RemoteVideo'
|
||||||
className={classnames({ fullsize: !!props.fullsize })}
|
className={classnames({
|
||||||
|
fullsize : !!props.fullsize,
|
||||||
|
'active-speaker' : props.isActiveSpeaker
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<Video
|
<Video
|
||||||
stream={props.stream}
|
stream={props.stream}
|
||||||
|
|
@ -96,6 +99,7 @@ RemoteVideo.propTypes =
|
||||||
peer : React.PropTypes.object.isRequired,
|
peer : React.PropTypes.object.isRequired,
|
||||||
stream : React.PropTypes.object.isRequired,
|
stream : React.PropTypes.object.isRequired,
|
||||||
fullsize : React.PropTypes.bool,
|
fullsize : React.PropTypes.bool,
|
||||||
|
isActiveSpeaker : React.PropTypes.bool.isRequired,
|
||||||
onDisableVideo : React.PropTypes.func.isRequired,
|
onDisableVideo : React.PropTypes.func.isRequired,
|
||||||
onEnableVideo : React.PropTypes.func.isRequired
|
onEnableVideo : React.PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ export default class Room extends React.Component
|
||||||
connectionState : null,
|
connectionState : null,
|
||||||
remoteStreams : {},
|
remoteStreams : {},
|
||||||
showStats : false,
|
showStats : false,
|
||||||
stats : null
|
stats : null,
|
||||||
|
activeSpeakerId : null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mounted flag
|
// Mounted flag
|
||||||
|
|
@ -89,6 +90,7 @@ export default class Room extends React.Component
|
||||||
peer={peer}
|
peer={peer}
|
||||||
stream={stream}
|
stream={stream}
|
||||||
fullsize={numPeers === 1}
|
fullsize={numPeers === 1}
|
||||||
|
isActiveSpeaker={peer.id === state.activeSpeakerId}
|
||||||
onDisableVideo={this.handleDisableRemoteVideo.bind(this)}
|
onDisableVideo={this.handleDisableRemoteVideo.bind(this)}
|
||||||
onEnableVideo={this.handleEnableRemoteVideo.bind(this)}
|
onEnableVideo={this.handleEnableRemoteVideo.bind(this)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -107,6 +109,7 @@ export default class Room extends React.Component
|
||||||
multipleWebcams={state.multipleWebcams}
|
multipleWebcams={state.multipleWebcams}
|
||||||
webcamType={state.webcamType}
|
webcamType={state.webcamType}
|
||||||
connectionState={state.connectionState}
|
connectionState={state.connectionState}
|
||||||
|
isActiveSpeaker={props.peerId === state.activeSpeakerId}
|
||||||
onMicMute={this.handleLocalMute.bind(this)}
|
onMicMute={this.handleLocalMute.bind(this)}
|
||||||
onWebcamToggle={this.handleLocalWebcamToggle.bind(this)}
|
onWebcamToggle={this.handleLocalWebcamToggle.bind(this)}
|
||||||
onWebcamChange={this.handleLocalWebcamChange.bind(this)}
|
onWebcamChange={this.handleLocalWebcamChange.bind(this)}
|
||||||
|
|
@ -296,8 +299,12 @@ export default class Room extends React.Component
|
||||||
|
|
||||||
this._client.on('close', (error) =>
|
this._client.on('close', (error) =>
|
||||||
{
|
{
|
||||||
// Clear remote streams (for reconnections).
|
// Clear remote streams (for reconnections) and more stuff.
|
||||||
this.setState({ remoteStreams: {} });
|
this.setState(
|
||||||
|
{
|
||||||
|
remoteStreams : {},
|
||||||
|
activeSpeakerId : null
|
||||||
|
});
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
|
|
@ -431,6 +438,14 @@ export default class Room extends React.Component
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1347578
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1347578
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._client.on('activespeaker', (peer) =>
|
||||||
|
{
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
activeSpeakerId : (peer ? peer.id : null)
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_startStats()
|
_startStats()
|
||||||
|
|
|
||||||
|
|
@ -90,12 +90,12 @@ export default class Video extends React.Component
|
||||||
|
|
||||||
this._hark.on('speaking', () =>
|
this._hark.on('speaking', () =>
|
||||||
{
|
{
|
||||||
logger.debug('hark "speaking" event');
|
// logger.debug('hark "speaking" event');
|
||||||
});
|
});
|
||||||
|
|
||||||
this._hark.on('stopped_speaking', () =>
|
this._hark.on('stopped_speaking', () =>
|
||||||
{
|
{
|
||||||
logger.debug('hark "stopped_speaking" event');
|
// logger.debug('hark "stopped_speaking" event');
|
||||||
|
|
||||||
this.setState({ volume: 0 });
|
this.setState({ volume: 0 });
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.state-checking {
|
&.state-checking {
|
||||||
animation: LocalVideo-state-checking .75s infinite linear;
|
animation: LocalVideo-state-checking .5s infinite linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.state-connected,
|
&.state-connected,
|
||||||
|
|
@ -35,6 +35,10 @@
|
||||||
border-color: rgba(#ff2000, 0.75);
|
border-color: rgba(#ff2000, 0.75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.active-speaker {
|
||||||
|
border-color: rgba(#fff, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
> .controls {
|
> .controls {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.active-speaker {
|
||||||
|
border-color: rgba(#fff, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
> .controls {
|
> .controls {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
top: 0
|
top: 0
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 2px;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ const config = require('../config');
|
||||||
const MAX_BITRATE = config.mediasoup.maxBitrate || 3000000;
|
const MAX_BITRATE = config.mediasoup.maxBitrate || 3000000;
|
||||||
const MIN_BITRATE = Math.min(50000 || MAX_BITRATE);
|
const MIN_BITRATE = Math.min(50000 || MAX_BITRATE);
|
||||||
const BITRATE_FACTOR = 0.75;
|
const BITRATE_FACTOR = 0.75;
|
||||||
|
const MIN_AUDIO_LEVEL = -50;
|
||||||
|
|
||||||
class Room extends EventEmitter
|
class Room extends EventEmitter
|
||||||
{
|
{
|
||||||
|
|
@ -30,6 +31,8 @@ class Room extends EventEmitter
|
||||||
this._pendingProtooPeers = [];
|
this._pendingProtooPeers = [];
|
||||||
// Current max bitrate for all the participants.
|
// Current max bitrate for all the participants.
|
||||||
this._maxBitrate = MAX_BITRATE;
|
this._maxBitrate = MAX_BITRATE;
|
||||||
|
// Current active speaker mediasoup Peer.
|
||||||
|
this._activeSpeaker = null;
|
||||||
|
|
||||||
// Create a mediasoup room.
|
// Create a mediasoup room.
|
||||||
mediaServer.createRoom(
|
mediaServer.createRoom(
|
||||||
|
|
@ -53,6 +56,60 @@ class Room extends EventEmitter
|
||||||
this._updateMaxBitrate();
|
this._updateMaxBitrate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._mediaRoom.on('audiolevels', (entries) =>
|
||||||
|
{
|
||||||
|
logger.debug('room "audiolevels" event');
|
||||||
|
|
||||||
|
for (let entry of entries)
|
||||||
|
{
|
||||||
|
logger.debug('- [peer name:%s, rtpReceiver.id:%s, audio level:%s]',
|
||||||
|
entry.peer.name, entry.rtpReceiver.id, entry.audioLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeSpeaker;
|
||||||
|
let activeLevel;
|
||||||
|
|
||||||
|
if (entries.length > 0)
|
||||||
|
{
|
||||||
|
activeSpeaker = entries[0].peer;
|
||||||
|
activeLevel = entries[0].audioLevel;
|
||||||
|
|
||||||
|
if (activeLevel < MIN_AUDIO_LEVEL)
|
||||||
|
{
|
||||||
|
activeSpeaker = null;
|
||||||
|
activeLevel = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
activeSpeaker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._activeSpeaker !== activeSpeaker)
|
||||||
|
{
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
if (activeSpeaker)
|
||||||
|
{
|
||||||
|
logger.debug('active speaker [peer:"%s", volume:%s]',
|
||||||
|
activeSpeaker.name, activeLevel);
|
||||||
|
|
||||||
|
data.peer = { id: activeSpeaker.name };
|
||||||
|
data.level = activeLevel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.debug('no current speaker');
|
||||||
|
|
||||||
|
data.peer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._protooRoom.spread('activespeaker', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._activeSpeaker = activeSpeaker;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run all the pending join requests.
|
// Run all the pending join requests.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue