create component "NetworkIndicator"
add to VideoView for Me, receive data from server / getTransportStats, estimate of connection/value [wip] show recv/send bitrate in advanced modeauto_join_3.3
parent
cb5f4cd48e
commit
b73b8c1aa0
|
|
@ -32,6 +32,7 @@
|
|||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.4.1",
|
||||
"react-wakelock-react16": "0.0.7",
|
||||
"react-wifi-indicator": "^1.0.1",
|
||||
"redux": "^4.0.4",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-persist": "^6.0.0",
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ export default class RoomClient
|
|||
this._startKeyListener();
|
||||
|
||||
this._startDevicesListener();
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
logger.debug('changeDisplayName() [displayName:"%s"]', displayName);
|
||||
|
|
@ -1959,6 +1975,7 @@ export default class RoomClient
|
|||
{
|
||||
switch (notification.method)
|
||||
{
|
||||
|
||||
case 'enteredLobby':
|
||||
{
|
||||
store.dispatch(roomActions.setInLobby(true));
|
||||
|
|
|
|||
|
|
@ -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 ⇙ |
|
||||
{ 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)));
|
||||
|
|
@ -10,6 +10,7 @@ import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar';
|
|||
import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar';
|
||||
import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar';
|
||||
import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt';
|
||||
import NetworkIndicator from '../Controls/NetworkIndicator';
|
||||
|
||||
const styles = (theme) =>
|
||||
({
|
||||
|
|
@ -267,20 +268,25 @@ class VideoView extends React.PureComponent
|
|||
<div className={classes.peer}>
|
||||
<div className={classes.box}>
|
||||
{ isMe ?
|
||||
<EditableInput
|
||||
value={displayName}
|
||||
propName='newDisplayName'
|
||||
className={classes.displayNameEdit}
|
||||
classLoading='loading'
|
||||
classInvalid='invalid'
|
||||
shouldBlockWhileLoading
|
||||
editProps={{
|
||||
maxLength : 30,
|
||||
autoCorrect : 'off',
|
||||
spellCheck : false
|
||||
}}
|
||||
onChange={({ newDisplayName }) => onChangeDisplayName(newDisplayName)}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<EditableInput
|
||||
value={displayName}
|
||||
propName='newDisplayName'
|
||||
className={classes.displayNameEdit}
|
||||
classLoading='loading'
|
||||
classInvalid='invalid'
|
||||
shouldBlockWhileLoading
|
||||
editProps={{
|
||||
maxLength : 30,
|
||||
autoCorrect : 'off',
|
||||
spellCheck : false
|
||||
}}
|
||||
onChange={
|
||||
({ newDisplayName }) =>
|
||||
onChangeDisplayName(newDisplayName)}
|
||||
/>
|
||||
<NetworkIndicator />
|
||||
</React.Fragment>
|
||||
:
|
||||
<span className={classes.displayNameStatic}>
|
||||
{displayName}
|
||||
|
|
|
|||
Loading…
Reference in New Issue