Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

Thumbnail.js 11KB

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