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
auto_join_3.3
Roman Drozd 2020-04-26 21:46:55 +02:00
parent cb5f4cd48e
commit b73b8c1aa0
4 changed files with 297 additions and 14 deletions

View File

@ -32,6 +32,7 @@
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scripts": "3.4.1", "react-scripts": "3.4.1",
"react-wakelock-react16": "0.0.7", "react-wakelock-react16": "0.0.7",
"react-wifi-indicator": "^1.0.1",
"redux": "^4.0.4", "redux": "^4.0.4",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",

View File

@ -257,6 +257,7 @@ export default class RoomClient
this._startKeyListener(); this._startKeyListener();
this._startDevicesListener(); this._startDevicesListener();
} }
close() close()
@ -594,6 +595,21 @@ export default class RoomClient
}); });
} }
async getTransportStats(transportId)
{
logger.debug('getTransportStats() [transportId: "%s"]', transportId);
try
{
return await this.sendRequest('getTransportStats', { transportId: transportId });
}
catch (error)
{
logger.error('getTransportStats() | failed: %o', error);
}
}
async changeDisplayName(displayName) async changeDisplayName(displayName)
{ {
logger.debug('changeDisplayName() [displayName:"%s"]', displayName); logger.debug('changeDisplayName() [displayName:"%s"]', displayName);
@ -1959,6 +1975,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));

View File

@ -0,0 +1,259 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
peersLengthSelector
} from '../Selectors';
import * as appPropTypes from '../appPropTypes';
import { withRoomContext } from '../../RoomContext';
import { withStyles } from '@material-ui/core/styles';
import WifiIndicator from 'react-wifi-indicator';
import Logger from '../../Logger';
const logger = new Logger('NetworkIndicator');
const styles = () =>
({
root : {
verticalAlign : 'middle',
'& img' : {
display : 'inline',
width : '1.7em',
height : '1.7em',
margin : '10px'
}
},
label : {
color : 'white'
},
strength :
{
margin : 0,
padding : 0
}
});
class NetworkIndicator extends React.Component
{
constructor(props)
{
super(props);
this.state = {
strengthScale : {
1 : 'EXCELLENT',
2 : 'GREAT',
3 : 'OKAY',
4 : 'WEAK',
5 : 'UNUSABLE',
6 : 'DISCONNECTED'
},
strength : 6,
bitrate : null,
recv : {},
send : {},
probe : [],
currBitrate : 0,
maxBitrate : 0,
avgBitrate : 0,
medBitrate : 0
};
}
// const intl = useIntl();
async handleUpdateStrength()
{
// if (this.props.peersLength == 0)
// {
const percent = this.state.percent;
logger.warn('[percent: "%s"]', percent);
switch (true)
{
case (percent <= 20):
await this.setState({ strength: 5 });
break;
case (percent <= 40):
await this.setState({ strength: 4 });
break;
case (percent <= 60):
await this.setState({ strength: 3 });
break;
case (percent <= 80):
await this.setState({ strength: 2 });
break;
case (percent <= 100):
await this.setState({ strength: 1 });
break;
default:
break;
}
// }
// else
// {
// this.setState({ strength: 6 });
// }
}
async handleGetData()
{
const rc = this.props.roomClient;
const recv = await rc.getTransportStats(rc._recvTransport.id);
const send = await rc.getTransportStats(rc._sendTransport.id);
// current
const currBitrate = Math.round(send[0].recvBitrate / 1024 / 8); // in kb
// probe
const probe = [ ...this.state.probe ]; // clone
const sec = new Date().getSeconds()
.toString()
.split('')
.map(Number)[1];
probe[sec] = currBitrate; // add/update next element
// median
const med = (arr) =>
{
const mid = Math.floor(arr.length / 2);
const nums = [ ...arr ].sort((a, b) => a - b);
return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
};
const medBitrate = med([ ...probe ]);
// maximum
let maxBitrate = Math.max(...probe);
maxBitrate = (currBitrate > maxBitrate) ? currBitrate : maxBitrate;
// average
const avgBitrate = [ ...probe ]
.map((x, i, avgBitrate) => x/avgBitrate.length)
.reduce((a, b) => a + b);
const percent =
await Math.round(this.state.currBitrate / this.state.medBitrate * 100);
this.setState({
recv : recv[0],
send : send[0],
probe,
currBitrate,
maxBitrate,
avgBitrate,
medBitrate,
percent
});
logger.warn('[currBitrate: "%s"]', currBitrate);
logger.warn('[maxBitrate: "%s"]', maxBitrate);
logger.warn('[medBitrate: "%s"]', medBitrate);
logger.warn('[avgBitrate: "%s"]', avgBitrate);
}
componentDidMount()
{
this.update = setInterval(async () =>
{
await this.handleGetData();
await this.handleUpdateStrength();
}, 1000);
}
componentWillUnmount()
{
clearInterval(this.update);
}
render()
{
const {
classes,
advancedMode
} = this.props;
return (
<React.Fragment>
<span className={classes.root}>
<WifiIndicator
strength={this.state.strengthScale[this.state.strength]}
/>
</span>
{advancedMode &&
<span className={classes.label}>
{/* rr:{ Math.round(this.state.recv.recvBitrate / 1024 /8) || 0}, */}
{ Math.round(this.state.recv.sendBitrate / 1024 /8) || 0}kb |&nbsp;
{ Math.round(this.state.send.recvBitrate / 1024 /8) || 0}kb
{/* ss:{ Math.round(this.state.send.sendBitrate / 1024) /8 || 0} */}
</span>
}
</React.Fragment>
);
}
}
NetworkIndicator.propTypes =
{
roomClient : PropTypes.object.isRequired,
room : appPropTypes.Room.isRequired,
peersLength : PropTypes.number,
theme : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired,
me : PropTypes.object.isRequired,
advancedMode : PropTypes.bool.isRequired
};
const mapStateToProps = (state) =>
({
room : state.room,
advancedMode : state.settings.advancedMode,
peersLength : peersLengthSelector(state),
me : state.me
});
const mapDispatchToProps = (dispatch) =>
({
// toggleToolArea : () =>
// {
// dispatch(toolareaActions.toggleToolArea());
// }
});
export default withRoomContext(connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room === next.room &&
prev.peers === next.peers &&
prev.settings.advancedMode === next.settings.advancedMode
);
}
}
)(withStyles(styles, { withTheme: true })(NetworkIndicator)));

View File

@ -10,6 +10,7 @@ import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar';
import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar'; import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar';
import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar'; import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar';
import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt'; import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt';
import NetworkIndicator from '../Controls/NetworkIndicator';
const styles = (theme) => const styles = (theme) =>
({ ({
@ -267,6 +268,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'
@ -279,8 +281,12 @@ class VideoView extends React.PureComponent
autoCorrect : 'off', autoCorrect : 'off',
spellCheck : false spellCheck : false
}} }}
onChange={({ newDisplayName }) => onChangeDisplayName(newDisplayName)} onChange={
({ newDisplayName }) =>
onChangeDisplayName(newDisplayName)}
/> />
<NetworkIndicator />
</React.Fragment>
: :
<span className={classes.displayNameStatic}> <span className={classes.displayNameStatic}>
{displayName} {displayName}