feat(stats) add stats for mobile
This commit is contained in:
parent
8d813a499c
commit
5ecb5717c7
|
@ -293,7 +293,7 @@ PODS:
|
|||
- React
|
||||
- react-native-splash-screen (3.2.0):
|
||||
- React
|
||||
- react-native-webrtc (1.87.1):
|
||||
- react-native-webrtc (1.87.2):
|
||||
- React-Core
|
||||
- react-native-webview (11.0.2):
|
||||
- React-Core
|
||||
|
@ -562,7 +562,7 @@ SPEC CHECKSUMS:
|
|||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
|
||||
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
||||
react-native-webrtc: 40eca4cac200fda34fb843da07e3402211bbbd10
|
||||
react-native-webrtc: e6fca8432542dd1c77afa6c59629f0176ed78ee6
|
||||
react-native-webview: b2542d6fd424bcc3e3b2ec5f854f0abb4ec86c87
|
||||
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
|
||||
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
"bandwidth": "Estimated bandwidth:",
|
||||
"bitrate": "Bitrate:",
|
||||
"bridgeCount": "Server count: ",
|
||||
"codecs": "Codecs (A/V): ",
|
||||
"connectedTo": "Connected to:",
|
||||
"framerate": "Frame rate:",
|
||||
"less": "Show less",
|
||||
|
|
|
@ -844,6 +844,7 @@
|
|||
"standardDefinition": "Standard definition"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Connection Info",
|
||||
"domute": "Mute",
|
||||
"domuteOthers": "Mute everyone else",
|
||||
"flip": "Flip",
|
||||
|
|
|
@ -10776,8 +10776,8 @@
|
|||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#8bb653f1d6d450f4d17f56eb6bd4c353138f6829",
|
||||
"from": "github:jitsi/lib-jitsi-meet#8bb653f1d6d450f4d17f56eb6bd4c353138f6829",
|
||||
"version": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
|
||||
"from": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.2",
|
||||
"@jitsi/sdp-interop": "1.0.3",
|
||||
|
@ -14284,9 +14284,9 @@
|
|||
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "1.87.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.87.1.tgz",
|
||||
"integrity": "sha512-XIztid40ohLUoOIDqpavskyAPzopWIjNOoC/y3AtTymt+o+W/rIHZ9Qw8JZCaIjWh2AIrcO2wtb/f1aMWSz2Zw==",
|
||||
"version": "1.87.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.87.2.tgz",
|
||||
"integrity": "sha512-bUMoMvfK17nT8S2w16bpL1uMMyDvDwOmhVjGrP6FDrCS7lAx/w2jVMUtZlNVS6zCJXN92wTkYJ6P3z+nr2hhNg==",
|
||||
"requires": {
|
||||
"base64-js": "^1.1.2",
|
||||
"cross-os": "^1.3.0",
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#8bb653f1d6d450f4d17f56eb6bd4c353138f6829",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#310983c5b0398d213a2ca054c2eb0e9797fa1ae0",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.19",
|
||||
"moment": "2.19.4",
|
||||
|
@ -84,7 +84,7 @@
|
|||
"react-native-svg-transformer": "0.14.3",
|
||||
"react-native-url-polyfill": "1.2.0",
|
||||
"react-native-watch-connectivity": "0.4.3",
|
||||
"react-native-webrtc": "1.87.1",
|
||||
"react-native-webrtc": "1.87.2",
|
||||
"react-native-webview": "11.0.2",
|
||||
"react-native-youtube-iframe": "1.2.3",
|
||||
"react-redux": "7.1.0",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M12 24l-8-9h6v-15h4v15h6z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 128 B |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M12 0l8 9h-6v15h-4v-15h-6z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 129 B |
|
@ -4,8 +4,10 @@ export { default as IconAdd } from './add.svg';
|
|||
export { default as IconAddPeople } from './link.svg';
|
||||
export { default as IconArrowBack } from './arrow_back.svg';
|
||||
export { default as IconArrowDown } from './arrow_down.svg';
|
||||
export { default as IconArrowDownLarge } from './arrow_down_large.svg';
|
||||
export { default as IconArrowDownSmall } from './arrow-down-small.svg';
|
||||
export { default as IconArrowUp } from './arrow_up.svg';
|
||||
export { default as IconArrowUpLarge } from './arrow_up_large.svg';
|
||||
export { default as IconArrowLeft } from './arrow-left.svg';
|
||||
export { default as IconAudioOnly } from './visibility.svg';
|
||||
export { default as IconAudioOnlyOff } from './visibility-off.svg';
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Icon } from '../../../icons';
|
|||
import { type StyleType } from '../../../styles';
|
||||
|
||||
import styles from './indicatorstyles';
|
||||
import { BASE_INDICATOR } from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
|
@ -40,7 +41,9 @@ export default class BaseIndicator extends Component<Props> {
|
|||
const { highlight, icon, iconStyle } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { highlight ? styles.highlightedIndicator : null }>
|
||||
<View
|
||||
style = { [ BASE_INDICATOR,
|
||||
highlight ? styles.highlightedIndicator : null ] }>
|
||||
<Icon
|
||||
src = { icon }
|
||||
style = { [
|
||||
|
|
|
@ -208,6 +208,11 @@ export const TINTED_VIEW_DEFAULT = {
|
|||
opacity: 0.8
|
||||
};
|
||||
|
||||
export const BASE_INDICATOR = {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the generic React {@code Component}s implemented by the feature
|
||||
* base/react.
|
||||
|
|
|
@ -21,6 +21,7 @@ import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
|||
import { ConnectionIndicator } from '../../../connection-indicator';
|
||||
import { DisplayNameLabel } from '../../../display-name';
|
||||
import { RemoteVideoMenu } from '../../../remote-video-menu';
|
||||
import ConnectionStatusComponent from '../../../remote-video-menu/components/native/ConnectionStatusComponent';
|
||||
import { toggleToolboxVisible } from '../../../toolbox/actions.native';
|
||||
|
||||
import AudioMutedIndicator from './AudioMutedIndicator';
|
||||
|
@ -54,7 +55,7 @@ type Props = {
|
|||
/**
|
||||
* Handles long press on the thumbnail.
|
||||
*/
|
||||
_onShowRemoteVideoMenu: ?Function,
|
||||
_onThumbnailLongPress: ?Function,
|
||||
|
||||
/**
|
||||
* Whether to show the dominant speaker indicator or not.
|
||||
|
@ -120,7 +121,7 @@ function Thumbnail(props: Props) {
|
|||
_audioMuted: audioMuted,
|
||||
_largeVideo: largeVideo,
|
||||
_onClick,
|
||||
_onShowRemoteVideoMenu,
|
||||
_onThumbnailLongPress,
|
||||
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
||||
_renderModeratorIndicator: renderModeratorIndicator,
|
||||
_styles,
|
||||
|
@ -140,7 +141,7 @@ function Thumbnail(props: Props) {
|
|||
return (
|
||||
<Container
|
||||
onClick = { _onClick }
|
||||
onLongPress = { participant.local ? undefined : _onShowRemoteVideoMenu }
|
||||
onLongPress = { _onThumbnailLongPress }
|
||||
style = { [
|
||||
styles.thumbnail,
|
||||
participant.pinned && !tileView
|
||||
|
@ -230,12 +231,18 @@ function _mapDispatchToProps(dispatch: Function, ownProps): Object {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onShowRemoteVideoMenu() {
|
||||
_onThumbnailLongPress() {
|
||||
const { participant } = ownProps;
|
||||
|
||||
dispatch(openDialog(RemoteVideoMenu, {
|
||||
participant
|
||||
}));
|
||||
if (participant.local) {
|
||||
dispatch(openDialog(ConnectionStatusComponent, {
|
||||
participantID: participant.id
|
||||
}));
|
||||
} else {
|
||||
dispatch(openDialog(RemoteVideoMenu, {
|
||||
participant
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// @flow
|
||||
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconInfo } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
|
||||
import ConnectionStatusComponent from './ConnectionStatusComponent';
|
||||
|
||||
export type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The redux {@code 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.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* A remote video menu button which shows the connection statistics.
|
||||
*/
|
||||
class ConnectionStatusButton extends AbstractButton<Props, *> {
|
||||
icon = IconInfo;
|
||||
label = 'videothumbnail.connectionInfo';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and kicks the participant.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
dispatch(openDialog(ConnectionStatusComponent, {
|
||||
participantID
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(ConnectionStatusButton));
|
|
@ -0,0 +1,431 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { BottomSheet, isDialogOpen, hideDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconArrowDownLarge, IconArrowUpLarge } from '../../../base/icons';
|
||||
import { getParticipantDisplayName } from '../../../base/participants';
|
||||
import { BaseIndicator } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType, ColorPalette } from '../../../base/styles';
|
||||
import statsEmitter from '../../../connection-indicator/statsEmitter';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Size of the rendered avatar in the menu.
|
||||
*/
|
||||
const AVATAR_SIZE = 25;
|
||||
|
||||
const CONNECTION_QUALITY = [
|
||||
'Low',
|
||||
'Medium',
|
||||
'Good'
|
||||
];
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant that this button is supposed to pin.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the BottomSheet.
|
||||
*/
|
||||
_bottomSheetStyles: StyleType,
|
||||
|
||||
/**
|
||||
* True if the menu is currently open, false otherwise.
|
||||
*/
|
||||
_isOpen: boolean,
|
||||
|
||||
/**
|
||||
* Display name of the participant retreived from Redux.
|
||||
*/
|
||||
_participantDisplayName: string,
|
||||
|
||||
/**
|
||||
* The function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link ConnectionStatusComponent}.
|
||||
*/
|
||||
type State = {
|
||||
resolutionString: string,
|
||||
downloadString: string,
|
||||
uploadString: string,
|
||||
packetLostDownloadString: string,
|
||||
packetLostUploadString: string,
|
||||
serverRegionString: string,
|
||||
codecString: string,
|
||||
connectionString: string
|
||||
};
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let ConnectionStatusComponent_;
|
||||
|
||||
/**
|
||||
* Class to implement a popup menu that show the connection statistics.
|
||||
*/
|
||||
class ConnectionStatusComponent extends Component<Props, State> {
|
||||
|
||||
/**
|
||||
* Constructor of the component.
|
||||
*
|
||||
* @param {P} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onStatsUpdated = this._onStatsUpdated.bind(this);
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._renderMenuHeader = this._renderMenuHeader.bind(this);
|
||||
|
||||
this.state = {
|
||||
resolutionString: 'N/A',
|
||||
downloadString: 'N/A',
|
||||
uploadString: 'N/A',
|
||||
packetLostDownloadString: 'N/A',
|
||||
packetLostUploadString: 'N/A',
|
||||
serverRegionString: 'N/A',
|
||||
codecString: 'N/A',
|
||||
connectionString: 'N/A'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
render(): React$Node {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
onCancel = { this._onCancel }
|
||||
renderHeader = { this._renderMenuHeader }>
|
||||
<View style = { styles.statsWrapper }>
|
||||
<View style = { styles.statsInfoCell }>
|
||||
<Text style = { styles.statsTitleText }>
|
||||
{ `${t('connectionindicator.status')} ` }
|
||||
</Text>
|
||||
<Text style = { styles.statsInfoText }>
|
||||
{ this.state.connectionString }
|
||||
</Text>
|
||||
</View>
|
||||
<View style = { styles.statsInfoCell }>
|
||||
<Text style = { styles.statsTitleText }>
|
||||
{ `${t('connectionindicator.bitrate')}` }
|
||||
</Text>
|
||||
<BaseIndicator
|
||||
icon = { IconArrowDownLarge }
|
||||
iconStyle = {{
|
||||
color: ColorPalette.darkGrey
|
||||
}} />
|
||||
<Text style = { styles.statsInfoText }>
|
||||
{ this.state.downloadString }
|
||||
</Text>
|
||||
<BaseIndicator
|
||||
icon = { IconArrowUpLarge }
|
||||
iconStyle = {{
|
||||
color: ColorPalette.darkGrey
|
||||
}} />
|
||||
<Text style = { styles.statsInfoText }>
|
||||
{ `${this.state.uploadString} Kbps` }
|
||||
</Text>
|
||||
</View>
|
||||
<View style = { styles.statsInfoCell }>
|
||||
<Text style = { styles.statsTitleText }>
|
||||
{ `${t('connectionindicator.packetloss')}` }
|
||||
</Text>
|
||||
<BaseIndicator
|
||||
icon = { IconArrowDownLarge }
|
||||
iconStyle = {{
|
||||
color: ColorPalette.darkGrey
|
||||
}} />
|
||||
<Text style = { styles.statsInfoText }>
|
||||
{ this.state.packetLostDownloadString }
|
||||
</Text>
|
||||
<BaseIndicator
|
||||
icon = { IconArrowUpLarge }
|
||||
iconStyle = {{
|
||||
color: ColorPalette.darkGrey
|
||||
}} />
|
||||
<Text style = { styles.statsInfoText }>
|
||||
{ this.state.packetLostUploadString }
|
||||
</Text>
|
||||
</View>
|
||||
<View style = { styles.statsInfoCell }>
|
||||
<Text style = { styles.statsTitleText }>
|
||||
{ `${t('connectionindicator.resolution')} ` }
|
||||
</Text>
|
||||
<Text style = { styles.statsInfoText }>
|
||||
{ this.state.resolutionString }
|
||||
</Text>
|
||||
</View>
|
||||
<View style = { styles.statsInfoCell }>
|
||||
<Text style = { styles.statsTitleText }>
|
||||
{ `${t('connectionindicator.codecs')}` }
|
||||
</Text>
|
||||
<Text style = { styles.statsInfoText }>
|
||||
{ this.state.codecString }
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts listening for stat updates.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
statsEmitter.subscribeToClientStats(
|
||||
this.props.participantID, this._onStatsUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates which user's stats are being listened to.
|
||||
*
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.participantID !== this.props.participantID) {
|
||||
statsEmitter.unsubscribeToClientStats(
|
||||
prevProps.participantID, this._onStatsUpdated);
|
||||
statsEmitter.subscribeToClientStats(
|
||||
this.props.participantID, this._onStatsUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
_onStatsUpdated: Object => void;
|
||||
|
||||
/**
|
||||
* Callback invoked when new connection stats associated with the passed in
|
||||
* user ID are available. Will update the component's display of current
|
||||
* statistics.
|
||||
*
|
||||
* @param {Object} stats - Connection stats from the library.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStatsUpdated(stats = {}) {
|
||||
const newState = this._buildState(stats);
|
||||
|
||||
this.setState(newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts statistics and builds the state object.
|
||||
*
|
||||
* @param {Object} stats - Connection stats from the library.
|
||||
* @private
|
||||
* @returns {State}
|
||||
*/
|
||||
_buildState(stats) {
|
||||
const { download: downloadBitrate, upload: uploadBitrate } = this._extractBitrate(stats) ?? {};
|
||||
|
||||
const { download: downloadPacketLost, upload: uploadPacketLost } = this._extractPacketLost(stats) ?? {};
|
||||
|
||||
return {
|
||||
resolutionString: this._extractResolutionString(stats) ?? this.state.resolutionString,
|
||||
downloadString: downloadBitrate ?? this.state.downloadString,
|
||||
uploadString: uploadBitrate ?? this.state.uploadString,
|
||||
packetLostDownloadString: downloadPacketLost === undefined
|
||||
? this.state.packetLostDownloadString : `${downloadPacketLost}%`,
|
||||
packetLostUploadString: uploadPacketLost === undefined
|
||||
? this.state.packetLostUploadString : `${uploadPacketLost}%`,
|
||||
serverRegionString: this._extractServer(stats) ?? this.state.serverRegionString,
|
||||
codecString: this._extractCodecs(stats) ?? this.state.codecString,
|
||||
connectionString: this._extractConnection(stats) ?? this.state.connectionString
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the resolution and framerate.
|
||||
*
|
||||
* @param {Object} stats - Connection stats from the library.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_extractResolutionString(stats) {
|
||||
const { framerate, resolution } = stats;
|
||||
|
||||
const resolutionString = Object.keys(resolution || {})
|
||||
.map(ssrc => {
|
||||
const { width, height } = resolution[ssrc];
|
||||
|
||||
return `${width}x${height}`;
|
||||
})
|
||||
.join(', ') || null;
|
||||
|
||||
const frameRateString = Object.keys(framerate || {})
|
||||
.map(ssrc => framerate[ssrc])
|
||||
.join(', ') || null;
|
||||
|
||||
return resolutionString && frameRateString ? `${resolutionString}@${frameRateString}fps` : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the download and upload bitrates.
|
||||
*
|
||||
* @param {Object} stats - Connection stats from the library.
|
||||
* @private
|
||||
* @returns {{ download, upload }}
|
||||
*/
|
||||
_extractBitrate(stats) {
|
||||
return stats.bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the download and upload packet lost.
|
||||
*
|
||||
* @param {Object} stats - Connection stats from the library.
|
||||
* @private
|
||||
* @returns {{ download, upload }}
|
||||
*/
|
||||
_extractPacketLost(stats) {
|
||||
return stats.packetLoss;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the server name.
|
||||
*
|
||||
* @param {Object} stats - Connection stats from the library.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_extractServer(stats) {
|
||||
return stats.serverRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the audio and video codecs names.
|
||||
*
|
||||
* @param {Object} stats - Connection stats from the library.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_extractCodecs(stats) {
|
||||
const { codec } = stats;
|
||||
|
||||
let codecString;
|
||||
|
||||
// Only report one codec, in case there are multiple for a user.
|
||||
Object.keys(codec || {})
|
||||
.forEach(ssrc => {
|
||||
const { audio, video } = codec[ssrc];
|
||||
|
||||
codecString = `${audio}, ${video}`;
|
||||
});
|
||||
|
||||
return codecString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the connection percentage and sets connection quality.
|
||||
*
|
||||
* @param {Object} stats - Connection stats from the library.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_extractConnection(stats) {
|
||||
const { connectionQuality } = stats;
|
||||
|
||||
if (connectionQuality) {
|
||||
const signalLevel = Math.floor(connectionQuality / 33.4);
|
||||
|
||||
return CONNECTION_QUALITY[signalLevel];
|
||||
}
|
||||
}
|
||||
|
||||
_onCancel: () => boolean;
|
||||
|
||||
/**
|
||||
* Callback to hide the {@code ConnectionStatusComponent}.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onCancel() {
|
||||
statsEmitter.unsubscribeToClientStats(
|
||||
this.props.participantID, this._onStatsUpdated);
|
||||
|
||||
if (this.props._isOpen) {
|
||||
this.props.dispatch(hideDialog(ConnectionStatusComponent_));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_renderMenuHeader: () => React$Element<any>;
|
||||
|
||||
/**
|
||||
* Function to render the menu's header.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderMenuHeader() {
|
||||
const { _bottomSheetStyles, participantID } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
_bottomSheetStyles.sheet,
|
||||
styles.participantNameContainer ] }>
|
||||
<Avatar
|
||||
participantId = { participantID }
|
||||
size = { AVATAR_SIZE } />
|
||||
<Text style = { styles.participantNameLabel }>
|
||||
{ this.props._participantDisplayName }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that maps parts of Redux state tree into component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @param {Object} ownProps - Properties of component.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const { participantID } = ownProps;
|
||||
|
||||
return {
|
||||
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
|
||||
_isOpen: isDialogOpen(state, ConnectionStatusComponent_),
|
||||
_participantDisplayName: getParticipantDisplayName(state, participantID)
|
||||
};
|
||||
}
|
||||
|
||||
ConnectionStatusComponent_ = translate(connect(_mapStateToProps)(ConnectionStatusComponent));
|
||||
|
||||
export default ConnectionStatusComponent_;
|
|
@ -13,6 +13,7 @@ import { StyleType } from '../../../base/styles';
|
|||
import { PrivateMessageButton } from '../../../chat';
|
||||
import { hideRemoteVideoMenu } from '../../actions';
|
||||
|
||||
import ConnectionStatusButton from './ConnectionStatusButton';
|
||||
import GrantModeratorButton from './GrantModeratorButton';
|
||||
import KickButton from './KickButton';
|
||||
import MuteButton from './MuteButton';
|
||||
|
@ -106,6 +107,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
|
|||
<PinButton { ...buttonProps } />
|
||||
<PrivateMessageButton { ...buttonProps } />
|
||||
<MuteEveryoneElseButton { ...buttonProps } />
|
||||
<ConnectionStatusButton { ...buttonProps } />
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,5 +25,28 @@ export default createStyleSheet({
|
|||
fontSize: MD_FONT_SIZE,
|
||||
marginLeft: MD_ITEM_MARGIN_PADDING,
|
||||
opacity: 0.90
|
||||
},
|
||||
|
||||
statsTitleText: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
marginRight: 3
|
||||
},
|
||||
|
||||
statsInfoText: {
|
||||
fontSize: 16,
|
||||
marginRight: 2,
|
||||
marginLeft: 2
|
||||
},
|
||||
|
||||
statsInfoCell: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
height: 30,
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
|
||||
statsWrapper: {
|
||||
marginVertical: 10
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue