remove graphical indicator, refactor only to statistics

auto_join_3.3
Roman Drozd 2020-05-11 21:10:08 +02:00
parent 2a4f660691
commit 59d48253a3
7 changed files with 173 additions and 325 deletions

View File

@ -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)
{

View File

@ -0,0 +1,5 @@
export const addTransportStats = (transport, type) =>
({
type : 'ADD_TRANSPORT_STATS',
payload : { transport, type }
});

View File

@ -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
);
}
}

View File

@ -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 (
<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,
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)));

View File

@ -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
<div className={classes.info}>
<div className={classes.media}>
<div className={classnames(classes.box, 'left', { hidden: !advancedMode })}>
{ audioCodec && <p>{audioCodec}</p> }
<p className={'netInfo'}>
{ audioCodec &&
<React.Fragment>
<span className={'AcodL'}>Acod: </span>
<span className={'Acod'}>
{audioCodec}
</span>
</React.Fragment>
}
{ videoCodec &&
<p>
{videoCodec}
</p>
}
{ videoCodec &&
<React.Fragment>
<span className={'VcodL'}>Vcod: </span>
<span className={'Vcod'}>
{videoCodec}
</span>
</React.Fragment>
}
{ videoMultiLayer &&
<p>
{`current spatial-temporal layers: ${consumerCurrentSpatialLayer} ${consumerCurrentTemporalLayer}`}
<br />
{`preferred spatial-temporal layers: ${consumerPreferredSpatialLayer} ${consumerPreferredTemporalLayer}`}
</p>
}
{ (videoVisible && videoWidth !== null) &&
<React.Fragment>
<span className={'ResL'}>Res: </span>
<span className={'Res'}>
{videoWidth}x{videoHeight}
</span>
</React.Fragment>
}
{ isMe &&
(netInfo.recv && netInfo.send && netInfo.send.iceSelectedTuple) &&
<React.Fragment>
<span className={'RecvL'}>Recv: </span>
<span className={'RecvBps'}>
{(netInfo.recv.sendBitrate/8/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/8/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>
}
</p>
{ (videoVisible && videoWidth !== null) &&
<p>{videoWidth}x{videoHeight}</p>
}
</div>
{ !isMe &&
<div className={classnames(classes.box, 'right')}>
@ -285,7 +373,6 @@ class VideoView extends React.PureComponent
({ newDisplayName }) =>
onChangeDisplayName(newDisplayName)}
/>
<NetworkIndicator />
</React.Fragment>
:
<span className={classes.displayNameStatic}>
@ -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);

View File

@ -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,

View File

@ -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;