2018-02-13 15:55:18 +00:00
|
|
|
// @flow
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
import React, { Component } from 'react';
|
2017-12-12 15:30:23 +00:00
|
|
|
import { Text, View } from 'react-native';
|
2018-07-31 09:44:48 +00:00
|
|
|
import FastImage from 'react-native-fast-image';
|
2016-10-05 14:36:59 +00:00
|
|
|
import { connect } from 'react-redux';
|
|
|
|
|
2017-12-12 15:30:23 +00:00
|
|
|
import { translate } from '../../i18n';
|
|
|
|
import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
|
2016-10-05 14:36:59 +00:00
|
|
|
import {
|
|
|
|
MEDIA_TYPE,
|
|
|
|
VideoTrack
|
2017-02-27 20:37:53 +00:00
|
|
|
} from '../../media';
|
2017-12-12 15:30:23 +00:00
|
|
|
import { Container, TintedView } from '../../react';
|
2019-01-22 10:35:28 +00:00
|
|
|
import { StyleType } from '../../styles';
|
2018-04-18 18:37:33 +00:00
|
|
|
import { TestHint } from '../../testing/components';
|
2017-02-27 20:37:53 +00:00
|
|
|
import { getTrackByMediaTypeAndParticipant } from '../../tracks';
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2018-02-13 15:55:18 +00:00
|
|
|
import Avatar from './Avatar';
|
2017-12-12 15:30:23 +00:00
|
|
|
import {
|
2018-02-13 15:55:18 +00:00
|
|
|
getAvatarURL,
|
|
|
|
getParticipantById,
|
2019-01-25 10:17:58 +00:00
|
|
|
getParticipantDisplayName,
|
|
|
|
shouldRenderParticipantVideo
|
2017-12-12 15:30:23 +00:00
|
|
|
} from '../functions';
|
2017-06-10 22:50:42 +00:00
|
|
|
import styles from './styles';
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
/**
|
2018-02-13 15:55:18 +00:00
|
|
|
* The type of the React {@link Component} props of {@link ParticipantView}.
|
2016-10-05 14:36:59 +00:00
|
|
|
*/
|
2018-02-13 15:55:18 +00:00
|
|
|
type Props = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The source (e.g. URI, URL) of the avatar image of the participant with
|
|
|
|
* {@link #participantId}.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_avatar: 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: string,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The name of the participant which this component represents.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_participantName: string,
|
|
|
|
|
2019-01-25 10:17:58 +00:00
|
|
|
/**
|
|
|
|
* True if the video should be rendered, false otherwise.
|
|
|
|
*/
|
|
|
|
_renderVideo: boolean,
|
|
|
|
|
2018-02-13 15:55:18 +00:00
|
|
|
/**
|
|
|
|
* The video Track of the participant with {@link #participantId}.
|
|
|
|
*/
|
|
|
|
_videoTrack: Object,
|
|
|
|
|
2016-12-01 01:52:39 +00:00
|
|
|
/**
|
2018-02-13 15:55:18 +00:00
|
|
|
* The avatar size.
|
|
|
|
*/
|
|
|
|
avatarSize: number,
|
|
|
|
|
2018-04-06 21:11:01 +00:00
|
|
|
/**
|
|
|
|
* Callback to invoke when the {@code ParticipantView} is clicked/pressed.
|
|
|
|
*/
|
|
|
|
onPress: Function,
|
|
|
|
|
2018-02-13 15:55:18 +00:00
|
|
|
/**
|
|
|
|
* The ID of the participant (to be) depicted by {@link ParticipantView}.
|
2016-12-01 01:52:39 +00:00
|
|
|
*
|
2018-02-13 15:55:18 +00:00
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
participantId: string,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The style, if any, to apply to {@link ParticipantView} in addition to its
|
|
|
|
* default style.
|
2016-12-01 01:52:39 +00:00
|
|
|
*/
|
2018-02-13 15:55:18 +00:00
|
|
|
style: Object,
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2018-02-13 15:55:18 +00:00
|
|
|
/**
|
|
|
|
* The function to translate human-readable text.
|
|
|
|
*/
|
|
|
|
t: Function,
|
|
|
|
|
2018-07-10 15:27:48 +00:00
|
|
|
/**
|
|
|
|
* If true, a tinting will be applied to the view, regardless of video or
|
|
|
|
* avatar is rendered.
|
|
|
|
*/
|
|
|
|
tintEnabled: boolean,
|
|
|
|
|
2019-01-22 10:35:28 +00:00
|
|
|
/**
|
|
|
|
* The style of the tinting when applied.
|
|
|
|
*/
|
|
|
|
tintStyle: StyleType,
|
|
|
|
|
2018-04-18 18:37:33 +00:00
|
|
|
/**
|
|
|
|
* The test hint id which can be used to locate the {@code ParticipantView}
|
|
|
|
* on the jitsi-meet-torture side. If not provided, the
|
|
|
|
* {@code participantId} with the following format will be used:
|
|
|
|
* {@code `org.jitsi.meet.Participant#${participantId}`}
|
|
|
|
*/
|
|
|
|
testHintId: ?string,
|
|
|
|
|
2018-02-13 15:55:18 +00:00
|
|
|
/**
|
|
|
|
* Indicates if the connectivity info label should be shown, if appropriate.
|
|
|
|
* It will be shown in case the connection is interrupted.
|
|
|
|
*/
|
|
|
|
useConnectivityInfoLabel: boolean,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The z-order of the {@link Video} of {@link ParticipantView} in the
|
|
|
|
* stacking space of all {@code Video}s. For more details, refer to the
|
|
|
|
* {@code zOrder} property of the {@code Video} class for React Native.
|
|
|
|
*/
|
2018-04-07 07:52:38 +00:00
|
|
|
zOrder: number,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicates whether zooming (pinch to zoom and/or drag) is enabled.
|
|
|
|
*/
|
|
|
|
zoomEnabled: boolean
|
2018-02-13 15:55:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements a React Component which depicts a specific participant's avatar
|
|
|
|
* and video.
|
|
|
|
*
|
|
|
|
* @extends Component
|
|
|
|
*/
|
|
|
|
class ParticipantView extends Component<Props> {
|
2018-04-07 07:52:38 +00:00
|
|
|
|
2017-12-12 15:30:23 +00:00
|
|
|
/**
|
|
|
|
* 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 {
|
2018-02-05 10:57:40 +00:00
|
|
|
avatarSize,
|
2017-12-12 15:30:23 +00:00
|
|
|
_participantName: displayName,
|
|
|
|
t
|
|
|
|
} = this.props;
|
|
|
|
|
2018-02-13 15:55:18 +00:00
|
|
|
// 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.
|
2017-12-12 15:30:23 +00:00
|
|
|
const containerStyle = {
|
|
|
|
...styles.connectionInfoContainer,
|
2018-02-05 10:57:40 +00:00
|
|
|
width: avatarSize * 1.5
|
2017-12-12 15:30:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2018-04-05 19:46:31 +00:00
|
|
|
<View
|
|
|
|
pointerEvents = 'box-none'
|
|
|
|
style = { containerStyle }>
|
|
|
|
<Text style = { styles.connectionInfoText }>
|
2017-12-12 15:30:23 +00:00
|
|
|
{ t(messageKey, { displayName }) }
|
|
|
|
</Text>
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
/**
|
|
|
|
* Implements React's {@link Component#render()}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
* @returns {ReactElement}
|
|
|
|
*/
|
|
|
|
render() {
|
2017-03-29 09:26:46 +00:00
|
|
|
const {
|
|
|
|
_avatar: avatar,
|
2017-04-03 15:06:39 +00:00
|
|
|
_connectionStatus: connectionStatus,
|
2019-01-25 10:17:58 +00:00
|
|
|
_renderVideo: renderVideo,
|
2019-01-22 10:35:28 +00:00
|
|
|
_videoTrack: videoTrack,
|
|
|
|
onPress,
|
|
|
|
tintStyle
|
2017-03-29 09:26:46 +00:00
|
|
|
} = this.props;
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
const waitForVideoStarted = false;
|
|
|
|
|
|
|
|
// Is the avatar to be rendered?
|
2018-07-10 15:27:48 +00:00
|
|
|
const renderAvatar = Boolean(!renderVideo && avatar);
|
2018-04-06 21:11:01 +00:00
|
|
|
|
|
|
|
// If the connection has problems, we will "tint" the video / avatar.
|
2019-01-22 10:35:28 +00:00
|
|
|
const connectionProblem
|
|
|
|
= connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE;
|
2017-12-12 15:30:23 +00:00
|
|
|
const useTint
|
2019-01-22 10:35:28 +00:00
|
|
|
= connectionProblem || this.props.tintEnabled;
|
2017-12-12 15:30:23 +00:00
|
|
|
|
2018-04-18 18:37:33 +00:00
|
|
|
const testHintId
|
|
|
|
= this.props.testHintId
|
|
|
|
? this.props.testHintId
|
|
|
|
: `org.jitsi.meet.Participant#${this.props.participantId}`;
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
return (
|
|
|
|
<Container
|
2018-04-06 21:11:01 +00:00
|
|
|
onClick = { renderVideo ? undefined : onPress }
|
2016-10-05 14:36:59 +00:00
|
|
|
style = {{
|
|
|
|
...styles.participantView,
|
|
|
|
...this.props.style
|
2018-04-06 21:11:01 +00:00
|
|
|
}}
|
|
|
|
touchFeedback = { false }>
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2018-04-18 18:37:33 +00:00
|
|
|
<TestHint
|
|
|
|
id = { testHintId }
|
|
|
|
onPress = { onPress }
|
|
|
|
value = '' />
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
{ renderVideo
|
|
|
|
&& <VideoTrack
|
2018-06-05 11:45:11 +00:00
|
|
|
onPress = { onPress }
|
2016-10-05 14:36:59 +00:00
|
|
|
videoTrack = { videoTrack }
|
|
|
|
waitForVideoStarted = { waitForVideoStarted }
|
2018-04-07 07:52:38 +00:00
|
|
|
zOrder = { this.props.zOrder }
|
|
|
|
zoomEnabled = { this.props.zoomEnabled } /> }
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
{ renderAvatar
|
|
|
|
&& <Avatar
|
2018-02-05 10:57:40 +00:00
|
|
|
size = { this.props.avatarSize }
|
2016-10-05 14:36:59 +00:00
|
|
|
uri = { avatar } /> }
|
2017-12-12 15:30:23 +00:00
|
|
|
|
|
|
|
{ useTint
|
|
|
|
|
|
|
|
// If the connection has problems, tint the video / avatar.
|
2019-01-22 10:35:28 +00:00
|
|
|
&& <TintedView
|
|
|
|
style = {
|
|
|
|
connectionProblem ? undefined : tintStyle } /> }
|
2017-12-12 15:30:23 +00:00
|
|
|
|
|
|
|
{ this.props.useConnectivityInfoLabel
|
|
|
|
&& this._renderConnectionInfo(connectionStatus) }
|
2016-10-05 14:36:59 +00:00
|
|
|
</Container>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-02-13 15:55:18 +00:00
|
|
|
* Maps (parts of) the redux state to the associated {@link ParticipantView}'s
|
|
|
|
* props.
|
2016-10-05 14:36:59 +00:00
|
|
|
*
|
2018-02-13 15:55:18 +00:00
|
|
|
* @param {Object} state - The redux state.
|
|
|
|
* @param {Object} ownProps - The React {@code Component} props passed to the
|
|
|
|
* associated (instance of) {@code ParticipantView}.
|
2017-01-28 23:34:57 +00:00
|
|
|
* @private
|
2016-10-05 14:36:59 +00:00
|
|
|
* @returns {{
|
|
|
|
* _avatar: string,
|
2017-04-03 15:06:39 +00:00
|
|
|
* _connectionStatus: string,
|
2018-02-13 15:55:18 +00:00
|
|
|
* _participantName: string,
|
2019-01-25 10:17:58 +00:00
|
|
|
* _renderVideo: boolean,
|
2016-10-05 14:36:59 +00:00
|
|
|
* _videoTrack: Track
|
|
|
|
* }}
|
|
|
|
*/
|
2017-01-28 23:34:57 +00:00
|
|
|
function _mapStateToProps(state, ownProps) {
|
2017-02-28 23:12:02 +00:00
|
|
|
const { participantId } = ownProps;
|
2018-05-22 22:41:53 +00:00
|
|
|
const participant = getParticipantById(state, participantId);
|
2017-04-05 09:01:57 +00:00
|
|
|
let avatar;
|
|
|
|
let connectionStatus;
|
2017-12-12 15:30:23 +00:00
|
|
|
let participantName;
|
2017-04-05 09:01:57 +00:00
|
|
|
|
|
|
|
if (participant) {
|
|
|
|
avatar = getAvatarURL(participant);
|
|
|
|
connectionStatus = participant.connectionStatus;
|
2018-02-13 15:55:18 +00:00
|
|
|
participantName = getParticipantDisplayName(state, participant.id);
|
2017-08-03 17:08:34 +00:00
|
|
|
|
|
|
|
// 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}`);
|
2017-08-03 17:43:04 +00:00
|
|
|
|
|
|
|
// ParticipantView knows before Avatar that an avatar URL will be used
|
|
|
|
// so it's advisable to prefetch here.
|
2018-07-31 09:44:48 +00:00
|
|
|
avatar && !avatar.startsWith('#')
|
|
|
|
&& FastImage.preload([ { uri: avatar } ]);
|
2017-04-05 09:01:57 +00:00
|
|
|
}
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
return {
|
2017-04-05 09:01:57 +00:00
|
|
|
_avatar: avatar,
|
|
|
|
_connectionStatus:
|
|
|
|
connectionStatus
|
|
|
|
|| JitsiParticipantConnectionStatus.ACTIVE,
|
2017-12-12 15:30:23 +00:00
|
|
|
_participantName: participantName,
|
2019-01-25 10:17:58 +00:00
|
|
|
_renderVideo: shouldRenderParticipantVideo(state, participantId),
|
2016-10-05 14:36:59 +00:00
|
|
|
_videoTrack:
|
|
|
|
getTrackByMediaTypeAndParticipant(
|
|
|
|
state['features/base/tracks'],
|
|
|
|
MEDIA_TYPE.VIDEO,
|
|
|
|
participantId)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-12-12 15:30:23 +00:00
|
|
|
export default translate(connect(_mapStateToProps)(ParticipantView));
|