[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:
parent
ac09233558
commit
5640524647
|
@ -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));
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue