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 10KB

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