Merge pull request #349 from havfo/feat-network-indicator
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 * Change probe counting from sec to state inc * Rebuild, fix null error with null * Store probe from zero * Add highestBitrate * Fix eslint errors * remove graphical indicator, refactor only to statistics * uninstall "wifi-network-indicator" package * Remove "netInfo" class * Change "advanced mode" box info "bitrate" from MB/s to Mb/s * fix my own fault * Fix crash when screen sharing is running * Update transport stats only when advancedMode is on * Fix/Mute eslint Multiline support info * Fix lint * Set VideoView/netInfo prop as not required * Fix lint * Set RoomClient/_getTransportStats() as public Co-authored-by: Stefan Otto <stefan.otto@uninett.no>auto_join_3.3
commit
21a56f3b40
|
|
@ -14,6 +14,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;
|
||||
|
||||
|
|
@ -616,6 +617,36 @@ export default class RoomClient
|
|||
});
|
||||
}
|
||||
|
||||
async getTransportStats()
|
||||
{
|
||||
try
|
||||
{
|
||||
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'));
|
||||
}
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('getTransportStats() | failed: %o', error);
|
||||
}
|
||||
}
|
||||
|
||||
async sendRequest(method, data)
|
||||
{
|
||||
logger.debug('sendRequest() [method:"%s", data:"%o"]', method, data);
|
||||
|
|
@ -1043,7 +1074,7 @@ export default class RoomClient
|
|||
{
|
||||
if (volume < this._hark.lastVolume)
|
||||
{
|
||||
volume = this._hark.lastVolume - Math.pow((volume - this._hark.lastVolume)/(100 + this._hark.lastVolume),4)*2;
|
||||
volume = this._hark.lastVolume - Math.pow((volume - this._hark.lastVolume)/(100 + this._hark.lastVolume), 4)*2;
|
||||
}
|
||||
this._hark.lastVolume = volume;
|
||||
store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume));
|
||||
|
|
@ -2092,6 +2123,7 @@ export default class RoomClient
|
|||
{
|
||||
switch (notification.method)
|
||||
{
|
||||
|
||||
case 'enteredLobby':
|
||||
{
|
||||
store.dispatch(roomActions.setInLobby(true));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
export const addTransportStats = (transport, type) =>
|
||||
({
|
||||
type : 'ADD_TRANSPORT_STATS',
|
||||
payload : { transport, type }
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
meProducersSelector,
|
||||
|
|
@ -176,6 +176,7 @@ const Me = (props) =>
|
|||
screenProducer,
|
||||
extraVideoProducers,
|
||||
canShareScreen,
|
||||
transports,
|
||||
noiseVolume,
|
||||
classes
|
||||
} = props;
|
||||
|
|
@ -333,6 +334,20 @@ const Me = (props) =>
|
|||
);
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
let poll;
|
||||
|
||||
const interval = 1000;
|
||||
|
||||
if (advancedMode)
|
||||
{
|
||||
poll = setInterval(() => roomClient.getTransportStats(), interval);
|
||||
}
|
||||
|
||||
return () => clearInterval(poll);
|
||||
}, [ roomClient, advancedMode ]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
|
|
@ -673,6 +688,7 @@ const Me = (props) =>
|
|||
videoVisible={videoVisible}
|
||||
audioCodec={micProducer && micProducer.codec}
|
||||
videoCodec={webcamProducer && webcamProducer.codec}
|
||||
netInfo={transports && transports}
|
||||
audioScore={audioScore}
|
||||
videoScore={videoScore}
|
||||
onChangeDisplayName={(displayName) =>
|
||||
|
|
@ -888,7 +904,8 @@ Me.propTypes =
|
|||
canShareScreen : PropTypes.bool.isRequired,
|
||||
noiseVolume : PropTypes.number,
|
||||
classes : PropTypes.object.isRequired,
|
||||
theme : PropTypes.object.isRequired
|
||||
theme : PropTypes.object.isRequired,
|
||||
transports : PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () =>
|
||||
|
|
@ -916,6 +933,7 @@ const makeMapStateToProps = () =>
|
|||
settings : state.settings,
|
||||
activeSpeaker : state.me.id === state.room.activeSpeakerId,
|
||||
canShareScreen : hasPermission(state),
|
||||
transports : state.transports,
|
||||
noiseVolume : volume
|
||||
};
|
||||
};
|
||||
|
|
@ -937,7 +955,8 @@ export default withRoomContext(connect(
|
|||
Math.round(next.peerVolumes[next.me.id]) &&
|
||||
prev.peers === next.peers &&
|
||||
prev.producers === next.producers &&
|
||||
prev.settings === next.settings
|
||||
prev.settings === next.settings &&
|
||||
prev.transports === next.transports
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const styles = (theme) =>
|
|||
flexDirection : 'column',
|
||||
overflow : 'hidden'
|
||||
},
|
||||
|
||||
video :
|
||||
{
|
||||
flex : '100 100 auto',
|
||||
|
|
@ -75,17 +76,51 @@ const styles = (theme) =>
|
|||
{
|
||||
padding : theme.spacing(0.5),
|
||||
borderRadius : 2,
|
||||
'& p' :
|
||||
{
|
||||
userSelect : 'none',
|
||||
margin : 0,
|
||||
color : 'rgba(255, 255, 255, 0.7)',
|
||||
fontSize : '0.8em'
|
||||
},
|
||||
userSelect : 'none',
|
||||
margin : 0,
|
||||
color : 'rgba(255, 255, 255, 0.7)',
|
||||
fontSize : '0.8em',
|
||||
|
||||
'&.left' :
|
||||
{
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.25)'
|
||||
},
|
||||
{
|
||||
backgroundColor : 'rgba(0, 0, 0, 0.25)',
|
||||
display : 'grid',
|
||||
gap : '1px 5px',
|
||||
|
||||
// eslint-disable-next-line
|
||||
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' }
|
||||
|
||||
},
|
||||
'&.right' :
|
||||
{
|
||||
marginLeft : 'auto',
|
||||
|
|
@ -170,7 +205,8 @@ class VideoView extends React.PureComponent
|
|||
videoCodec,
|
||||
onChangeDisplayName,
|
||||
children,
|
||||
classes
|
||||
classes,
|
||||
netInfo
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
|
|
@ -244,25 +280,74 @@ 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> }
|
||||
|
||||
{ videoCodec &&
|
||||
<p>
|
||||
{videoCodec}
|
||||
</p>
|
||||
{ audioCodec &&
|
||||
<React.Fragment>
|
||||
<span className={'AcodL'}>Acod: </span>
|
||||
<span className={'Acod'}>
|
||||
{audioCodec}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{ videoMultiLayer &&
|
||||
<p>
|
||||
{`current spatial-temporal layers: ${consumerCurrentSpatialLayer} ${consumerCurrentTemporalLayer}`}
|
||||
<br />
|
||||
{`preferred spatial-temporal layers: ${consumerPreferredSpatialLayer} ${consumerPreferredTemporalLayer}`}
|
||||
</p>
|
||||
{ videoCodec &&
|
||||
<React.Fragment>
|
||||
<span className={'VcodL'}>Vcod: </span>
|
||||
<span className={'Vcod'}>
|
||||
{videoCodec}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{ (videoVisible && videoWidth !== null) &&
|
||||
<p>{videoWidth}x{videoHeight}</p>
|
||||
<React.Fragment>
|
||||
<span className={'ResL'}>Res: </span>
|
||||
<span className={'Res'}>
|
||||
{videoWidth}x{videoHeight}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{ isMe && !isScreen &&
|
||||
(netInfo.recv && netInfo.send && netInfo.send.iceSelectedTuple) &&
|
||||
<React.Fragment>
|
||||
<span className={'RecvL'}>Recv: </span>
|
||||
<span className={'RecvBps'}>
|
||||
{(netInfo.recv.sendBitrate/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/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>
|
||||
}
|
||||
|
||||
</div>
|
||||
{ showQuality &&
|
||||
<div className={classnames(classes.box, 'right')}>
|
||||
|
|
@ -277,20 +362,24 @@ 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)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
:
|
||||
<span className={classes.displayNameStatic}>
|
||||
{displayName}
|
||||
|
|
@ -466,7 +555,8 @@ VideoView.propTypes =
|
|||
videoCodec : PropTypes.string,
|
||||
onChangeDisplayName : PropTypes.func,
|
||||
children : PropTypes.object,
|
||||
classes : PropTypes.object.isRequired
|
||||
classes : PropTypes.object.isRequired,
|
||||
netInfo : PropTypes.object
|
||||
};
|
||||
|
||||
export default withStyles(styles)(VideoView);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
class AudioAnalyzer extends EventEmitter
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
Loading…
Reference in New Issue