From 59d48253a33f5474042536c9ba905b3340c838b0 Mon Sep 17 00:00:00 2001 From: Roman Drozd Date: Mon, 11 May 2020 21:10:08 +0200 Subject: [PATCH] remove graphical indicator, refactor only to statistics --- app/src/RoomClient.js | 32 +- app/src/actions/transportActions.js | 5 + app/src/components/Containers/Me.js | 14 +- .../components/Controls/NetworkIndicator.js | 294 ------------------ .../components/VideoContainers/VideoView.js | 130 ++++++-- app/src/reducers/rootReducer.js | 4 +- app/src/reducers/transports.js | 19 ++ 7 files changed, 173 insertions(+), 325 deletions(-) create mode 100644 app/src/actions/transportActions.js delete mode 100644 app/src/components/Controls/NetworkIndicator.js create mode 100644 app/src/reducers/transports.js diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 5d6efad..b39b2b9 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -13,6 +13,7 @@ import * as lobbyPeerActions from './actions/lobbyPeerActions'; import * as consumerActions from './actions/consumerActions'; import * as producerActions from './actions/producerActions'; import * as notificationActions from './actions/notificationActions'; +import * as transportActions from './actions/transportActions'; let createTorrent; @@ -258,6 +259,8 @@ export default class RoomClient this._startDevicesListener(); + this._getTransportStats(); + } close() @@ -595,14 +598,33 @@ export default class RoomClient }); } - async getTransportStats(transportId) + async _getTransportStats() { - - logger.debug('getTransportStats() [transportId: "%s"]', transportId); - try { - return await this.sendRequest('getTransportStats', { transportId: transportId }); + setInterval(async () => + { + 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')); + } + + }, 1000); } catch (error) { diff --git a/app/src/actions/transportActions.js b/app/src/actions/transportActions.js new file mode 100644 index 0000000..46b901e --- /dev/null +++ b/app/src/actions/transportActions.js @@ -0,0 +1,5 @@ +export const addTransportStats = (transport, type) => + ({ + type : 'ADD_TRANSPORT_STATS', + payload : { transport, type } + }); \ No newline at end of file diff --git a/app/src/components/Containers/Me.js b/app/src/components/Containers/Me.js index b79c5d0..fb5b60a 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -141,7 +141,8 @@ const Me = (props) => webcamProducer, screenProducer, canShareScreen, - classes + classes, + transports } = props; const videoVisible = ( @@ -445,6 +446,7 @@ const Me = (props) => videoVisible={videoVisible} audioCodec={micProducer && micProducer.codec} videoCodec={webcamProducer && webcamProducer.codec} + netInfo={transports && transports} onChangeDisplayName={(displayName) => { roomClient.changeDisplayName(displayName); @@ -541,7 +543,9 @@ Me.propTypes = smallButtons : PropTypes.bool, canShareScreen : PropTypes.bool.isRequired, classes : PropTypes.object.isRequired, - theme : PropTypes.object.isRequired + theme : PropTypes.object.isRequired, + transports : PropTypes.object.isRequired + }; const mapStateToProps = (state) => @@ -553,7 +557,8 @@ const mapStateToProps = (state) => activeSpeaker : state.me.id === state.room.activeSpeakerId, canShareScreen : state.me.roles.some((role) => - state.room.permissionsFromRoles.SHARE_SCREEN.includes(role)) + state.room.permissionsFromRoles.SHARE_SCREEN.includes(role)), + transports : state.transports }; }; @@ -569,7 +574,8 @@ export default withRoomContext(connect( prev.me === next.me && prev.producers === next.producers && prev.settings === next.settings && - prev.room.activeSpeakerId === next.room.activeSpeakerId + prev.room.activeSpeakerId === next.room.activeSpeakerId && + prev.transports === next.transports ); } } diff --git a/app/src/components/Controls/NetworkIndicator.js b/app/src/components/Controls/NetworkIndicator.js deleted file mode 100644 index 694dd6e..0000000 --- a/app/src/components/Controls/NetworkIndicator.js +++ /dev/null @@ -1,294 +0,0 @@ -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 : { // text statuses prowived by the "react-wifi-indicator" - 1 : 'EXCELLENT', - 2 : 'GREAT', - 3 : 'OKAY', - 4 : 'WEAK', - 5 : 'UNUSABLE', - 6 : 'DISCONNECTED' - }, - strength : 6, - recv : {}, - send : {}, - probe : [], - currBitrate : 0, - maxBitrate : 0, - avgBitrate : 0, - medBitrate : 0, - probeCount : 0, - probeLimit : 3, - highestBitrate : 0, - resolution : '' - }; - } - - // 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 probe = [ ...this.state.probe ]; // clone - - const probeCount = this.state.probeCount; - - const probeLimit = this.state.probeLimit; - - const currBitrate = this.state.currBitrate; - - let highestBitrate = this.state.highestBitrate; - - const recv = this.state.recv; - - const send = this.state.send; - - probe[probeCount] = 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); - - // highest - this.setState({ resolution: this.props.resolution }); - - highestBitrate = (currBitrate > highestBitrate) ? currBitrate : highestBitrate; - - maxBitrate = (currBitrate > maxBitrate) ? currBitrate : maxBitrate; - - // average - const avgBitrate = [ ...probe ] - .map((x, i, avg) => x/avg.length) - .reduce((a, b) => a + b); - - const percent = - await Math.round(currBitrate / medBitrate * 100); - - const x = (rc._recvTransport) - ? (await rc.getTransportStats(rc._recvTransport.id)) - : null; - - const y = (rc._sendTransport) - ? (await rc.getTransportStats(rc._sendTransport.id)) - : null; - - if (x && y) - { - - this.setState({ - recv : x[0], - send : y[0] - }); - } - - this.setState({ - probe, - probeCount : (probeCount < probeLimit - 1) ? probeCount + 1 : 0, - currBitrate : (send) ? Math.round(send.recvBitrate / 1024 / 8) : 0, - maxBitrate, - avgBitrate, - medBitrate, - percent, - highestBitrate - }); - - logger.warn('[currBitrate: "%s"]', currBitrate); - logger.warn('[maxBitrate: "%s"]', maxBitrate); - logger.warn('[medBitrate: "%s"]', medBitrate); - logger.warn('[avgBitrate: "%s"]', avgBitrate); - logger.warn('[probeCount: "%s"]', this.state.probeCount); - } - - componentDidMount() - { - this.update = setInterval(async () => - { - await this.handleGetData(); - await this.handleUpdateStrength(); - }, 1000); - } - - componentWillUnmount() - { - clearInterval(this.update); - } - - // componentDidUpdate(prevProps, prevState) { - // if (this.prevState.resolution !== this.state.resolution) { - // this.setState({ highestBitrate: 0}); - // } - // } - - render() - { - const { - classes, - advancedMode, - resolution - } = 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, - resolution : PropTypes.string.isRequired - }; - -const mapStateToProps = (state) => - ({ - room : state.room, - advancedMode : state.settings.advancedMode, - peersLength : peersLengthSelector(state), - me : state.me, - resolution : state.settings.resolution - }); - -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 && - prev.settings.resolution === next.settings.resolution - ); - } - } -)(withStyles(styles, { withTheme: true })(NetworkIndicator))); diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index fec8779..799d09e 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -10,7 +10,6 @@ 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) => ({ @@ -24,6 +23,7 @@ const styles = (theme) => flexDirection : 'column', overflow : 'hidden' }, + video : { flex : '100 100 auto', @@ -94,7 +94,43 @@ const styles = (theme) => { opacity : 0, transitionDuration : '0s' - } + }, + '& .netInfo' : + { + display : 'grid', + gap : '1px 5px', + 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' } + } }, peer : { @@ -166,7 +202,8 @@ class VideoView extends React.PureComponent videoCodec, onChangeDisplayName, children, - classes + classes, + netInfo } = this.props; const { @@ -235,25 +272,76 @@ class VideoView extends React.PureComponent
- { audioCodec &&

{audioCodec}

} +

+ { audioCodec && + + Acod: + + {audioCodec} + + + } - { videoCodec && -

- {videoCodec} -

- } + { videoCodec && + + Vcod: + + {videoCodec} + + + } - { videoMultiLayer && -

- {`current spatial-temporal layers: ${consumerCurrentSpatialLayer} ${consumerCurrentTemporalLayer}`} -
- {`preferred spatial-temporal layers: ${consumerPreferredSpatialLayer} ${consumerPreferredTemporalLayer}`} -

- } + { (videoVisible && videoWidth !== null) && + + Res: + + {videoWidth}x{videoHeight} + + + } + + { isMe && + (netInfo.recv && netInfo.send && netInfo.send.iceSelectedTuple) && + + Recv: + + {(netInfo.recv.sendBitrate/8/1024/1024).toFixed(2)}MB/s + + + {(netInfo.recv.bytesSent/1024/1024).toFixed(2)}MB + + + Send: + + {(netInfo.send.recvBitrate/8/1024/1024).toFixed(2)}MB/s + + + {(netInfo.send.bytesReceived/1024/1024).toFixed(2)}MB + + + IPloc: + + {netInfo.send.iceSelectedTuple.remoteIp} + + + IPsrv: + + {netInfo.send.iceSelectedTuple.localIp} + + + } + + { videoMultiLayer && + + STLcurr: + {consumerCurrentSpatialLayer} {consumerCurrentTemporalLayer} + + STLpref: + {consumerPreferredSpatialLayer} {consumerPreferredTemporalLayer} + + } +

- { (videoVisible && videoWidth !== null) && -

{videoWidth}x{videoHeight}

- }
{ !isMe &&
@@ -285,7 +373,6 @@ class VideoView extends React.PureComponent ({ newDisplayName }) => onChangeDisplayName(newDisplayName)} /> - : @@ -412,7 +499,8 @@ VideoView.propTypes = videoCodec : PropTypes.string, onChangeDisplayName : PropTypes.func, children : PropTypes.object, - classes : PropTypes.object.isRequired + classes : PropTypes.object.isRequired, + netInfo : PropTypes.object.isRequired }; export default withStyles(styles)(VideoView); diff --git a/app/src/reducers/rootReducer.js b/app/src/reducers/rootReducer.js index d4025e7..036c6d0 100644 --- a/app/src/reducers/rootReducer.js +++ b/app/src/reducers/rootReducer.js @@ -11,14 +11,16 @@ import chat from './chat'; import toolarea from './toolarea'; import files from './files'; import settings from './settings'; +import transports from './transports'; export default combineReducers({ room, me, producers, + consumers, + transports, peers, lobbyPeers, - consumers, peerVolumes, notifications, chat, diff --git a/app/src/reducers/transports.js b/app/src/reducers/transports.js new file mode 100644 index 0000000..e8682f3 --- /dev/null +++ b/app/src/reducers/transports.js @@ -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;