Merge pull request #349 from havfo/feat-network-indicator
create component "NetworkIndicator" add to VideoView for Me, receive data from server / getTransportStats, estimate of connection/value [wip] show recv/send bitrate in advanced mode * Change probe counting from sec to state inc * Rebuild, fix null error with null * Store probe from zero * Add highestBitrate * Fix eslint errors * remove graphical indicator, refactor only to statistics * uninstall "wifi-network-indicator" package * Remove "netInfo" class * Change "advanced mode" box info "bitrate" from MB/s to Mb/s * fix my own fault * Fix crash when screen sharing is running * Update transport stats only when advancedMode is on * Fix/Mute eslint Multiline support info * Fix lint * Set VideoView/netInfo prop as not required * Fix lint * Set RoomClient/_getTransportStats() as public Co-authored-by: Stefan Otto <stefan.otto@uninett.no>auto_join_3.3
commit
21a56f3b40
|
|
@ -14,6 +14,7 @@ import * as lobbyPeerActions from './actions/lobbyPeerActions';
|
||||||
import * as consumerActions from './actions/consumerActions';
|
import * as consumerActions from './actions/consumerActions';
|
||||||
import * as producerActions from './actions/producerActions';
|
import * as producerActions from './actions/producerActions';
|
||||||
import * as notificationActions from './actions/notificationActions';
|
import * as notificationActions from './actions/notificationActions';
|
||||||
|
import * as transportActions from './actions/transportActions';
|
||||||
|
|
||||||
let createTorrent;
|
let createTorrent;
|
||||||
|
|
||||||
|
|
@ -616,6 +617,36 @@ export default class RoomClient
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTransportStats()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this._recvTransport)
|
||||||
|
{
|
||||||
|
logger.debug('getTransportStats() - recv [transportId: "%s"]', this._recvTransport.id);
|
||||||
|
|
||||||
|
const recv = await this.sendRequest('getTransportStats', { transportId: this._recvTransport.id });
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
transportActions.addTransportStats(recv, 'recv'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._sendTransport)
|
||||||
|
{
|
||||||
|
logger.debug('getTransportStats() - send [transportId: "%s"]', this._sendTransport.id);
|
||||||
|
|
||||||
|
const send = await this.sendRequest('getTransportStats', { transportId: this._sendTransport.id });
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
transportActions.addTransportStats(send, 'send'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
logger.error('getTransportStats() | failed: %o', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async sendRequest(method, data)
|
async sendRequest(method, data)
|
||||||
{
|
{
|
||||||
logger.debug('sendRequest() [method:"%s", data:"%o"]', method, data);
|
logger.debug('sendRequest() [method:"%s", data:"%o"]', method, data);
|
||||||
|
|
@ -1043,7 +1074,7 @@ export default class RoomClient
|
||||||
{
|
{
|
||||||
if (volume < this._hark.lastVolume)
|
if (volume < this._hark.lastVolume)
|
||||||
{
|
{
|
||||||
volume = this._hark.lastVolume - Math.pow((volume - this._hark.lastVolume)/(100 + this._hark.lastVolume),4)*2;
|
volume = this._hark.lastVolume - Math.pow((volume - this._hark.lastVolume)/(100 + this._hark.lastVolume), 4)*2;
|
||||||
}
|
}
|
||||||
this._hark.lastVolume = volume;
|
this._hark.lastVolume = volume;
|
||||||
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
|
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
|
||||||
|
|
@ -2092,6 +2123,7 @@ export default class RoomClient
|
||||||
{
|
{
|
||||||
switch (notification.method)
|
switch (notification.method)
|
||||||
{
|
{
|
||||||
|
|
||||||
case 'enteredLobby':
|
case 'enteredLobby':
|
||||||
{
|
{
|
||||||
store.dispatch(roomActions.setInLobby(true));
|
store.dispatch(roomActions.setInLobby(true));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const addTransportStats = (transport, type) =>
|
||||||
|
({
|
||||||
|
type : 'ADD_TRANSPORT_STATS',
|
||||||
|
payload : { transport, type }
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
meProducersSelector,
|
meProducersSelector,
|
||||||
|
|
@ -176,6 +176,7 @@ const Me = (props) =>
|
||||||
screenProducer,
|
screenProducer,
|
||||||
extraVideoProducers,
|
extraVideoProducers,
|
||||||
canShareScreen,
|
canShareScreen,
|
||||||
|
transports,
|
||||||
noiseVolume,
|
noiseVolume,
|
||||||
classes
|
classes
|
||||||
} = props;
|
} = props;
|
||||||
|
|
@ -333,6 +334,20 @@ const Me = (props) =>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
let poll;
|
||||||
|
|
||||||
|
const interval = 1000;
|
||||||
|
|
||||||
|
if (advancedMode)
|
||||||
|
{
|
||||||
|
poll = setInterval(() => roomClient.getTransportStats(), interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => clearInterval(poll);
|
||||||
|
}, [ roomClient, advancedMode ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div
|
<div
|
||||||
|
|
@ -673,6 +688,7 @@ const Me = (props) =>
|
||||||
videoVisible={videoVisible}
|
videoVisible={videoVisible}
|
||||||
audioCodec={micProducer && micProducer.codec}
|
audioCodec={micProducer && micProducer.codec}
|
||||||
videoCodec={webcamProducer && webcamProducer.codec}
|
videoCodec={webcamProducer && webcamProducer.codec}
|
||||||
|
netInfo={transports && transports}
|
||||||
audioScore={audioScore}
|
audioScore={audioScore}
|
||||||
videoScore={videoScore}
|
videoScore={videoScore}
|
||||||
onChangeDisplayName={(displayName) =>
|
onChangeDisplayName={(displayName) =>
|
||||||
|
|
@ -888,7 +904,8 @@ Me.propTypes =
|
||||||
canShareScreen : PropTypes.bool.isRequired,
|
canShareScreen : PropTypes.bool.isRequired,
|
||||||
noiseVolume : PropTypes.number,
|
noiseVolume : PropTypes.number,
|
||||||
classes : PropTypes.object.isRequired,
|
classes : PropTypes.object.isRequired,
|
||||||
theme : PropTypes.object.isRequired
|
theme : PropTypes.object.isRequired,
|
||||||
|
transports : PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeMapStateToProps = () =>
|
const makeMapStateToProps = () =>
|
||||||
|
|
@ -916,6 +933,7 @@ const makeMapStateToProps = () =>
|
||||||
settings : state.settings,
|
settings : state.settings,
|
||||||
activeSpeaker : state.me.id === state.room.activeSpeakerId,
|
activeSpeaker : state.me.id === state.room.activeSpeakerId,
|
||||||
canShareScreen : hasPermission(state),
|
canShareScreen : hasPermission(state),
|
||||||
|
transports : state.transports,
|
||||||
noiseVolume : volume
|
noiseVolume : volume
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -937,7 +955,8 @@ export default withRoomContext(connect(
|
||||||
Math.round(next.peerVolumes[next.me.id]) &&
|
Math.round(next.peerVolumes[next.me.id]) &&
|
||||||
prev.peers === next.peers &&
|
prev.peers === next.peers &&
|
||||||
prev.producers === next.producers &&
|
prev.producers === next.producers &&
|
||||||
prev.settings === next.settings
|
prev.settings === next.settings &&
|
||||||
|
prev.transports === next.transports
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ const styles = (theme) =>
|
||||||
flexDirection : 'column',
|
flexDirection : 'column',
|
||||||
overflow : 'hidden'
|
overflow : 'hidden'
|
||||||
},
|
},
|
||||||
|
|
||||||
video :
|
video :
|
||||||
{
|
{
|
||||||
flex : '100 100 auto',
|
flex : '100 100 auto',
|
||||||
|
|
@ -75,16 +76,50 @@ const styles = (theme) =>
|
||||||
{
|
{
|
||||||
padding : theme.spacing(0.5),
|
padding : theme.spacing(0.5),
|
||||||
borderRadius : 2,
|
borderRadius : 2,
|
||||||
'& p' :
|
|
||||||
{
|
|
||||||
userSelect : 'none',
|
userSelect : 'none',
|
||||||
margin : 0,
|
margin : 0,
|
||||||
color : 'rgba(255, 255, 255, 0.7)',
|
color : 'rgba(255, 255, 255, 0.7)',
|
||||||
fontSize : '0.8em'
|
fontSize : '0.8em',
|
||||||
},
|
|
||||||
'&.left' :
|
'&.left' :
|
||||||
{
|
{
|
||||||
backgroundColor : 'rgba(0, 0, 0, 0.25)'
|
backgroundColor : 'rgba(0, 0, 0, 0.25)',
|
||||||
|
display : 'grid',
|
||||||
|
gap : '1px 5px',
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
gridTemplateAreas : '\
|
||||||
|
"AcodL Acod Acod Acod Acod" \
|
||||||
|
"VcodL Vcod Vcod Vcod Vcod" \
|
||||||
|
"ResL Res Res Res Res" \
|
||||||
|
"RecvL RecvBps RecvBps RecvSum RecvSum" \
|
||||||
|
"SendL SendBps SendBps SendSum SendSum" \
|
||||||
|
"IPlocL IPloc IPloc IPloc IPloc" \
|
||||||
|
"IPsrvL IPsrv IPsrv IPsrv IPsrv" \
|
||||||
|
"STLcurrL STLcurr STLcurr STLcurr STLcurr" \
|
||||||
|
"STLprefL STLpref STLpref STLpref STLpref"',
|
||||||
|
|
||||||
|
'& .AcodL' : { gridArea: 'AcodL' },
|
||||||
|
'& .Acod' : { gridArea: 'Acod' },
|
||||||
|
'& .VcodL' : { gridArea: 'VcodL' },
|
||||||
|
'& .Vcod' : { gridArea: 'Vcod' },
|
||||||
|
'& .ResL' : { gridArea: 'ResL' },
|
||||||
|
'& .Res' : { gridArea: 'Res' },
|
||||||
|
'& .RecvL' : { gridArea: 'RecvL' },
|
||||||
|
'& .RecvBps' : { gridArea: 'RecvBps', justifySelf: 'flex-end' },
|
||||||
|
'& .RecvSum' : { gridArea: 'RecvSum', justifySelf: 'flex-end' },
|
||||||
|
'& .SendL' : { gridArea: 'SendL' },
|
||||||
|
'& .SendBps' : { gridArea: 'SendBps', justifySelf: 'flex-end' },
|
||||||
|
'& .SendSum' : { gridArea: 'SendSum', justifySelf: 'flex-end' },
|
||||||
|
'& .IPlocL' : { gridArea: 'IPlocL' },
|
||||||
|
'& .IPloc' : { gridArea: 'IPloc' },
|
||||||
|
'& .IPsrvL' : { gridArea: 'IPsrvL' },
|
||||||
|
'& .IPsrv' : { gridArea: 'IPsrv' },
|
||||||
|
'& .STLcurrL' : { gridArea: 'STLcurrL' },
|
||||||
|
'& .STLcurr' : { gridArea: 'STLcurr' },
|
||||||
|
'& .STLprefL' : { gridArea: 'STLprefL' },
|
||||||
|
'& .STLpref' : { gridArea: 'STLpref' }
|
||||||
|
|
||||||
},
|
},
|
||||||
'&.right' :
|
'&.right' :
|
||||||
{
|
{
|
||||||
|
|
@ -170,7 +205,8 @@ class VideoView extends React.PureComponent
|
||||||
videoCodec,
|
videoCodec,
|
||||||
onChangeDisplayName,
|
onChangeDisplayName,
|
||||||
children,
|
children,
|
||||||
classes
|
classes,
|
||||||
|
netInfo
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -244,25 +280,74 @@ class VideoView extends React.PureComponent
|
||||||
<div className={classes.info}>
|
<div className={classes.info}>
|
||||||
<div className={classes.media}>
|
<div className={classes.media}>
|
||||||
<div className={classnames(classes.box, 'left', { hidden: !advancedMode })}>
|
<div className={classnames(classes.box, 'left', { hidden: !advancedMode })}>
|
||||||
{ audioCodec && <p>{audioCodec}</p> }
|
{ audioCodec &&
|
||||||
|
<React.Fragment>
|
||||||
{ videoCodec &&
|
<span className={'AcodL'}>Acod: </span>
|
||||||
<p>
|
<span className={'Acod'}>
|
||||||
{videoCodec}
|
{audioCodec}
|
||||||
</p>
|
</span>
|
||||||
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
{ videoMultiLayer &&
|
{ videoCodec &&
|
||||||
<p>
|
<React.Fragment>
|
||||||
{`current spatial-temporal layers: ${consumerCurrentSpatialLayer} ${consumerCurrentTemporalLayer}`}
|
<span className={'VcodL'}>Vcod: </span>
|
||||||
<br />
|
<span className={'Vcod'}>
|
||||||
{`preferred spatial-temporal layers: ${consumerPreferredSpatialLayer} ${consumerPreferredTemporalLayer}`}
|
{videoCodec}
|
||||||
</p>
|
</span>
|
||||||
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
{ (videoVisible && videoWidth !== null) &&
|
{ (videoVisible && videoWidth !== null) &&
|
||||||
<p>{videoWidth}x{videoHeight}</p>
|
<React.Fragment>
|
||||||
|
<span className={'ResL'}>Res: </span>
|
||||||
|
<span className={'Res'}>
|
||||||
|
{videoWidth}x{videoHeight}
|
||||||
|
</span>
|
||||||
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ isMe && !isScreen &&
|
||||||
|
(netInfo.recv && netInfo.send && netInfo.send.iceSelectedTuple) &&
|
||||||
|
<React.Fragment>
|
||||||
|
<span className={'RecvL'}>Recv: </span>
|
||||||
|
<span className={'RecvBps'}>
|
||||||
|
{(netInfo.recv.sendBitrate/1024/1024).toFixed(2)}Mb/s
|
||||||
|
</span>
|
||||||
|
<span className={'RecvSum'}>
|
||||||
|
{(netInfo.recv.bytesSent/1024/1024).toFixed(2)}MB
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className={'SendL'}>Send: </span>
|
||||||
|
<span className={'SendBps'}>
|
||||||
|
{(netInfo.send.recvBitrate/1024/1024).toFixed(2)}Mb/s
|
||||||
|
</span>
|
||||||
|
<span className={'SendSum'}>
|
||||||
|
{(netInfo.send.bytesReceived/1024/1024).toFixed(2)}MB
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className={'IPlocL'}>IPloc: </span>
|
||||||
|
<span className={'IPloc'}>
|
||||||
|
{netInfo.send.iceSelectedTuple.remoteIp}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className={'IPsrvL'}>IPsrv: </span>
|
||||||
|
<span className={'IPsrv'}>
|
||||||
|
{netInfo.send.iceSelectedTuple.localIp}
|
||||||
|
</span>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
|
||||||
|
{ videoMultiLayer &&
|
||||||
|
<React.Fragment>
|
||||||
|
<span className={'STLcurrL'}>STLcurr: </span>
|
||||||
|
<span className={'STLcurr'}>{consumerCurrentSpatialLayer} {consumerCurrentTemporalLayer}</span>
|
||||||
|
|
||||||
|
<span className={'STLprefL'}>STLpref: </span>
|
||||||
|
<span className={'STLpref'}>{consumerPreferredSpatialLayer} {consumerPreferredTemporalLayer}</span>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{ showQuality &&
|
{ showQuality &&
|
||||||
<div className={classnames(classes.box, 'right')}>
|
<div className={classnames(classes.box, 'right')}>
|
||||||
|
|
@ -277,6 +362,7 @@ class VideoView extends React.PureComponent
|
||||||
<div className={classes.peer}>
|
<div className={classes.peer}>
|
||||||
<div className={classes.box}>
|
<div className={classes.box}>
|
||||||
{ isMe ?
|
{ isMe ?
|
||||||
|
<React.Fragment>
|
||||||
<EditableInput
|
<EditableInput
|
||||||
value={displayName}
|
value={displayName}
|
||||||
propName='newDisplayName'
|
propName='newDisplayName'
|
||||||
|
|
@ -289,8 +375,11 @@ class VideoView extends React.PureComponent
|
||||||
autoCorrect : 'off',
|
autoCorrect : 'off',
|
||||||
spellCheck : false
|
spellCheck : false
|
||||||
}}
|
}}
|
||||||
onChange={({ newDisplayName }) => onChangeDisplayName(newDisplayName)}
|
onChange={
|
||||||
|
({ newDisplayName }) =>
|
||||||
|
onChangeDisplayName(newDisplayName)}
|
||||||
/>
|
/>
|
||||||
|
</React.Fragment>
|
||||||
:
|
:
|
||||||
<span className={classes.displayNameStatic}>
|
<span className={classes.displayNameStatic}>
|
||||||
{displayName}
|
{displayName}
|
||||||
|
|
@ -466,7 +555,8 @@ VideoView.propTypes =
|
||||||
videoCodec : PropTypes.string,
|
videoCodec : PropTypes.string,
|
||||||
onChangeDisplayName : PropTypes.func,
|
onChangeDisplayName : PropTypes.func,
|
||||||
children : PropTypes.object,
|
children : PropTypes.object,
|
||||||
classes : PropTypes.object.isRequired
|
classes : PropTypes.object.isRequired,
|
||||||
|
netInfo : PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withStyles(styles)(VideoView);
|
export default withStyles(styles)(VideoView);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
class AudioAnalyzer extends EventEmitter
|
class AudioAnalyzer extends EventEmitter
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,16 @@ import chat from './chat';
|
||||||
import toolarea from './toolarea';
|
import toolarea from './toolarea';
|
||||||
import files from './files';
|
import files from './files';
|
||||||
import settings from './settings';
|
import settings from './settings';
|
||||||
|
import transports from './transports';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
room,
|
room,
|
||||||
me,
|
me,
|
||||||
producers,
|
producers,
|
||||||
|
consumers,
|
||||||
|
transports,
|
||||||
peers,
|
peers,
|
||||||
lobbyPeers,
|
lobbyPeers,
|
||||||
consumers,
|
|
||||||
peerVolumes,
|
peerVolumes,
|
||||||
notifications,
|
notifications,
|
||||||
chat,
|
chat,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
const initialState = {};
|
||||||
|
|
||||||
|
const transports = (state = initialState, action) =>
|
||||||
|
{
|
||||||
|
switch (action.type)
|
||||||
|
{
|
||||||
|
case 'ADD_TRANSPORT_STATS':
|
||||||
|
{
|
||||||
|
const { transport, type } = action.payload;
|
||||||
|
|
||||||
|
return { ...state, [type]: transport[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default transports;
|
||||||
Loading…
Reference in New Issue