Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

Thumbnail.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. // @flow
  2. import React, { PureComponent } from 'react';
  3. import { View } from 'react-native';
  4. import type { Dispatch } from 'redux';
  5. import { ColorSchemeRegistry } from '../../../base/color-scheme';
  6. import { openDialog } from '../../../base/dialog';
  7. import { MEDIA_TYPE, VIDEO_TYPE } from '../../../base/media';
  8. import {
  9. PARTICIPANT_ROLE,
  10. ParticipantView,
  11. getParticipantCount,
  12. isEveryoneModerator,
  13. pinParticipant,
  14. getParticipantByIdOrUndefined,
  15. getLocalParticipant
  16. } from '../../../base/participants';
  17. import { Container } from '../../../base/react';
  18. import { connect } from '../../../base/redux';
  19. import { StyleType } from '../../../base/styles';
  20. import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
  21. import { ConnectionIndicator } from '../../../connection-indicator';
  22. import { DisplayNameLabel } from '../../../display-name';
  23. import { toggleToolboxVisible } from '../../../toolbox/actions.native';
  24. import { RemoteVideoMenu } from '../../../video-menu';
  25. import ConnectionStatusComponent from '../../../video-menu/components/native/ConnectionStatusComponent';
  26. import SharedVideoMenu from '../../../video-menu/components/native/SharedVideoMenu';
  27. import { SQUARE_TILE_ASPECT_RATIO } from '../../constants';
  28. import AudioMutedIndicator from './AudioMutedIndicator';
  29. import DominantSpeakerIndicator from './DominantSpeakerIndicator';
  30. import ModeratorIndicator from './ModeratorIndicator';
  31. import RaisedHandIndicator from './RaisedHandIndicator';
  32. import ScreenShareIndicator from './ScreenShareIndicator';
  33. import VideoMutedIndicator from './VideoMutedIndicator';
  34. import styles, { AVATAR_SIZE } from './styles';
  35. /**
  36. * Thumbnail component's property types.
  37. */
  38. type Props = {
  39. /**
  40. * Whether local audio (microphone) is muted or not.
  41. */
  42. _audioMuted: boolean,
  43. /**
  44. * Indicates whether the participant is fake.
  45. */
  46. _isFakeParticipant: boolean,
  47. /**
  48. * Indicates whether the participant is screen sharing.
  49. */
  50. _isScreenShare: boolean,
  51. /**
  52. * Indicates whether the participant is local.
  53. */
  54. _local: boolean,
  55. /**
  56. * Shared video local participant owner.
  57. */
  58. _localVideoOwner: boolean,
  59. /**
  60. * The ID of the participant obtain from the participant object in Redux.
  61. *
  62. * NOTE: Generally it should be the same as the participantID prop except the case where the passed
  63. * participantID doesn't corespond to any of the existing participants.
  64. */
  65. _participantId: string,
  66. /**
  67. * Indicates whether the participant is displayed on the large video.
  68. */
  69. _participantInLargeVideo: boolean,
  70. /**
  71. * Indicates whether the participant is pinned or not.
  72. */
  73. _pinned: boolean,
  74. /**
  75. * Whether to show the dominant speaker indicator or not.
  76. */
  77. _renderDominantSpeakerIndicator: boolean,
  78. /**
  79. * Whether to show the moderator indicator or not.
  80. */
  81. _renderModeratorIndicator: boolean,
  82. /**
  83. * The color-schemed stylesheet of the feature.
  84. */
  85. _styles: StyleType,
  86. /**
  87. * Indicates whether the participant is video muted.
  88. */
  89. _videoMuted: boolean,
  90. /**
  91. * If true, there will be no color overlay (tint) on the thumbnail
  92. * indicating the participant associated with the thumbnail is displayed on
  93. * large video. By default there will be a tint.
  94. */
  95. disableTint?: boolean,
  96. /**
  97. * Invoked to trigger state changes in Redux.
  98. */
  99. dispatch: Dispatch<any>,
  100. /**
  101. * The height of the thumnail.
  102. */
  103. height: ?number,
  104. /**
  105. * The ID of the participant related to the thumbnail.
  106. */
  107. participantID: ?string,
  108. /**
  109. * Whether to display or hide the display name of the participant in the thumbnail.
  110. */
  111. renderDisplayName: ?boolean,
  112. /**
  113. * If true, it tells the thumbnail that it needs to behave differently. E.g. react differently to a single tap.
  114. */
  115. tileView?: boolean
  116. };
  117. /**
  118. * React component for video thumbnail.
  119. */
  120. class Thumbnail extends PureComponent<Props> {
  121. /**
  122. * Creates new Thumbnail component.
  123. *
  124. * @param {Props} props - The props of the component.
  125. * @returns {Thumbnail}
  126. */
  127. constructor(props: Props) {
  128. super(props);
  129. this._onClick = this._onClick.bind(this);
  130. this._onThumbnailLongPress = this._onThumbnailLongPress.bind(this);
  131. }
  132. _onClick: () => void;
  133. /**
  134. * Thumbnail click handler.
  135. *
  136. * @returns {void}
  137. */
  138. _onClick() {
  139. const { _participantId, _pinned, dispatch, tileView } = this.props;
  140. if (tileView) {
  141. dispatch(toggleToolboxVisible());
  142. } else {
  143. dispatch(pinParticipant(_pinned ? null : _participantId));
  144. }
  145. }
  146. _onThumbnailLongPress: () => void;
  147. /**
  148. * Thumbnail long press handler.
  149. *
  150. * @returns {void}
  151. */
  152. _onThumbnailLongPress() {
  153. const { _participantId, _local, _isFakeParticipant, _localVideoOwner, dispatch } = this.props;
  154. if (_local) {
  155. dispatch(openDialog(ConnectionStatusComponent, {
  156. participantID: _participantId
  157. }));
  158. } else if (_isFakeParticipant) {
  159. if (_localVideoOwner) {
  160. dispatch(openDialog(SharedVideoMenu, {
  161. _participantId
  162. }));
  163. }
  164. } else {
  165. dispatch(openDialog(RemoteVideoMenu, {
  166. participantId: _participantId
  167. }));
  168. }
  169. }
  170. /**
  171. * Renders the indicators for the thumbnail.
  172. *
  173. * @returns {ReactElement}
  174. */
  175. _renderIndicators() {
  176. const {
  177. _audioMuted: audioMuted,
  178. _isScreenShare: isScreenShare,
  179. _isFakeParticipant,
  180. _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
  181. _renderModeratorIndicator: renderModeratorIndicator,
  182. _participantId: participantId,
  183. _videoMuted: videoMuted
  184. } = this.props;
  185. const indicators = [];
  186. if (renderModeratorIndicator) {
  187. indicators.push(<View style = { styles.moderatorIndicatorContainer }>
  188. <ModeratorIndicator />
  189. </View>);
  190. }
  191. if (!_isFakeParticipant) {
  192. indicators.push(<View
  193. style = { [
  194. styles.thumbnailTopIndicatorContainer,
  195. styles.thumbnailTopLeftIndicatorContainer
  196. ] }>
  197. <RaisedHandIndicator participantId = { participantId } />
  198. { renderDominantSpeakerIndicator && <DominantSpeakerIndicator /> }
  199. </View>);
  200. indicators.push(<View
  201. style = { [
  202. styles.thumbnailTopIndicatorContainer,
  203. styles.thumbnailTopRightIndicatorContainer
  204. ] }>
  205. <ConnectionIndicator participantId = { participantId } />
  206. </View>);
  207. indicators.push(<Container style = { styles.thumbnailIndicatorContainer }>
  208. { audioMuted && <AudioMutedIndicator /> }
  209. { videoMuted && <VideoMutedIndicator /> }
  210. { isScreenShare && <ScreenShareIndicator /> }
  211. </Container>);
  212. }
  213. return indicators;
  214. }
  215. /**
  216. * Implements React's {@link Component#render()}.
  217. *
  218. * @inheritdoc
  219. * @returns {ReactElement}
  220. */
  221. render() {
  222. const {
  223. _isScreenShare: isScreenShare,
  224. _isFakeParticipant,
  225. _participantId: participantId,
  226. _participantInLargeVideo: participantInLargeVideo,
  227. _pinned,
  228. _styles,
  229. disableTint,
  230. height,
  231. renderDisplayName,
  232. tileView
  233. } = this.props;
  234. const styleOverrides = tileView ? {
  235. aspectRatio: SQUARE_TILE_ASPECT_RATIO,
  236. flex: 0,
  237. height,
  238. maxHeight: null,
  239. maxWidth: null,
  240. width: null
  241. } : null;
  242. return (
  243. <Container
  244. onClick = { this._onClick }
  245. onLongPress = { this._onThumbnailLongPress }
  246. style = { [
  247. styles.thumbnail,
  248. _pinned && !tileView ? _styles.thumbnailPinned : null,
  249. styleOverrides
  250. ] }
  251. touchFeedback = { false }>
  252. <ParticipantView
  253. avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
  254. disableVideo = { isScreenShare || _isFakeParticipant }
  255. participantId = { participantId }
  256. style = { _styles.participantViewStyle }
  257. tintEnabled = { participantInLargeVideo && !disableTint }
  258. tintStyle = { _styles.activeThumbnailTint }
  259. zOrder = { 1 } />
  260. {
  261. renderDisplayName
  262. && <Container style = { styles.displayNameContainer }>
  263. <DisplayNameLabel participantId = { participantId } />
  264. </Container>
  265. }
  266. {
  267. this._renderIndicators()
  268. }
  269. </Container>
  270. );
  271. }
  272. }
  273. /**
  274. * Function that maps parts of Redux state tree into component props.
  275. *
  276. * @param {Object} state - Redux state.
  277. * @param {Props} ownProps - Properties of component.
  278. * @returns {Object}
  279. */
  280. function _mapStateToProps(state, ownProps) {
  281. // We need read-only access to the state of features/large-video so that the
  282. // filmstrip doesn't render the video of the participant who is rendered on
  283. // the stage i.e. as a large video.
  284. const largeVideo = state['features/large-video'];
  285. const { ownerId } = state['features/shared-video'];
  286. const tracks = state['features/base/tracks'];
  287. const { participantID } = ownProps;
  288. const participant = getParticipantByIdOrUndefined(state, participantID);
  289. const localParticipantId = getLocalParticipant(state).id;
  290. const id = participant?.id;
  291. const audioTrack
  292. = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
  293. const videoTrack
  294. = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
  295. const videoMuted = videoTrack?.muted ?? true;
  296. const isScreenShare = videoTrack?.videoType === VIDEO_TYPE.DESKTOP;
  297. const participantCount = getParticipantCount(state);
  298. const renderDominantSpeakerIndicator = participant && participant.dominantSpeaker && participantCount > 2;
  299. const _isEveryoneModerator = isEveryoneModerator(state);
  300. const renderModeratorIndicator = !_isEveryoneModerator
  301. && participant?.role === PARTICIPANT_ROLE.MODERATOR;
  302. const participantInLargeVideo = id === largeVideo.participantId;
  303. return {
  304. _audioMuted: audioTrack?.muted ?? true,
  305. _isScreenShare: isScreenShare,
  306. _isFakeParticipant: participant?.isFakeParticipant,
  307. _local: participant?.local,
  308. _localVideoOwner: Boolean(ownerId === localParticipantId),
  309. _participantInLargeVideo: participantInLargeVideo,
  310. _participantId: id,
  311. _pinned: participant?.pinned,
  312. _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
  313. _renderModeratorIndicator: renderModeratorIndicator,
  314. _styles: ColorSchemeRegistry.get(state, 'Thumbnail'),
  315. _videoMuted: videoMuted
  316. };
  317. }
  318. export default connect(_mapStateToProps)(Thumbnail);