diff --git a/react/features/base/participants/components/ParticipantView.native.js b/react/features/base/participants/components/ParticipantView.native.js
index b346d895f..300ecf07d 100644
--- a/react/features/base/participants/components/ParticipantView.native.js
+++ b/react/features/base/participants/components/ParticipantView.native.js
@@ -1,19 +1,25 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
+import { Text, View } from 'react-native';
import { connect } from 'react-redux';
-import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
import { prefetch } from '../../../mobile/image-cache';
+
+import { translate } from '../../i18n';
+import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
import {
MEDIA_TYPE,
shouldRenderVideoTrack,
VideoTrack
} from '../../media';
-import { Container } from '../../react';
+import { Container, TintedView } from '../../react';
import { getTrackByMediaTypeAndParticipant } from '../../tracks';
+import {
+ getAvatarURL, getParticipantById, getParticipantDisplayName
+} from '../functions';
+
import Avatar from './Avatar';
-import { getAvatarURL, getParticipantById } from '../functions';
import styles from './styles';
/**
@@ -54,6 +60,13 @@ class ParticipantView extends Component {
*/
_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}.
*/
@@ -90,6 +103,17 @@ class ParticipantView extends Component {
*/
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
* all Videos. For more details, refer to the zOrder property of the
@@ -98,6 +122,50 @@ class ParticipantView extends Component {
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 (
+
+
+ { t(messageKey, { displayName }) }
+
+
+ );
+ }
+
/**
* 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
// waitForVideoStarted because videoTrack's state videoStarted will be
// 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 renderVideo
= !this.props._audioOnly
@@ -125,6 +197,12 @@ class ParticipantView extends Component {
// Is the avatar to be rendered?
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 (
}
+
+ { useTint
+
+ // If the connection has problems, tint the video / avatar.
+ && }
+
+ { this.props.useConnectivityInfoLabel
+ && this._renderConnectionInfo(connectionStatus) }
);
}
@@ -196,10 +282,12 @@ function _mapStateToProps(state, ownProps) {
participantId);
let avatar;
let connectionStatus;
+ let participantName;
if (participant) {
avatar = getAvatarURL(participant);
connectionStatus = participant.connectionStatus;
+ participantName = getParticipantDisplayName(state);
// Avatar (on React Native) now has the ability to generate an
// automatically-colored default image when no URI/URL is specified or
@@ -223,6 +311,7 @@ function _mapStateToProps(state, ownProps) {
_connectionStatus:
connectionStatus
|| JitsiParticipantConnectionStatus.ACTIVE,
+ _participantName: participantName,
_videoTrack:
getTrackByMediaTypeAndParticipant(
state['features/base/tracks'],
@@ -231,4 +320,4 @@ function _mapStateToProps(state, ownProps) {
};
}
-export default connect(_mapStateToProps)(ParticipantView);
+export default translate(connect(_mapStateToProps)(ParticipantView));
diff --git a/react/features/base/participants/components/styles.js b/react/features/base/participants/components/styles.js
index f33fea28c..a90731a00 100644
--- a/react/features/base/participants/components/styles.js
+++ b/react/features/base/participants/components/styles.js
@@ -1,9 +1,31 @@
-import { createStyleSheet } from '../../styles';
+import { BoxModel, ColorPalette, createStyleSheet } from '../../styles';
/**
* The styles of the feature base/participants.
*/
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.
*/
diff --git a/react/features/base/participants/functions.js b/react/features/base/participants/functions.js
index 7fd539104..3cf8ae194 100644
--- a/react/features/base/participants/functions.js
+++ b/react/features/base/participants/functions.js
@@ -122,6 +122,37 @@ export function getParticipantCount(stateful: Object | Function) {
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
diff --git a/react/features/large-video/components/LargeVideo.native.js b/react/features/large-video/components/LargeVideo.native.js
index e20855db8..ccf474557 100644
--- a/react/features/large-video/components/LargeVideo.native.js
+++ b/react/features/large-video/components/LargeVideo.native.js
@@ -41,6 +41,7 @@ class LargeVideo extends Component<*> {
avatarStyle = { styles.avatar }
participantId = { this.props._participantId }
style = { styles.largeVideo }
+ useConnectivityInfoLabel = { true }
zOrder = { 0 } />
);
}