fix(rn,connection-indicator) align rendering with web

Use the same way for calculating que perceived quality and display it.
This commit is contained in:
Saúl Ibarra Corretgé 2022-11-11 12:11:30 +01:00 committed by Saúl Ibarra Corretgé
parent 48efe36cdf
commit 14fcd153e5
15 changed files with 393 additions and 174 deletions

View File

@ -1,3 +1,8 @@
/**
* IMPORTANT: this file is deprecated. All of these colors should be moved to
* the theme instead.
*/
/** /**
* The application's definition of the default color black. * The application's definition of the default color black.
*/ */
@ -18,29 +23,12 @@ export const ColorPalette = {
* the sake of consistency. * the sake of consistency.
*/ */
black: BLACK, black: BLACK,
blackBlue: 'rgb(0, 3, 6)',
blue: '#17A0DB', blue: '#17A0DB',
blueHighlight: '#1081b2', blueHighlight: '#1081b2',
buttonUnderlay: '#495258',
darkGrey: '#555555', darkGrey: '#555555',
darkBackground: 'rgb(19,21,25)',
green: '#40b183', green: '#40b183',
lightGrey: '#AAAAAA', lightGrey: '#AAAAAA',
overflowMenuItemUnderlay: '#EEEEEE',
red: '#D00000', red: '#D00000',
transparent: 'rgba(0, 0, 0, 0)', transparent: 'rgba(0, 0, 0, 0)',
toggled: 'rgba(255,255,255,.15)', white: '#FFFFFF'
warning: 'rgb(215, 121, 118)',
white: '#FFFFFF',
/**
* These are colors from the atlaskit to be used on mobile, when needed.
*
* FIXME: Maybe a better solution would be good, or a native packaging of
* the respective atlaskit components.
*/
G400: '#00875A', // Slime
N500: '#42526E', // McFanning
R400: '#DE350B', // Red dirt
Y200: '#FFC400' // Pub mix
}; };

View File

@ -2,7 +2,7 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import { statsEmitter } from '../../../connection-indicator'; import statsEmitter from '../../../connection-indicator/statsEmitter';
import { getLocalParticipant } from '../../participants'; import { getLocalParticipant } from '../../participants';
import { connect } from '../../redux'; import { connect } from '../../redux';
import { isTestModeEnabled } from '../functions'; import { isTestModeEnabled } from '../functions';

View File

@ -1,4 +1,5 @@
/* eslint-disable lines-around-comment */ /* eslint-disable lines-around-comment */
import React from 'react'; import React from 'react';
import { StyleProp, Text, View, ViewStyle } from 'react-native'; import { StyleProp, Text, View, ViewStyle } from 'react-native';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
@ -57,7 +58,9 @@ const TitleBar = (props: IProps): JSX.Element => {
<VideoQualityLabel /> <VideoQualityLabel />
</View> </View>
<ConnectionIndicator <ConnectionIndicator
// @ts-ignore
iconStyle = { styles.connectionIndicatorIcon } iconStyle = { styles.connectionIndicatorIcon }
// @ts-ignore
participantId = { localParticipantId } /> participantId = { localParticipantId } />
<View style = { styles.headerLabels as StyleProp<ViewStyle> }> <View style = { styles.headerLabels as StyleProp<ViewStyle> }>
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } /> <RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />

View File

