import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
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, TintedView } from '../../react';
import { getTrackByMediaTypeAndParticipant } from '../../tracks';
import {
getAvatarURL, getParticipantById, getParticipantDisplayName
} from '../functions';
import Avatar from './Avatar';
import styles from './styles';
/**
* Implements a React Component which depicts a specific participant's avatar
* and video.
*
* @extends Component
*/
class ParticipantView extends Component {
/**
* ParticipantView component's property types.
*
* @static
*/
static propTypes = {
/**
* The indicator which determines whether conferencing is in audio-only
* mode.
*
* @private
*/
_audioOnly: PropTypes.bool,
/**
* The source (e.g. URI, URL) of the avatar image of the participant
* with {@link #participantId}.
*
* @private
*/
_avatar: PropTypes.string,
/**
* The connection status of the participant. Her video will only be
* rendered if the connection status is 'active'; otherwise, the avatar
* will be rendered. If undefined, 'active' is presumed.
*
* @private
*/
_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}.
*/
_videoTrack: PropTypes.object,
/**
* The style, if any, of the avatar in addition to the default style.
*/
avatarStyle: PropTypes.object,
/**
* The ID of the participant (to be) depicted by ParticipantView.
*
* @public
*/
participantId: PropTypes.string,
/**
* True if the avatar of the depicted participant is to be shown should
* the avatar be available and the video of the participant is not to be
* shown; otherwise, false. If undefined, defaults to true.
*/
showAvatar: PropTypes.bool,
/**
* True if the video of the depicted participant is to be shown should
* the video be available. If undefined, defaults to true.
*/
showVideo: PropTypes.bool,
/**
* The style, if any, to apply to ParticipantView in addition to its
* default style.
*/
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
* Video class for React Native.
*/
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()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
_avatar: avatar,
_connectionStatus: connectionStatus,
_videoTrack: videoTrack
} = this.props;
// Is the video to be rendered?
// 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
&& (connectionStatus
=== JitsiParticipantConnectionStatus.ACTIVE)
&& shouldRenderVideoTrack(videoTrack, waitForVideoStarted);
// 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 (
{ renderVideo
// The consumer of this ParticipantView is allowed to forbid
// showing the video if the private logic of this
// ParticipantView determines that the video could be
// rendered.
&& _toBoolean(this.props.showVideo, true)
&& }
{ renderAvatar
// The consumer of this ParticipantView is allowed to forbid
// showing the avatar if the private logic of this
// ParticipantView determines that the avatar could be
// rendered.
&& _toBoolean(this.props.showAvatar, true)
&& }
{ useTint
// If the connection has problems, tint the video / avatar.
&& }
{ this.props.useConnectivityInfoLabel
&& this._renderConnectionInfo(connectionStatus) }
);
}
}
/**
* Converts the specified value to a boolean value. If the specified value is
* undefined, returns the boolean value of undefinedValue.
*
* @param {any} value - The value to convert to a boolean value should it not be
* undefined.
* @param {any} undefinedValue - The value to convert to a boolean value should
* the specified value be undefined.
* @private
* @returns {boolean}
*/
function _toBoolean(value, undefinedValue) {
return Boolean(typeof value === 'undefined' ? undefinedValue : value);
}
/**
* Maps (parts of) the Redux state to the associated ParticipantView's props.
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The React Component props passed to the associated
* (instance of) ParticipantView.
* @private
* @returns {{
* _audioOnly: boolean,
* _avatar: string,
* _connectionStatus: string,
* _videoTrack: Track
* }}
*/
function _mapStateToProps(state, ownProps) {
const { participantId } = ownProps;
const participant
= getParticipantById(
state['features/base/participants'],
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
// when it fails to load. In order to make the coloring permanent(ish)
// per participant, Avatar will need something permanent(ish) per
// perticipant, obviously. A participant's ID is such a piece of data.
// But the local participant changes her ID as she joins, leaves.
// TODO @lyubomir: The participants may change their avatar URLs at
// runtime which means that, if their old and new avatar URLs fail to
// download, Avatar will change their automatically-generated colors.
avatar || participant.local || (avatar = `#${participant.id}`);
// ParticipantView knows before Avatar that an avatar URL will be used
// so it's advisable to prefetch here.
avatar && prefetch({ uri: avatar });
}
return {
_audioOnly: state['features/base/conference'].audioOnly,
_avatar: avatar,
_connectionStatus:
connectionStatus
|| JitsiParticipantConnectionStatus.ACTIVE,
_participantName: participantName,
_videoTrack:
getTrackByMediaTypeAndParticipant(
state['features/base/tracks'],
MEDIA_TYPE.VIDEO,
participantId)
};
}
export default translate(connect(_mapStateToProps)(ParticipantView));