123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- // @flow
-
- import React, { Component } from 'react';
- import { Text, View } from 'react-native';
- import { connect } from 'react-redux';
-
- import { translate } from '../../i18n';
- import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
- import {
- MEDIA_TYPE,
- shouldRenderVideoTrack,
- VideoTrack
- } from '../../media';
- import { prefetch } from '../../../mobile/image-cache';
- import { Container, TintedView } from '../../react';
- import { TestHint } from '../../testing/components';
- import { getTrackByMediaTypeAndParticipant } from '../../tracks';
-
- import Avatar from './Avatar';
- import {
- getAvatarURL,
- getParticipantById,
- getParticipantDisplayName
- } from '../functions';
- import styles from './styles';
-
- /**
- * The type of the React {@link Component} props of {@link ParticipantView}.
- */
- type Props = {
-
- /**
- * The indicator which determines whether conferencing is in audio-only
- * mode.
- *
- * @private
- */
- _audioOnly: boolean,
-
- /**
- * 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,
-
- /**
- * The video Track of the participant with {@link #participantId}.
- */
- _videoTrack: Object,
-
- /**
- * The avatar size.
- */
- avatarSize: number,
-
- /**
- * Callback to invoke when the {@code ParticipantView} is clicked/pressed.
- */
- onPress: Function,
-
- /**
- * The ID of the participant (to be) depicted by {@link ParticipantView}.
- *
- * @public
- */
- participantId: 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: boolean,
-
- /**
- * True if the video of the depicted participant is to be shown should the
- * video be available. If undefined, defaults to true.
- */
- showVideo: boolean,
-
- /**
- * The style, if any, to apply to {@link ParticipantView} in addition to its
- * default style.
- */
- style: Object,
-
- /**
- * The function to translate human-readable text.
- */
- t: Function,
-
- /**
- * 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,
-
- /**
- * 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.
- */
- zOrder: number,
-
- /**
- * Indicates whether zooming (pinch to zoom and/or drag) is enabled.
- */
- zoomEnabled: boolean
- };
-
- /**
- * Implements a React Component which depicts a specific participant's avatar
- * and video.
- *
- * @extends Component
- */
- class ParticipantView extends Component<Props> {
-
- /**
- * 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 {
- avatarSize,
- _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: avatarSize * 1.5
- };
-
- return (
- <View
- pointerEvents = 'box-none'
- style = { containerStyle }>
- <Text style = { styles.connectionInfoText }>
- { t(messageKey, { displayName }) }
- </Text>
- </View>
- );
- }
-
- /**
- * Implements React's {@link Component#render()}.
- *
- * @inheritdoc
- * @returns {ReactElement}
- */
- render() {
- const {
- onPress,
- _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;
- let renderVideo
- = !this.props._audioOnly
- && (connectionStatus
- === JitsiParticipantConnectionStatus.ACTIVE)
- && shouldRenderVideoTrack(videoTrack, waitForVideoStarted);
-
- // Is the avatar to be rendered?
- let renderAvatar = Boolean(!renderVideo && avatar);
-
- // 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.
- renderVideo = renderVideo && _toBoolean(this.props.showVideo, true);
-
- // 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.
- renderAvatar = renderAvatar && _toBoolean(this.props.showAvatar, true);
-
- // If the connection has problems, we will "tint" the video / avatar.
- const useTint
- = connectionStatus === JitsiParticipantConnectionStatus.INACTIVE
- || connectionStatus
- === JitsiParticipantConnectionStatus.INTERRUPTED;
-
- const testHintId
- = this.props.testHintId
- ? this.props.testHintId
- : `org.jitsi.meet.Participant#${this.props.participantId}`;
-
- return (
- <Container
- onClick = { renderVideo ? undefined : onPress }
- style = {{
- ...styles.participantView,
- ...this.props.style
- }}
- touchFeedback = { false }>
-
- <TestHint
- id = { testHintId }
- onPress = { onPress }
- value = '' />
-
- { renderVideo
- && <VideoTrack
- onPress = { renderVideo ? onPress : undefined }
- videoTrack = { videoTrack }
- waitForVideoStarted = { waitForVideoStarted }
- zOrder = { this.props.zOrder }
- zoomEnabled = { this.props.zoomEnabled } /> }
-
- { renderAvatar
- && <Avatar
- size = { this.props.avatarSize }
- uri = { avatar } /> }
-
- { useTint
-
- // If the connection has problems, tint the video / avatar.
- && <TintedView /> }
-
- { this.props.useConnectivityInfoLabel
- && this._renderConnectionInfo(connectionStatus) }
- </Container>
- );
- }
- }
-
- /**
- * 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 {@link ParticipantView}'s
- * props.
- *
- * @param {Object} state - The redux state.
- * @param {Object} ownProps - The React {@code Component} props passed to the
- * associated (instance of) {@code ParticipantView}.
- * @private
- * @returns {{
- * _audioOnly: boolean,
- * _avatar: string,
- * _connectionStatus: string,
- * _participantName: string,
- * _videoTrack: Track
- * }}
- */
- function _mapStateToProps(state, ownProps) {
- const { participantId } = ownProps;
- const participant = getParticipantById(state, participantId);
- let avatar;
- let connectionStatus;
- let participantName;
-
- if (participant) {
- avatar = getAvatarURL(participant);
- connectionStatus = participant.connectionStatus;
- participantName = getParticipantDisplayName(state, participant.id);
-
- // 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));
|