@ -209,7 +209,7 @@ class AbstractConnectionIndicator<P: Props, S: State> extends Component<P, S> {
*/ */
export function mapStateToProps(state: Object) { export function mapStateToProps(state: Object) {
return { return {
_autoHideTimeout: state['features/base/config'].connectionIndicators.autoHideTimeout ?? defaultAutoHideTimeout _autoHideTimeout: state['features/base/config'].connectionIndicators?.autoHideTimeout ?? defaultAutoHideTimeout
}; };
} }

View File

@ -1,3 +0,0 @@
// @flow
export * from './native';

View File

@ -1,3 +0,0 @@
// @flow
export * from './web';

View File

@ -1,68 +0,0 @@
// @flow
import React from 'react';
import { View } from 'react-native';
import { IconConnection } from '../../../base/icons';
import { BaseIndicator } from '../../../base/react';
import { connect } from '../../../base/redux';
import indicatorStyles from '../../../filmstrip/components/native/styles';
import AbstractConnectionIndicator, {
type Props,
type State
} from '../AbstractConnectionIndicator';
import { CONNECTOR_INDICATOR_COLORS, iconStyle } from './styles';
/**
* Implements an indicator to show the quality of the connection of a participant.
*/
class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
/**
* Initializes a new {@code ConnectionIndicator} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this.state = {
autoHideTimeout: undefined,
showIndicator: false,
stats: {}
};
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { showIndicator, stats } = this.state;
const { percent } = stats;
if (!showIndicator || typeof percent === 'undefined') {
return null;
}
// Signal level on a scale 0..2
const signalLevel = Math.floor(percent / 33.4);
return (
<View
style = {{
...indicatorStyles.indicatorContainer,
backgroundColor: CONNECTOR_INDICATOR_COLORS[signalLevel]
}}>
<BaseIndicator
icon = { IconConnection }
iconStyle = { this.props.iconStyle || iconStyle } />
</View>
);
}
}
export default connect()(ConnectionIndicator);

View File

@ -0,0 +1,216 @@
/* eslint-disable lines-around-comment */
import React from 'react';
import { View } from 'react-native';
import { IReduxState } from '../../../app/types';
import { IconConnection } from '../../../base/icons/svg';
import { MEDIA_TYPE } from '../../../base/media/constants';
import {
getLocalParticipant,
getParticipantById,
isScreenShareParticipant
} from '../../../base/participants/functions';
// @ts-ignore
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
import { connect } from '../../../base/redux/functions';
import {
getTrackByMediaTypeAndParticipant
} from '../../../base/tracks/functions.native';
// @ts-ignore
import indicatorStyles from '../../../filmstrip/components/native/styles';
import {
isTrackStreamingStatusInactive,
isTrackStreamingStatusInterrupted
} from '../../functions';
import AbstractConnectionIndicator, {
type Props as AbstractProps,
mapStateToProps as _abstractMapStateToProps
// @ts-ignore
} from '../AbstractConnectionIndicator';
import {
CONNECTOR_INDICATOR_COLORS,
CONNECTOR_INDICATOR_LOST,
CONNECTOR_INDICATOR_OTHER,
iconStyle
} from './styles';
type IProps = AbstractProps & {
/**
* Whether connection indicators are disabled or not.
*/
_connectionIndicatorDisabled: boolean;
/**
* Whether the inactive connection indicator is disabled or not.
*/
_connectionIndicatorInactiveDisabled: boolean;
/**
* Whether the connection is inactive or not.
*/
_isConnectionStatusInactive: boolean;
/**
* Whether the connection is interrupted or not.
*/
_isConnectionStatusInterrupted: boolean;
/**
* Whether the current participant is a virtual screenshare.
*/
_isVirtualScreenshareParticipant: boolean;
/**
* Redux dispatch function.
*/
dispatch: Function;
/**
* Icon style override.
*/
iconStyle: any;
};
type IState = {
autoHideTimeout: number | undefined;
showIndicator: boolean;
stats: any;
};
/**
* Implements an indicator to show the quality of the connection of a participant.
*/
class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
/**
* Initializes a new {@code ConnectionIndicator} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
// @ts-ignore
this.state = {
autoHideTimeout: undefined,
showIndicator: false,
stats: {}
};
}
/**
* Get the icon configuration from CONNECTOR_INDICATOR_COLORS which has a percentage
* that matches or exceeds the passed in percentage. The implementation
* assumes CONNECTOR_INDICATOR_COLORS is already sorted by highest to lowest
* percentage.
*
* @param {number} percent - The connection percentage, out of 100, to find
* the closest matching configuration for.
* @private
* @returns {Object}
*/
_getDisplayConfiguration(percent: number): any {
return CONNECTOR_INDICATOR_COLORS.find(x => percent >= x.percent) || {};
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
_connectionIndicatorInactiveDisabled,
_connectionIndicatorDisabled,
_isVirtualScreenshareParticipant,
_isConnectionStatusInactive,
_isConnectionStatusInterrupted
// @ts-ignore
} = this.props;
const {
showIndicator,
stats
// @ts-ignore
} = this.state;
const { percent } = stats;
if (!showIndicator || typeof percent === 'undefined'
|| _connectionIndicatorDisabled || _isVirtualScreenshareParticipant) {
return null;
}
let indicatorColor;
if (_isConnectionStatusInactive) {
if (_connectionIndicatorInactiveDisabled) {
return null;
}
indicatorColor = CONNECTOR_INDICATOR_OTHER;
} else if (_isConnectionStatusInterrupted) {
indicatorColor = CONNECTOR_INDICATOR_LOST;
} else {
const displayConfig = this._getDisplayConfiguration(percent);
if (!displayConfig) {
return null;
}
indicatorColor = displayConfig.color;
}
return (
<View
style = {{
...indicatorStyles.indicatorContainer,
backgroundColor: indicatorColor
}}>
<BaseIndicator
icon = { IconConnection }
// @ts-ignore
iconStyle = { this.props.iconStyle || iconStyle } />
</View>
);
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {IProps} ownProps - The own props of the component.
* @returns {IProps}
*/
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
const { participantId } = ownProps;
const tracks = state['features/base/tracks'];
const participant = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
const _isVirtualScreenshareParticipant = isScreenShareParticipant(participant);
let _isConnectionStatusInactive;
let _isConnectionStatusInterrupted;
if (!_isVirtualScreenshareParticipant) {
const _videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
_isConnectionStatusInactive = isTrackStreamingStatusInactive(_videoTrack);
_isConnectionStatusInterrupted = isTrackStreamingStatusInterrupted(_videoTrack);
}
return {
..._abstractMapStateToProps(state),
_connectionIndicatorInactiveDisabled:
Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled),
_connectionIndicatorDisabled:
Boolean(state['features/base/config'].connectionIndicators?.disabled),
_isVirtualScreenshareParticipant,
_isConnectionStatusInactive,
_isConnectionStatusInterrupted
};
}
// @ts-ignore
export default connect(_mapStateToProps)(ConnectionIndicator);

