[RN] Show an indication when connectivity problems occur

The video will switch to the avatar and be tinted with gray. On the large view,
a text message indicating the user has connectivity issues will be shown.
This commit is contained in:
Saúl Ibarra Corretgé 2017-12-12 16:30:23 +01:00 committed by Paweł Domas
parent ac09233558
commit 5640524647
4 changed files with 148 additions and 5 deletions

View File

@ -1,19 +1,25 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
import { prefetch } from '../../../mobile/image-cache'; import { prefetch } from '../../../mobile/image-cache';
import { translate } from '../../i18n';
import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
import { import {
MEDIA_TYPE, MEDIA_TYPE,
shouldRenderVideoTrack, shouldRenderVideoTrack,
VideoTrack VideoTrack
} from '../../media'; } from '../../media';
import { Container } from '../../react'; import { Container, TintedView } from '../../react';
import { getTrackByMediaTypeAndParticipant } from '../../tracks'; import { getTrackByMediaTypeAndParticipant } from '../../tracks';
import {
getAvatarURL, getParticipantById, getParticipantDisplayName
} from '../functions';
import Avatar from './Avatar'; import Avatar from './Avatar';
import { getAvatarURL, getParticipantById } from '../functions';
import styles from './styles'; import styles from './styles';
/** /**
@ -54,6 +60,13 @@ class ParticipantView extends Component {
*/ */
_connectionStatus: PropTypes.string, _connectionStatus: PropTypes.string,
/**
* The name of the participant which this component represents.
*
* @private
*/
_participantName: PropTypes.string,
/** /**
* The video Track of the participant with {@link #participantId}. * The video Track of the participant with {@link #participantId}.
*/ */
@ -90,6 +103,17 @@ class ParticipantView extends Component {
*/ */
style: PropTypes.object, style: PropTypes.object,
/**
* The function to translate human-readable text.
*/
t: PropTypes.func,
/**
* Indicates if the connectivity info label should be shown, if
* appropriate. It will be shown in case the connection is interrupted.
*/
useConnectivityInfoLabel: PropTypes.bool,
/** /**
* The z-order of the Video of ParticipantView in the stacking space of * The z-order of the Video of ParticipantView in the stacking space of
* all Videos. For more details, refer to the zOrder property of the * all Videos. For more details, refer to the zOrder property of the
@ -98,6 +122,50 @@ class ParticipantView extends Component {
zOrder: PropTypes.number zOrder: PropTypes.number
}; };
/**
* Renders the connection status label, if appropriate.
*
* @param {string} connectionStatus - The status of the participant's
* connection.
* @private
* @returns {ReactElement|null}
*/
_renderConnectionInfo(connectionStatus) {
let messageKey;
switch (connectionStatus) {
case JitsiParticipantConnectionStatus.INACTIVE:
messageKey = 'connection.LOW_BANDWIDTH';
break;
case JitsiParticipantConnectionStatus.INTERRUPTED:
messageKey = 'connection.USER_CONNECTION_INTERRUPTED';
break;
default:
return null;
}
const {
avatarStyle,
_participantName: displayName,
t
} = this.props;
// XXX Consider splitting this component into 2: one for the large
// view and one for the thumbnail. Some of these don't apply to both.
const containerStyle = {
...styles.connectionInfoContainer,
width: avatarStyle.width * 1.5
};
return (
<View style = { containerStyle } >
<Text style = { styles.connectionInfoText } >
{ t(messageKey, { displayName }) }
</Text>
</View>
);
}
/** /**
* Implements React's {@link Component#render()}. * Implements React's {@link Component#render()}.
* *
@ -115,6 +183,10 @@ class ParticipantView extends Component {
// FIXME It's currently impossible to have true as the value of // FIXME It's currently impossible to have true as the value of
// waitForVideoStarted because videoTrack's state videoStarted will be // waitForVideoStarted because videoTrack's state videoStarted will be
// updated only after videoTrack is rendered. // updated only after videoTrack is rendered.
// XXX Note that, unlike on web, we don't render video when the
// connection status is interrupted, this is because the renderer
// doesn't retain the last frame forever, so we would end up with a
// black screen.
const waitForVideoStarted = false; const waitForVideoStarted = false;
const renderVideo const renderVideo
= !this.props._audioOnly = !this.props._audioOnly
@ -125,6 +197,12 @@ class ParticipantView extends Component {
// Is the avatar to be rendered? // Is the avatar to be rendered?
const renderAvatar = Boolean(!renderVideo && avatar); const renderAvatar = Boolean(!renderVideo && avatar);
// If the connection has problems we will "tint" the video / avatar.
const useTint
= connectionStatus === JitsiParticipantConnectionStatus.INACTIVE
|| connectionStatus
=== JitsiParticipantConnectionStatus.INTERRUPTED;
return ( return (
<Container <Container
style = {{ style = {{
@ -154,6 +232,14 @@ class ParticipantView extends Component {
&& <Avatar && <Avatar
style = { this.props.avatarStyle } style = { this.props.avatarStyle }
uri = { avatar } /> } uri = { avatar } /> }
{ useTint
// If the connection has problems, tint the video / avatar.
&& <TintedView /> }
{ this.props.useConnectivityInfoLabel
&& this._renderConnectionInfo(connectionStatus) }
</Container> </Container>
); );
} }
@ -196,10 +282,12 @@ function _mapStateToProps(state, ownProps) {
participantId); participantId);
let avatar; let avatar;
let connectionStatus; let connectionStatus;
let participantName;
if (participant) { if (participant) {
avatar = getAvatarURL(participant); avatar = getAvatarURL(participant);
connectionStatus = participant.connectionStatus; connectionStatus = participant.connectionStatus;
participantName = getParticipantDisplayName(state);
// Avatar (on React Native) now has the ability to generate an // Avatar (on React Native) now has the ability to generate an
// automatically-colored default image when no URI/URL is specified or // automatically-colored default image when no URI/URL is specified or
@ -223,6 +311,7 @@ function _mapStateToProps(state, ownProps) {
_connectionStatus: _connectionStatus:
connectionStatus connectionStatus
|| JitsiParticipantConnectionStatus.ACTIVE, || JitsiParticipantConnectionStatus.ACTIVE,
_participantName: participantName,
_videoTrack: _videoTrack:
getTrackByMediaTypeAndParticipant( getTrackByMediaTypeAndParticipant(
state['features/base/tracks'], state['features/base/tracks'],
@ -231,4 +320,4 @@ function _mapStateToProps(state, ownProps) {
}; };
} }
export default connect(_mapStateToProps)(ParticipantView); export default translate(connect(_mapStateToProps)(ParticipantView));

View File

@ -1,9 +1,31 @@
import { createStyleSheet } from '../../styles'; import { BoxModel, ColorPalette, createStyleSheet } from '../../styles';
/** /**
* The styles of the feature base/participants. * The styles of the feature base/participants.
*/ */
export default createStyleSheet({ export default createStyleSheet({
/**
* Style for the text rendered when there is a connectivity problem.
*/
connectionInfoText: {
color: ColorPalette.white,
fontSize: 12,
marginVertical: BoxModel.margin,
marginHorizontal: BoxModel.margin,
textAlign: 'center'
},
/**
* Style for the container of the text rendered when there is a
* connectivity problem.
*/
connectionInfoContainer: {
alignSelf: 'center',
backgroundColor: ColorPalette.darkGrey,
borderRadius: 20,
marginTop: BoxModel.margin
},
/** /**
* {@code ParticipantView} style. * {@code ParticipantView} style.
*/ */

View File

@ -122,6 +122,37 @@ export function getParticipantCount(stateful: Object | Function) {
return getParticipants(stateful).length; return getParticipants(stateful).length;
} }
/**
* Returns participant's display name.
* FIXME: remove the hardcoded strings once interfaceConfig is stored in redux
* and merge with a similarly named method in conference.js.
*
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state.
* @param {string} id - The ID of the participant's display name to retrieve.
* @private
* @returns {string}
*/
export function getParticipantDisplayName(
stateful: Object | Function, id: string) {
const participant = getParticipantById(stateful, id);
if (participant) {
if (participant.name) {
return participant.name;
}
if (participant.local) {
return typeof interfaceConfig === 'object'
? interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME
: 'me';
}
}
return typeof interfaceConfig === 'object'
? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME
: 'Fellow Jitster';
}
/** /**
* Selectors for getting all known participants with fake participants filtered * Selectors for getting all known participants with fake participants filtered

View File

@ -41,6 +41,7 @@ class LargeVideo extends Component<*> {
avatarStyle = { styles.avatar } avatarStyle = { styles.avatar }
participantId = { this.props._participantId } participantId = { this.props._participantId }
style = { styles.largeVideo } style = { styles.largeVideo }
useConnectivityInfoLabel = { true }
zOrder = { 0 } /> zOrder = { 0 } />
); );
} }