Show remote audio levels

master
Iñaki Baz Castillo 2017-04-27 22:58:23 +02:00
parent f1658f1b3c
commit 2645dc619a
9 changed files with 118 additions and 15 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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
}; };

View File

@ -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()

View File

@ -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 });
}); });

View File

@ -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;

View File

@ -47,6 +47,10 @@
} }
} }
&.active-speaker {
border-color: rgba(#fff, 0.9);
}
> .controls { > .controls {
pointer-events: none; pointer-events: none;
position: absolute; position: absolute;

View File

@ -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;

View File

@ -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.