diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 6a2d16c..048c46a 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -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)); @@ -3046,7 +3078,7 @@ export default class RoomClient { await this.enableMic(); let autoMuteThreshold = 4; - + if ('autoMuteThreshold' in window.config) { autoMuteThreshold = window.config.autoMuteThreshold; 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 733fbc8..a302dad 100644 --- a/app/src/components/Containers/Me.js +++ b/app/src/components/Containers/Me.js @@ -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 (
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 ); } } diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index 4e7ad76..40dfba6 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -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
- { audioCodec &&

{audioCodec}

} - - { videoCodec && -

- {videoCodec} -

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

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

+ { videoCodec && + + Vcod: + + {videoCodec} + + } { (videoVisible && videoWidth !== null) && -

{videoWidth}x{videoHeight}

+ + Res: + + {videoWidth}x{videoHeight} + + } + + { isMe && !isScreen && + (netInfo.recv && netInfo.send && netInfo.send.iceSelectedTuple) && + + Recv: + + {(netInfo.recv.sendBitrate/1024/1024).toFixed(2)}Mb/s + + + {(netInfo.recv.bytesSent/1024/1024).toFixed(2)}MB + + + Send: + + {(netInfo.send.recvBitrate/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} + + } +
{ showQuality &&
@@ -277,20 +362,24 @@ class VideoView extends React.PureComponent
{ isMe ? - onChangeDisplayName(newDisplayName)} - /> + + + onChangeDisplayName(newDisplayName)} + /> + : {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); diff --git a/app/src/localAudioAnalyzer.js b/app/src/localAudioAnalyzer.js index 4f387c6..53da961 100644 --- a/app/src/localAudioAnalyzer.js +++ b/app/src/localAudioAnalyzer.js @@ -1,7 +1,5 @@ - - class AudioAnalyzer extends EventEmitter - + { constructor() { 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;