You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ParticipantView.native.js 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. // @flow
  2. import React, { Component } from 'react';
  3. import { Text, View } from 'react-native';
  4. import FastImage from 'react-native-fast-image';
  5. import { connect } from 'react-redux';
  6. import { translate } from '../../i18n';
  7. import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
  8. import {
  9. MEDIA_TYPE,
  10. shouldRenderVideoTrack,
  11. VideoTrack
  12. } from '../../media';
  13. import { Container, TintedView } from '../../react';
  14. import { TestHint } from '../../testing/components';
  15. import { getTrackByMediaTypeAndParticipant } from '../../tracks';
  16. import Avatar from './Avatar';
  17. import {
  18. getAvatarURL,
  19. getParticipantById,
  20. getParticipantDisplayName
  21. } from '../functions';
  22. import styles from './styles';
  23. /**
  24. * The type of the React {@link Component} props of {@link ParticipantView}.
  25. */
  26. type Props = {
  27. /**
  28. * The indicator which determines whether conferencing is in audio-only
  29. * mode.
  30. *
  31. * @private
  32. */
  33. _audioOnly: boolean,
  34. /**
  35. * The source (e.g. URI, URL) of the avatar image of the participant with
  36. * {@link #participantId}.
  37. *
  38. * @private
  39. */
  40. _avatar: string,
  41. /**
  42. * The connection status of the participant. Her video will only be rendered
  43. * if the connection status is 'active'; otherwise, the avatar will be
  44. * rendered. If undefined, 'active' is presumed.
  45. *
  46. * @private
  47. */
  48. _connectionStatus: string,
  49. /**
  50. * The name of the participant which this component represents.
  51. *
  52. * @private
  53. */
  54. _participantName: string,
  55. /**
  56. * The video Track of the participant with {@link #participantId}.
  57. */
  58. _videoTrack: Object,
  59. /**
  60. * The avatar size.
  61. */
  62. avatarSize: number,
  63. /**
  64. * Callback to invoke when the {@code ParticipantView} is clicked/pressed.
  65. */
  66. onPress: Function,
  67. /**
  68. * The ID of the participant (to be) depicted by {@link ParticipantView}.
  69. *
  70. * @public
  71. */
  72. participantId: string,
  73. /**
  74. * The style, if any, to apply to {@link ParticipantView} in addition to its
  75. * default style.
  76. */
  77. style: Object,
  78. /**
  79. * The function to translate human-readable text.
  80. */
  81. t: Function,
  82. /**
  83. * If true, a tinting will be applied to the view, regardless of video or
  84. * avatar is rendered.
  85. */
  86. tintEnabled: boolean,
  87. /**
  88. * The test hint id which can be used to locate the {@code ParticipantView}
  89. * on the jitsi-meet-torture side. If not provided, the
  90. * {@code participantId} with the following format will be used:
  91. * {@code `org.jitsi.meet.Participant#${participantId}`}
  92. */
  93. testHintId: ?string,
  94. /**
  95. * Indicates if the connectivity info label should be shown, if appropriate.
  96. * It will be shown in case the connection is interrupted.
  97. */
  98. useConnectivityInfoLabel: boolean,
  99. /**
  100. * The z-order of the {@link Video} of {@link ParticipantView} in the
  101. * stacking space of all {@code Video}s. For more details, refer to the
  102. * {@code zOrder} property of the {@code Video} class for React Native.
  103. */
  104. zOrder: number,
  105. /**
  106. * Indicates whether zooming (pinch to zoom and/or drag) is enabled.
  107. */
  108. zoomEnabled: boolean
  109. };
  110. /**
  111. * Implements a React Component which depicts a specific participant's avatar
  112. * and video.
  113. *
  114. * @extends Component
  115. */
  116. class ParticipantView extends Component<Props> {
  117. /**
  118. * Renders the connection status label, if appropriate.
  119. *
  120. * @param {string} connectionStatus - The status of the participant's
  121. * connection.
  122. * @private
  123. * @returns {ReactElement|null}
  124. */
  125. _renderConnectionInfo(connectionStatus) {
  126. let messageKey;
  127. switch (connectionStatus) {
  128. case JitsiParticipantConnectionStatus.INACTIVE:
  129. messageKey = 'connection.LOW_BANDWIDTH';
  130. break;
  131. case JitsiParticipantConnectionStatus.INTERRUPTED:
  132. messageKey = 'connection.USER_CONNECTION_INTERRUPTED';
  133. break;
  134. default:
  135. return null;
  136. }
  137. const {
  138. avatarSize,
  139. _participantName: displayName,
  140. t
  141. } = this.props;
  142. // XXX Consider splitting this component into 2: one for the large view
  143. // and one for the thumbnail. Some of these don't apply to both.
  144. const containerStyle = {
  145. ...styles.connectionInfoContainer,
  146. width: avatarSize * 1.5
  147. };
  148. return (
  149. <View
  150. pointerEvents = 'box-none'
  151. style = { containerStyle }>
  152. <Text style = { styles.connectionInfoText }>
  153. { t(messageKey, { displayName }) }
  154. </Text>
  155. </View>
  156. );
  157. }
  158. /**
  159. * Implements React's {@link Component#render()}.
  160. *
  161. * @inheritdoc
  162. * @returns {ReactElement}
  163. */
  164. render() {
  165. const {
  166. onPress,
  167. _avatar: avatar,
  168. _connectionStatus: connectionStatus,
  169. _videoTrack: videoTrack
  170. } = this.props;
  171. // Is the video to be rendered?
  172. // FIXME It's currently impossible to have true as the value of
  173. // waitForVideoStarted because videoTrack's state videoStarted will be
  174. // updated only after videoTrack is rendered.
  175. // XXX Note that, unlike on web, we don't render video when the
  176. // connection status is interrupted, this is because the renderer
  177. // doesn't retain the last frame forever, so we would end up with a
  178. // black screen.
  179. const waitForVideoStarted = false;
  180. const renderVideo
  181. = !this.props._audioOnly
  182. && (connectionStatus
  183. === JitsiParticipantConnectionStatus.ACTIVE)
  184. && shouldRenderVideoTrack(videoTrack, waitForVideoStarted);
  185. // Is the avatar to be rendered?
  186. const renderAvatar = Boolean(!renderVideo && avatar);
  187. // If the connection has problems, we will "tint" the video / avatar.
  188. const useTint
  189. = connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE
  190. || this.props.tintEnabled;
  191. const testHintId
  192. = this.props.testHintId
  193. ? this.props.testHintId
  194. : `org.jitsi.meet.Participant#${this.props.participantId}`;
  195. return (
  196. <Container
  197. onClick = { renderVideo ? undefined : onPress }
  198. style = {{
  199. ...styles.participantView,
  200. ...this.props.style
  201. }}
  202. touchFeedback = { false }>
  203. <TestHint
  204. id = { testHintId }
  205. onPress = { onPress }
  206. value = '' />
  207. { renderVideo
  208. && <VideoTrack
  209. onPress = { onPress }
  210. videoTrack = { videoTrack }
  211. waitForVideoStarted = { waitForVideoStarted }
  212. zOrder = { this.props.zOrder }
  213. zoomEnabled = { this.props.zoomEnabled } /> }
  214. { renderAvatar
  215. && <Avatar
  216. size = { this.props.avatarSize }
  217. uri = { avatar } /> }
  218. { useTint
  219. // If the connection has problems, tint the video / avatar.
  220. && <TintedView /> }
  221. { this.props.useConnectivityInfoLabel
  222. && this._renderConnectionInfo(connectionStatus) }
  223. </Container>
  224. );
  225. }
  226. }
  227. /**
  228. * Maps (parts of) the redux state to the associated {@link ParticipantView}'s
  229. * props.
  230. *
  231. * @param {Object} state - The redux state.
  232. * @param {Object} ownProps - The React {@code Component} props passed to the
  233. * associated (instance of) {@code ParticipantView}.
  234. * @private
  235. * @returns {{
  236. * _audioOnly: boolean,
  237. * _avatar: string,
  238. * _connectionStatus: string,
  239. * _participantName: string,
  240. * _videoTrack: Track
  241. * }}
  242. */
  243. function _mapStateToProps(state, ownProps) {
  244. const { participantId } = ownProps;
  245. const participant = getParticipantById(state, participantId);
  246. let avatar;
  247. let connectionStatus;
  248. let participantName;
  249. if (participant) {
  250. avatar = getAvatarURL(participant);
  251. connectionStatus = participant.connectionStatus;
  252. participantName = getParticipantDisplayName(state, participant.id);
  253. // Avatar (on React Native) now has the ability to generate an
  254. // automatically-colored default image when no URI/URL is specified or
  255. // when it fails to load. In order to make the coloring permanent(ish)
  256. // per participant, Avatar will need something permanent(ish) per
  257. // perticipant, obviously. A participant's ID is such a piece of data.
  258. // But the local participant changes her ID as she joins, leaves.
  259. // TODO @lyubomir: The participants may change their avatar URLs at
  260. // runtime which means that, if their old and new avatar URLs fail to
  261. // download, Avatar will change their automatically-generated colors.
  262. avatar || participant.local || (avatar = `#${participant.id}`);
  263. // ParticipantView knows before Avatar that an avatar URL will be used
  264. // so it's advisable to prefetch here.
  265. avatar && !avatar.startsWith('#')
  266. && FastImage.preload([ { uri: avatar } ]);
  267. }
  268. return {
  269. _audioOnly: state['features/base/conference'].audioOnly,
  270. _avatar: avatar,
  271. _connectionStatus:
  272. connectionStatus
  273. || JitsiParticipantConnectionStatus.ACTIVE,
  274. _participantName: participantName,
  275. _videoTrack:
  276. getTrackByMediaTypeAndParticipant(
  277. state['features/base/tracks'],
  278. MEDIA_TYPE.VIDEO,
  279. participantId)
  280. };
  281. }
  282. export default translate(connect(_mapStateToProps)(ParticipantView));