From b73b8c1aa05612d2841d5f50e3c8e0d1fe166a29 Mon Sep 17 00:00:00 2001 From: Roman Drozd Date: Sun, 26 Apr 2020 21:46:55 +0200 Subject: [PATCH] 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 --- app/package.json | 1 + app/src/RoomClient.js | 17 ++ .../components/Controls/NetworkIndicator.js | 259 ++++++++++++++++++ .../components/VideoContainers/VideoView.js | 34 ++- 4 files changed, 297 insertions(+), 14 deletions(-) create mode 100644 app/src/components/Controls/NetworkIndicator.js diff --git a/app/package.json b/app/package.json index 1dab672..583dd08 100644 --- a/app/package.json +++ b/app/package.json @@ -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", diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 630851a..5d6efad 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -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)); diff --git a/app/src/components/Controls/NetworkIndicator.js b/app/src/components/Controls/NetworkIndicator.js new file mode 100644 index 0000000..a888386 --- /dev/null +++ b/app/src/components/Controls/NetworkIndicator.js @@ -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 ( + + + + + + {advancedMode && + + {/* 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} */} + + } + + ); + } +} + +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))); diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index c978433..fec8779 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -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
{ isMe ? - onChangeDisplayName(newDisplayName)} - /> + + + onChangeDisplayName(newDisplayName)} + /> + + : {displayName}