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

LargeVideo.web.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. // @flow
  2. import React, { Component } from 'react';
  3. import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
  4. import { VIDEO_TYPE } from '../../base/media';
  5. import { getLocalParticipant } from '../../base/participants';
  6. import { Watermarks } from '../../base/react';
  7. import { connect } from '../../base/redux';
  8. import { getVideoTrackByParticipant } from '../../base/tracks';
  9. import { setColorAlpha } from '../../base/util';
  10. import { StageParticipantNameLabel } from '../../display-name';
  11. import { FILMSTRIP_BREAKPOINT, isFilmstripResizable } from '../../filmstrip';
  12. import { getVerticalViewMaxWidth } from '../../filmstrip/functions.web';
  13. import { getLargeVideoParticipant } from '../../large-video/functions';
  14. import { SharedVideo } from '../../shared-video/components/web';
  15. import { Captions } from '../../subtitles/';
  16. import { setTileView } from '../../video-layout/actions';
  17. import Whiteboard from '../../whiteboard/components/web/Whiteboard';
  18. import { isWhiteboardEnabled } from '../../whiteboard/functions';
  19. import { setSeeWhatIsBeingShared } from '../actions.web';
  20. import ScreenSharePlaceholder from './ScreenSharePlaceholder.web';
  21. // Hack to detect Spot.
  22. const SPOT_DISPLAY_NAME = 'Meeting Room';
  23. declare var interfaceConfig: Object;
  24. type Props = {
  25. /**
  26. * The alpha(opacity) of the background.
  27. */
  28. _backgroundAlpha: number,
  29. /**
  30. * The user selected background color.
  31. */
  32. _customBackgroundColor: string,
  33. /**
  34. * The user selected background image url.
  35. */
  36. _customBackgroundImageUrl: string,
  37. /**
  38. * Whether the screen-sharing placeholder should be displayed or not.
  39. */
  40. _displayScreenSharingPlaceholder: boolean,
  41. /**
  42. * Prop that indicates whether the chat is open.
  43. */
  44. _isChatOpen: boolean,
  45. /**
  46. * Used to determine the value of the autoplay attribute of the underlying
  47. * video element.
  48. */
  49. _noAutoPlayVideo: boolean,
  50. /**
  51. * Whether or not the filmstrip is resizable.
  52. */
  53. _resizableFilmstrip: boolean,
  54. /**
  55. * Whether or not to show dominant speaker badge.
  56. */
  57. _showDominantSpeakerBadge: boolean,
  58. /**
  59. * The width of the vertical filmstrip (user resized).
  60. */
  61. _verticalFilmstripWidth: ?number,
  62. /**
  63. * The max width of the vertical filmstrip.
  64. */
  65. _verticalViewMaxWidth: number,
  66. /**
  67. * Whether or not the filmstrip is visible.
  68. */
  69. _visibleFilmstrip: boolean,
  70. /**
  71. * The large video participant id.
  72. */
  73. _largeVideoParticipantId: string,
  74. /**
  75. * Whether or not the local screen share is on large-video.
  76. */
  77. _isScreenSharing: boolean,
  78. /**
  79. * Whether or not the screen sharing is visible.
  80. */
  81. _seeWhatIsBeingShared: boolean,
  82. /**
  83. * Whether or not the whiteboard is enabled.
  84. */
  85. _whiteboardEnabled: boolean;
  86. /**
  87. * The Redux dispatch function.
  88. */
  89. dispatch: Function
  90. }
  91. /** .
  92. * Implements a React {@link Component} which represents the large video (a.k.a.
  93. * The conference participant who is on the local stage) on Web/React.
  94. *
  95. * @augments Component
  96. */
  97. class LargeVideo extends Component<Props> {
  98. _tappedTimeout: ?TimeoutID;
  99. _containerRef: Object;
  100. _wrapperRef: Object;
  101. /**
  102. * Constructor of the component.
  103. *
  104. * @inheritdoc
  105. */
  106. constructor(props) {
  107. super(props);
  108. this._containerRef = React.createRef();
  109. this._wrapperRef = React.createRef();
  110. this._clearTapTimeout = this._clearTapTimeout.bind(this);
  111. this._onDoubleTap = this._onDoubleTap.bind(this);
  112. this._updateLayout = this._updateLayout.bind(this);
  113. }
  114. /**
  115. * Implements {@code Component#componentDidUpdate}.
  116. *
  117. * @inheritdoc
  118. */
  119. componentDidUpdate(prevProps: Props) {
  120. const { _visibleFilmstrip, _isScreenSharing, _seeWhatIsBeingShared, _largeVideoParticipantId } = this.props;
  121. if (prevProps._visibleFilmstrip !== _visibleFilmstrip) {
  122. this._updateLayout();
  123. }
  124. if (prevProps._isScreenSharing !== _isScreenSharing && !_isScreenSharing) {
  125. this.props.dispatch(setSeeWhatIsBeingShared(false));
  126. }
  127. if (_isScreenSharing && _seeWhatIsBeingShared) {
  128. VideoLayout.updateLargeVideo(_largeVideoParticipantId, true, true);
  129. }
  130. }
  131. /**
  132. * Implements React's {@link Component#render()}.
  133. *
  134. * @inheritdoc
  135. * @returns {React$Element}
  136. */
  137. render() {
  138. const {
  139. _displayScreenSharingPlaceholder,
  140. _isChatOpen,
  141. _noAutoPlayVideo,
  142. _showDominantSpeakerBadge,
  143. _whiteboardEnabled
  144. } = this.props;
  145. const style = this._getCustomStyles();
  146. const className = `videocontainer${_isChatOpen ? ' shift-right' : ''}`;
  147. return (
  148. <div
  149. className = { className }
  150. id = 'largeVideoContainer'
  151. ref = { this._containerRef }
  152. style = { style }>
  153. <SharedVideo />
  154. {_whiteboardEnabled && <Whiteboard />}
  155. <div id = 'etherpad' />
  156. <Watermarks />
  157. <div
  158. id = 'dominantSpeaker'
  159. onTouchEnd = { this._onDoubleTap }>
  160. <div className = 'dynamic-shadow' />
  161. <div id = 'dominantSpeakerAvatarContainer' />
  162. </div>
  163. <div id = 'remotePresenceMessage' />
  164. <span id = 'remoteConnectionMessage' />
  165. <div id = 'largeVideoElementsContainer'>
  166. <div id = 'largeVideoBackgroundContainer' />
  167. {/*
  168. * FIXME: the architecture of elements related to the large
  169. * video and the naming. The background is not part of
  170. * largeVideoWrapper because we are controlling the size of
  171. * the video through largeVideoWrapper. That's why we need
  172. * another container for the background and the
  173. * largeVideoWrapper in order to hide/show them.
  174. */}
  175. <div
  176. id = 'largeVideoWrapper'
  177. onTouchEnd = { this._onDoubleTap }
  178. ref = { this._wrapperRef }
  179. role = 'figure' >
  180. { _displayScreenSharingPlaceholder ? <ScreenSharePlaceholder /> : <video
  181. autoPlay = { !_noAutoPlayVideo }
  182. id = 'largeVideo'
  183. muted = { true }
  184. playsInline = { true } /* for Safari on iOS to work */ /> }
  185. </div>
  186. </div>
  187. { interfaceConfig.DISABLE_TRANSCRIPTION_SUBTITLES
  188. || <Captions /> }
  189. {_showDominantSpeakerBadge && <StageParticipantNameLabel />}
  190. </div>
  191. );
  192. }
  193. _updateLayout: () => void;
  194. /**
  195. * Refreshes the video layout to determine the dimensions of the stage view.
  196. * If the filmstrip is toggled it adds CSS transition classes and removes them
  197. * when the transition is done.
  198. *
  199. * @returns {void}
  200. */
  201. _updateLayout() {
  202. const { _verticalFilmstripWidth, _resizableFilmstrip } = this.props;
  203. if (_resizableFilmstrip && _verticalFilmstripWidth >= FILMSTRIP_BREAKPOINT) {
  204. this._containerRef.current.classList.add('transition');
  205. this._wrapperRef.current.classList.add('transition');
  206. VideoLayout.refreshLayout();
  207. setTimeout(() => {
  208. this._containerRef.current && this._containerRef.current.classList.remove('transition');
  209. this._wrapperRef.current && this._wrapperRef.current.classList.remove('transition');
  210. }, 1000);
  211. } else {
  212. VideoLayout.refreshLayout();
  213. }
  214. }
  215. _clearTapTimeout: () => void;
  216. /**
  217. * Clears the '_tappedTimout'.
  218. *
  219. * @private
  220. * @returns {void}
  221. */
  222. _clearTapTimeout() {
  223. clearTimeout(this._tappedTimeout);
  224. this._tappedTimeout = undefined;
  225. }
  226. /**
  227. * Creates the custom styles object.
  228. *
  229. * @private
  230. * @returns {Object}
  231. */
  232. _getCustomStyles() {
  233. const styles = {};
  234. const {
  235. _customBackgroundColor,
  236. _customBackgroundImageUrl,
  237. _verticalFilmstripWidth,
  238. _verticalViewMaxWidth,
  239. _visibleFilmstrip
  240. } = this.props;
  241. styles.backgroundColor = _customBackgroundColor || interfaceConfig.DEFAULT_BACKGROUND;
  242. if (this.props._backgroundAlpha !== undefined) {
  243. const alphaColor = setColorAlpha(styles.backgroundColor, this.props._backgroundAlpha);
  244. styles.backgroundColor = alphaColor;
  245. }
  246. if (_customBackgroundImageUrl) {
  247. styles.backgroundImage = `url(${_customBackgroundImageUrl})`;
  248. styles.backgroundSize = 'cover';
  249. }
  250. if (_visibleFilmstrip && _verticalFilmstripWidth >= FILMSTRIP_BREAKPOINT) {
  251. styles.width = `calc(100% - ${_verticalViewMaxWidth || 0}px)`;
  252. }
  253. return styles;
  254. }
  255. _onDoubleTap: () => void;
  256. /**
  257. * Sets view to tile view on double tap.
  258. *
  259. * @param {Object} e - The event.
  260. * @private
  261. * @returns {void}
  262. */
  263. _onDoubleTap(e) {
  264. e.stopPropagation();
  265. e.preventDefault();
  266. if (this._tappedTimeout) {
  267. this._clearTapTimeout();
  268. this.props.dispatch(setTileView(true));
  269. } else {
  270. this._tappedTimeout = setTimeout(this._clearTapTimeout, 300);
  271. }
  272. }
  273. }
  274. /**
  275. * Maps (parts of) the Redux state to the associated LargeVideo props.
  276. *
  277. * @param {Object} state - The Redux state.
  278. * @private
  279. * @returns {Props}
  280. */
  281. function _mapStateToProps(state) {
  282. const testingConfig = state['features/base/config'].testing;
  283. const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding'];
  284. const { isOpen: isChatOpen } = state['features/chat'];
  285. const { width: verticalFilmstripWidth, visible } = state['features/filmstrip'];
  286. const { defaultLocalDisplayName, hideDominantSpeakerBadge } = state['features/base/config'];
  287. const { seeWhatIsBeingShared } = state['features/large-video'];
  288. const localParticipantId = getLocalParticipant(state)?.id;
  289. const largeVideoParticipant = getLargeVideoParticipant(state);
  290. const videoTrack = getVideoTrackByParticipant(state, largeVideoParticipant);
  291. const isLocalScreenshareOnLargeVideo = largeVideoParticipant?.id?.includes(localParticipantId)
  292. && videoTrack?.videoType === VIDEO_TYPE.DESKTOP;
  293. const isOnSpot = defaultLocalDisplayName === SPOT_DISPLAY_NAME;
  294. return {
  295. _backgroundAlpha: state['features/base/config'].backgroundAlpha,
  296. _customBackgroundColor: backgroundColor,
  297. _customBackgroundImageUrl: backgroundImageUrl,
  298. _displayScreenSharingPlaceholder: isLocalScreenshareOnLargeVideo && !seeWhatIsBeingShared && !isOnSpot,
  299. _isChatOpen: isChatOpen,
  300. _isScreenSharing: isLocalScreenshareOnLargeVideo,
  301. _largeVideoParticipantId: largeVideoParticipant?.id,
  302. _noAutoPlayVideo: testingConfig?.noAutoPlayVideo,
  303. _resizableFilmstrip: isFilmstripResizable(state),
  304. _seeWhatIsBeingShared: seeWhatIsBeingShared,
  305. _showDominantSpeakerBadge: !hideDominantSpeakerBadge,
  306. _verticalFilmstripWidth: verticalFilmstripWidth.current,
  307. _verticalViewMaxWidth: getVerticalViewMaxWidth(state),
  308. _visibleFilmstrip: visible,
  309. _whiteboardEnabled: isWhiteboardEnabled(state)
  310. };
  311. }
  312. export default connect(_mapStateToProps)(LargeVideo);