您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

Thumbnail.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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. key = 'top-left-indicators'
  194. style = { [
  195. styles.thumbnailTopIndicatorContainer,
  196. styles.thumbnailTopLeftIndicatorContainer
  197. ] }>
  198. <RaisedHandIndicator participantId = { participantId } />
  199. { renderDominantSpeakerIndicator && <DominantSpeakerIndicator /> }
  200. </View>);
  201. indicators.push(<View
  202. key = 'top-right-indicators'
  203. style = { [
  204. styles.thumbnailTopIndicatorContainer,
  205. styles.thumbnailTopRightIndicatorContainer
  206. ] }>
  207. <ConnectionIndicator participantId = { participantId } />
  208. </View>);
  209. indicators.push(<Container
  210. key = 'bottom-indicators'
  211. style = { styles.thumbnailIndicatorContainer }>
  212. { audioMuted && <AudioMutedIndicator /> }
  213. { videoMuted && <VideoMutedIndicator /> }
  214. { isScreenShare && <ScreenShareIndicator /> }
  215. </Container>);
  216. }
  217. return indicators;
  218. }
  219. /**
  220. * Implements React's {@link Component#render()}.
  221. *
  222. * @inheritdoc
  223. * @returns {ReactElement}
  224. */
  225. render() {
  226. const {
  227. _isScreenShare: isScreenShare,
  228. _isFakeParticipant,
  229. _participantId: participantId,
  230. _participantInLargeVideo: participantInLargeVideo,
  231. _pinned,
  232. _styles,
  233. disableTint,
  234. height,
  235. renderDisplayName,
  236. tileView
  237. } = this.props;
  238. const styleOverrides = tileView ? {
  239. aspectRatio: SQUARE_TILE_ASPECT_RATIO,
  240. flex: 0,
  241. height,
  242. maxHeight: null,
  243. maxWidth: null,
  244. width: null
  245. } : null;
  246. return (
  247. <Container
  248. onClick = { this._onClick }
  249. onLongPress = { this._onThumbnailLongPress }
  250. style = { [
  251. styles.thumbnail,
  252. _pinned && !tileView ? _styles.thumbnailPinned : null,
  253. styleOverrides
  254. ] }
  255. touchFeedback = { false }>
  256. <ParticipantView
  257. avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
  258. disableVideo = { isScreenShare || _isFakeParticipant }
  259. participantId = { participantId }
  260. style = { _styles.participantViewStyle }
  261. tintEnabled = { participantInLargeVideo && !disableTint }
  262. tintStyle = { _styles.activeThumbnailTint }
  263. zOrder = { 1 } />
  264. {
  265. renderDisplayName
  266. && <Container style = { styles.displayNameContainer }>
  267. <DisplayNameLabel participantId = { participantId } />
  268. </Container>
  269. }
  270. {
  271. this._renderIndicators()
  272. }
  273. </Container>
  274. );
  275. }
  276. }
  277. /**
  278. * Function that maps parts of Redux state tree into component props.
  279. *
  280. * @param {Object} state - Redux state.
  281. * @param {Props} ownProps - Properties of component.
  282. * @returns {Object}
  283. */
  284. function _mapStateToProps(state, ownProps) {
  285. // We need read-only access to the state of features/large-video so that the
  286. // filmstrip doesn't render the video of the participant who is rendered on
  287. // the stage i.e. as a large video.
  288. const largeVideo = state['features/large-video'];
  289. const { ownerId } = state['features/shared-video'];
  290. const tracks = state['features/base/tracks'];
  291. const { participantID } = ownProps;
  292. const participant = getParticipantByIdOrUndefined(state, participantID);
  293. const localParticipantId = getLocalParticipant(state).id;
  294. const id = participant?.id;
  295. const audioTrack
  296. = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
  297. const videoTrack
  298. = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
  299. const videoMuted = videoTrack?.muted ?? true;
  300. const isScreenShare = videoTrack?.videoType === VIDEO_TYPE.DESKTOP;
  301. const participantCount = getParticipantCount(state);
  302. const renderDominantSpeakerIndicator = participant && participant.dominantSpeaker && participantCount > 2;
  303. const _isEveryoneModerator = isEveryoneModerator(state);
  304. const renderModeratorIndicator = !_isEveryoneModerator
  305. && participant?.role === PARTICIPANT_ROLE.MODERATOR;
  306. const participantInLargeVideo = id === largeVideo.participantId;
  307. return {
  308. _audioMuted: audioTrack?.muted ?? true,
  309. _isFakeParticipant: participant?.isFakeParticipant,
  310. _isScreenShare: isScreenShare,
  311. _local: participant?.local,
  312. _localVideoOwner: Boolean(ownerId === localParticipantId),
  313. _participantInLargeVideo: participantInLargeVideo,
  314. _participantId: id,
  315. _pinned: participant?.pinned,
  316. _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
  317. _renderModeratorIndicator: renderModeratorIndicator,
  318. _styles: ColorSchemeRegistry.get(state, 'Thumbnail'),
  319. _videoMuted: videoMuted
  320. };
  321. }
  322. export default connect(_mapStateToProps)(Thumbnail);