View File

@ -1,3 +0,0 @@
// @flow
export { default as ConnectionIndicator } from './ConnectionIndicator';

View File

@ -1,13 +0,0 @@
// @flow
import { ColorPalette } from '../../../base/styles';
export const CONNECTOR_INDICATOR_COLORS = [
ColorPalette.red,
ColorPalette.Y200,
ColorPalette.green
];
export const iconStyle = {
fontSize: 14
};

View File

@ -0,0 +1,32 @@
/* eslint-disable lines-around-comment */
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
// @ts-ignore
import { INDICATOR_DISPLAY_THRESHOLD } from '../AbstractConnectionIndicator';
export const CONNECTOR_INDICATOR_LOST = BaseTheme.palette.ui05;
export const CONNECTOR_INDICATOR_OTHER = BaseTheme.palette.action01;
export const CONNECTOR_INDICATOR_COLORS = [
// Full (3 bars)
{
color: BaseTheme.palette.success01,
percent: INDICATOR_DISPLAY_THRESHOLD
},
// 2 bars.
{
color: BaseTheme.palette.warning01,
percent: 10
},
// 1 bar.
{
color: BaseTheme.palette.iconError,
percent: 0
}
];
export const iconStyle = {
fontSize: 14
};

View File

@ -27,7 +27,8 @@ import {
import AbstractConnectionIndicator, { import AbstractConnectionIndicator, {
type Props as AbstractProps, type Props as AbstractProps,
type State as AbstractState, type State as AbstractState,
INDICATOR_DISPLAY_THRESHOLD INDICATOR_DISPLAY_THRESHOLD,
mapStateToProps as _abstractMapStateToProps
// @ts-ignore // @ts-ignore
} from '../AbstractConnectionIndicator'; } from '../AbstractConnectionIndicator';
@ -386,6 +387,7 @@ export function _mapStateToProps(state: IReduxState, ownProps: Props) {
const _isConnectionStatusInterrupted = isTrackStreamingStatusInterrupted(_videoTrack); const _isConnectionStatusInterrupted = isTrackStreamingStatusInterrupted(_videoTrack);
return { return {
..._abstractMapStateToProps(state),
_connectionIndicatorInactiveDisabled: _connectionIndicatorInactiveDisabled:
Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled), Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled),
_isVirtualScreenshareParticipant: isScreenShareParticipant(participant), _isVirtualScreenshareParticipant: isScreenShareParticipant(participant),

View File

@ -1,5 +0,0 @@
// @flow
export * from './components';
export { default as statsEmitter } from './statsEmitter';

View File

@ -25,7 +25,7 @@ import {
getVideoTrackByParticipant, getVideoTrackByParticipant,
trackStreamingStatusChanged trackStreamingStatusChanged
} from '../../../base/tracks'; } from '../../../base/tracks';
import { ConnectionIndicator } from '../../../connection-indicator'; import ConnectionIndicator from '../../../connection-indicator/components/native/ConnectionIndicator';
import { DisplayNameLabel } from '../../../display-name'; import { DisplayNameLabel } from '../../../display-name';
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions'; import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
import { import {
@ -191,6 +191,8 @@ class Thumbnail extends PureComponent<Props> {
dispatch(showSharedVideoMenu(_participantId)); dispatch(showSharedVideoMenu(_participantId));
} }
// TODO: add support for getting info about the virtual screen shares.
if (!_fakeParticipant) { if (!_fakeParticipant) {
dispatch(showContextMenuDetails(_participantId, _local)); dispatch(showContextMenuDetails(_participantId, _local));
} }

View File

@ -1,17 +1,34 @@
import React, { Component } from 'react'; /* eslint-disable lines-around-comment */
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import { withTheme } from 'react-native-paper'; import { withTheme } from 'react-native-paper';
import { Avatar } from '../../../base/avatar'; import { IReduxState } from '../../../app/types';
import { BottomSheet, hideSheet } from '../../../base/dialog'; // @ts-ignore
import Avatar from '../../../base/avatar/components/Avatar';
import { hideSheet } from '../../../base/dialog/actions';
// @ts-ignore
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
// @ts-ignore
import { bottomSheetStyles } from '../../../base/dialog/components/native/styles'; import { bottomSheetStyles } from '../../../base/dialog/components/native/styles';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n/functions';
import { IconArrowDownLarge, IconArrowUpLarge } from '../../../base/icons'; import { IconArrowDownLarge, IconArrowUpLarge } from '../../../base/icons/svg';
import { getParticipantDisplayName } from '../../../base/participants'; import { MEDIA_TYPE } from '../../../base/media/constants';
import { BaseIndicator } from '../../../base/react'; import { getParticipantDisplayName } from '../../../base/participants/functions';
import { connect } from '../../../base/redux'; // @ts-ignore
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
import { connect } from '../../../base/redux/functions';
import {
getTrackByMediaTypeAndParticipant
} from '../../../base/tracks/functions.native';
import {
isTrackStreamingStatusInactive,
isTrackStreamingStatusInterrupted
} from '../../../connection-indicator/functions';
import statsEmitter from '../../../connection-indicator/statsEmitter'; import statsEmitter from '../../../connection-indicator/statsEmitter';
// @ts-ignore
import styles from './styles'; import styles from './styles';
/** /**
@ -20,62 +37,87 @@ import styles from './styles';
const AVATAR_SIZE = 25; const AVATAR_SIZE = 25;
const CONNECTION_QUALITY = [ const CONNECTION_QUALITY = [
'Low',
'Medium', // Full (3 bars)
'Good' {
msg: 'connectionindicator.quality.good',
percent: 30 // INDICATOR_DISPLAY_THRESHOLD
},
// 2 bars.
{
msg: 'connectionindicator.quality.nonoptimal',
percent: 10
},
// 1 bar.
{
msg: 'connectionindicator.quality.poor',
percent: 0
}
]; ];
export type Props = { type IProps = {
/** /**
* The Redux dispatch function. * Whether this participant's connection is inactive.
*/ */
dispatch: Function, _isConnectionStatusInactive: boolean;
/** /**
* The ID of the participant that this button is supposed to pin. * Whether this participant's connection is interrupted.
*/ */
participantID: string, _isConnectionStatusInterrupted: boolean;
/** /**
* True if the menu is currently open, false otherwise. * True if the menu is currently open, false otherwise.
*/ */
_isOpen: boolean, _isOpen: boolean;
/** /**
* Display name of the participant retrieved from Redux. * Display name of the participant retrieved from Redux.
*/ */
_participantDisplayName: string, _participantDisplayName: string;
/**
* The Redux dispatch function.
*/
dispatch: Function;
/**
* The ID of the participant that this button is supposed to pin.
*/
participantID: string;
/** /**
* The function to be used to translate i18n labels. * The function to be used to translate i18n labels.
*/ */
t: Function, t: Function;
/** /**
* Theme used for styles. * Theme used for styles.
*/ */
theme: Object theme: any;
} };
/** /**
* The type of the React {@code Component} state of {@link ConnectionStatusComponent}. * The type of the React {@code Component} state of {@link ConnectionStatusComponent}.
*/ */
type State = { type IState = {
resolutionString: string, codecString: string;
downloadString: string, connectionString: string;
uploadString: string, downloadString: string;
packetLostDownloadString: string, packetLostDownloadString: string;
packetLostUploadString: string, packetLostUploadString: string;
serverRegionString: string, resolutionString: string;
codecString: string, serverRegionString: string;
connectionString: string uploadString: string;
}; };
/** /**
* Class to implement a popup menu that show the connection statistics. * Class to implement a popup menu that show the connection statistics.
*/ */
class ConnectionStatusComponent extends Component<Props, State> { class ConnectionStatusComponent extends PureComponent<IProps, IState> {
/** /**
* Constructor of the component. * Constructor of the component.
@ -85,7 +127,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* *
* @inheritdoc * @inheritdoc
*/ */
constructor(props: Props) { constructor(props: IProps) {
super(props); super(props);
this._onStatsUpdated = this._onStatsUpdated.bind(this); this._onStatsUpdated = this._onStatsUpdated.bind(this);
@ -121,15 +163,15 @@ class ConnectionStatusComponent extends Component<Props, State> {
<View style = { styles.statsWrapper }> <View style = { styles.statsWrapper }>
<View style = { styles.statsInfoCell }> <View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }> <Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.status')} ` } { t('connectionindicator.status') }
</Text> </Text>
<Text style = { styles.statsInfoText }> <Text style = { styles.statsInfoText }>
{ this.state.connectionString } { t(this.state.connectionString) }
</Text> </Text>
</View> </View>
<View style = { styles.statsInfoCell }> <View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }> <Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.bitrate')}` } { t('connectionindicator.bitrate') }
</Text> </Text>
<BaseIndicator <BaseIndicator
icon = { IconArrowDownLarge } icon = { IconArrowDownLarge }
@ -150,7 +192,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
</View> </View>
<View style = { styles.statsInfoCell }> <View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }> <Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.packetloss')}` } { t('connectionindicator.packetloss') }
</Text> </Text>
<BaseIndicator <BaseIndicator
icon = { IconArrowDownLarge } icon = { IconArrowDownLarge }
@ -171,7 +213,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
</View> </View>
<View style = { styles.statsInfoCell }> <View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }> <Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.resolution')} ` } { t('connectionindicator.resolution') }
</Text> </Text>
<Text style = { styles.statsInfoText }> <Text style = { styles.statsInfoText }>
{ this.state.resolutionString } { this.state.resolutionString }
@ -179,7 +221,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
</View> </View>
<View style = { styles.statsInfoCell }> <View style = { styles.statsInfoCell }>
<Text style = { styles.statsTitleText }> <Text style = { styles.statsTitleText }>
{ `${t('connectionindicator.codecs')}` } { t('connectionindicator.codecs') }
</Text> </Text>
<Text style = { styles.statsInfoText }> <Text style = { styles.statsInfoText }>
{ this.state.codecString } { this.state.codecString }
@ -206,7 +248,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @inheritdoc * @inheritdoc
* returns {void} * returns {void}
*/ */
componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps: IProps) {
if (prevProps.participantID !== this.props.participantID) { if (prevProps.participantID !== this.props.participantID) {
statsEmitter.unsubscribeToClientStats( statsEmitter.unsubscribeToClientStats(
prevProps.participantID, this._onStatsUpdated); prevProps.participantID, this._onStatsUpdated);
@ -215,8 +257,6 @@ class ConnectionStatusComponent extends Component<Props, State> {
} }
} }
_onStatsUpdated: Object => void;
/** /**
* Callback invoked when new connection stats associated with the passed in * Callback invoked when new connection stats associated with the passed in
* user ID are available. Will update the component's display of current * user ID are available. Will update the component's display of current
@ -239,9 +279,8 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private * @private
* @returns {State} * @returns {State}
*/ */
_buildState(stats) { _buildState(stats: any) {
const { download: downloadBitrate, upload: uploadBitrate } = this._extractBitrate(stats) ?? {}; const { download: downloadBitrate, upload: uploadBitrate } = this._extractBitrate(stats) ?? {};
const { download: downloadPacketLost, upload: uploadPacketLost } = this._extractPacketLost(stats) ?? {}; const { download: downloadPacketLost, upload: uploadPacketLost } = this._extractPacketLost(stats) ?? {};
return { return {
@ -254,7 +293,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
? this.state.packetLostUploadString : `${uploadPacketLost}%`, ? this.state.packetLostUploadString : `${uploadPacketLost}%`,
serverRegionString: this._extractServer(stats) ?? this.state.serverRegionString, serverRegionString: this._extractServer(stats) ?? this.state.serverRegionString,
codecString: this._extractCodecs(stats) ?? this.state.codecString, codecString: this._extractCodecs(stats) ?? this.state.codecString,
connectionString: this._extractConnection(stats) ?? this.state.connectionString connectionString: this._extractConnection(stats)
}; };
} }
@ -265,7 +304,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private * @private
* @returns {string} * @returns {string}
*/ */
_extractResolutionString(stats) { _extractResolutionString(stats: any) {
const { framerate, resolution } = stats; const { framerate, resolution } = stats;
const resolutionString = Object.keys(resolution || {}) const resolutionString = Object.keys(resolution || {})
@ -290,7 +329,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private * @private
* @returns {{ download, upload }} * @returns {{ download, upload }}
*/ */
_extractBitrate(stats) { _extractBitrate(stats: any) {
return stats.bitrate; return stats.bitrate;
} }
@ -301,7 +340,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private * @private
* @returns {{ download, upload }} * @returns {{ download, upload }}
*/ */
_extractPacketLost(stats) { _extractPacketLost(stats: any) {
return stats.packetLoss; return stats.packetLoss;
} }
@ -312,7 +351,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private * @private
* @returns {string} * @returns {string}
*/ */
_extractServer(stats) { _extractServer(stats: any) {
return stats.serverRegion; return stats.serverRegion;
} }
@ -323,17 +362,17 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private * @private
* @returns {string} * @returns {string}
*/ */
_extractCodecs(stats) { _extractCodecs(stats: any) {
const { codec } = stats; const { codec } = stats;
let codecString; let codecString;
if (codec) { if (codec) {
const audioCodecs = Object.values(codec) const audioCodecs = Object.values(codec)
.map(c => c.audio) .map((c: any) => c.audio)
.filter(Boolean); .filter(Boolean);
const videoCodecs = Object.values(codec) const videoCodecs = Object.values(codec)
.map(c => c.video) .map((c: any) => c.video)
.filter(Boolean); .filter(Boolean);
if (audioCodecs.length || videoCodecs.length) { if (audioCodecs.length || videoCodecs.length) {
@ -352,14 +391,39 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private * @private
* @returns {string} * @returns {string}
*/ */
_extractConnection(stats) { _extractConnection(stats: any) {
const { connectionQuality } = stats; const { connectionQuality } = stats;
const {
_isConnectionStatusInactive,
_isConnectionStatusInterrupted
} = this.props;
if (connectionQuality) { if (_isConnectionStatusInactive) {
const signalLevel = Math.floor(connectionQuality / 33.4); return 'connectionindicator.quality.inactive';
} else if (_isConnectionStatusInterrupted) {
return CONNECTION_QUALITY[signalLevel]; return 'connectionindicator.quality.lost';
} else if (typeof connectionQuality === 'undefined') {
return 'connectionindicator.quality.good';
} }
const qualityConfig = this._getQualityConfig(connectionQuality);
return qualityConfig.msg;
}
/**
* Get the quality configuration from CONNECTION_QUALITY which has a percentage
* that matches or exceeds the passed in percentage. The implementation
* assumes CONNECTION_QUALITY is already sorted by highest to lowest
* percentage.
*
* @param {number} percent - The connection percentage, out of 100, to find
* the closest matching configuration for.
* @private
* @returns {Object}
*/
_getQualityConfig(percent: number): any {
return CONNECTION_QUALITY.find(x => percent >= x.percent) || {};
} }
/** /**
@ -406,12 +470,19 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private * @private
* @returns {Props} * @returns {Props}
*/ */
function _mapStateToProps(state, ownProps) { function _mapStateToProps(state: IReduxState, ownProps: IProps) {
const { participantID } = ownProps; const { participantID } = ownProps;
const tracks = state['features/base/tracks'];
const _videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
const _isConnectionStatusInactive = isTrackStreamingStatusInactive(_videoTrack);
const _isConnectionStatusInterrupted = isTrackStreamingStatusInterrupted(_videoTrack);
return { return {
_isConnectionStatusInactive,
_isConnectionStatusInterrupted,
_participantDisplayName: getParticipantDisplayName(state, participantID) _participantDisplayName: getParticipantDisplayName(state, participantID)
}; };
} }
// @ts-ignore
export default translate(connect(_mapStateToProps)(withTheme(ConnectionStatusComponent))); export default translate(connect(_mapStateToProps)(withTheme(ConnectionStatusComponent)));