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.

VirtualBackgroundPreview.js 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. // @flow
  2. import Spinner from '@atlaskit/spinner';
  3. import React, { PureComponent } from 'react';
  4. import { hideDialog } from '../../base/dialog';
  5. import { translate } from '../../base/i18n';
  6. import { VIDEO_TYPE } from '../../base/media';
  7. import Video from '../../base/media/components/Video';
  8. import { connect, equals } from '../../base/redux';
  9. import { getCurrentCameraDeviceId } from '../../base/settings';
  10. import { createLocalTracksF } from '../../base/tracks/functions';
  11. import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications';
  12. import { showWarningNotification } from '../../notifications/actions';
  13. import { toggleBackgroundEffect } from '../actions';
  14. import { VIRTUAL_BACKGROUND_TYPE } from '../constants';
  15. import { localTrackStopped } from '../functions';
  16. import logger from '../logger';
  17. const videoClassName = 'video-preview-video';
  18. /**
  19. * The type of the React {@code PureComponent} props of {@link VirtualBackgroundPreview}.
  20. */
  21. export type Props = {
  22. /**
  23. * The deviceId of the camera device currently being used.
  24. */
  25. _currentCameraDeviceId: string,
  26. /**
  27. * The redux {@code dispatch} function.
  28. */
  29. dispatch: Function,
  30. /**
  31. * Dialog callback that indicates if the background preview was loaded.
  32. */
  33. loadedPreview: Function,
  34. /**
  35. * Represents the virtual background setted options.
  36. */
  37. options: Object,
  38. /**
  39. * Invoked to obtain translated strings.
  40. */
  41. t: Function
  42. };
  43. /**
  44. * The type of the React {@code Component} state of {@link VirtualBackgroundPreview}.
  45. */
  46. type State = {
  47. /**
  48. * Loader activated on setting virtual background.
  49. */
  50. loading: boolean,
  51. /**
  52. * Flag that indicates if the local track was loaded.
  53. */
  54. localTrackLoaded: boolean,
  55. /**
  56. * Activate the selected device camera only.
  57. */
  58. jitsiTrack: Object
  59. };
  60. /**
  61. * Implements a React {@link PureComponent} which displays the virtual
  62. * background preview.
  63. *
  64. * @augments PureComponent
  65. */
  66. class VirtualBackgroundPreview extends PureComponent<Props, State> {
  67. _componentWasUnmounted: boolean;
  68. /**
  69. * Initializes a new {@code VirtualBackgroundPreview} instance.
  70. *
  71. * @param {Object} props - The read-only properties with which the new
  72. * instance is to be initialized.
  73. */
  74. constructor(props) {
  75. super(props);
  76. this.state = {
  77. loading: false,
  78. localTrackLoaded: false,
  79. jitsiTrack: null
  80. };
  81. }
  82. /**
  83. * Destroys the jitsiTrack object.
  84. *
  85. * @param {Object} jitsiTrack - The track that needs to be disposed.
  86. * @returns {Promise<void>}
  87. */
  88. _stopStream(jitsiTrack) {
  89. if (jitsiTrack) {
  90. jitsiTrack.dispose();
  91. }
  92. }
  93. /**
  94. * Creates and updates the track data.
  95. *
  96. * @returns {void}
  97. */
  98. async _setTracks() {
  99. try {
  100. this.setState({ loading: true });
  101. const [ jitsiTrack ] = await createLocalTracksF({
  102. cameraDeviceId: this.props._currentCameraDeviceId,
  103. devices: [ 'video' ]
  104. });
  105. this.setState({ localTrackLoaded: true });
  106. // In case the component gets unmounted before the tracks are created
  107. // avoid a leak by not setting the state
  108. if (this._componentWasUnmounted) {
  109. this._stopStream(jitsiTrack);
  110. return;
  111. }
  112. this.setState({
  113. jitsiTrack,
  114. loading: false
  115. });
  116. this.props.loadedPreview(true);
  117. } catch (error) {
  118. this.props.dispatch(hideDialog());
  119. this.props.dispatch(
  120. showWarningNotification({
  121. titleKey: 'virtualBackground.backgroundEffectError',
  122. description: 'Failed to access camera device.'
  123. }, NOTIFICATION_TIMEOUT_TYPE.LONG)
  124. );
  125. logger.error('Failed to access camera device. Error on apply background effect.');
  126. return;
  127. }
  128. if (this.props.options.backgroundType === VIRTUAL_BACKGROUND_TYPE.DESKTOP_SHARE
  129. && this.state.localTrackLoaded) {
  130. this._applyBackgroundEffect();
  131. }
  132. }
  133. /**
  134. * Apply background effect on video preview.
  135. *
  136. * @returns {Promise}
  137. */
  138. async _applyBackgroundEffect() {
  139. this.setState({ loading: true });
  140. this.props.loadedPreview(false);
  141. await this.props.dispatch(toggleBackgroundEffect(this.props.options, this.state.jitsiTrack));
  142. this.props.loadedPreview(true);
  143. this.setState({ loading: false });
  144. }
  145. /**
  146. * Apply video preview loader.
  147. *
  148. * @returns {Promise}
  149. */
  150. _loadVideoPreview() {
  151. return (
  152. <div className = 'video-preview-loader'>
  153. <Spinner
  154. invertColor = { true }
  155. isCompleting = { false }
  156. size = { 'large' } />
  157. </div>
  158. );
  159. }
  160. /**
  161. * Renders a preview entry.
  162. *
  163. * @param {Object} data - The track data.
  164. * @returns {React$Node}
  165. */
  166. _renderPreviewEntry(data) {
  167. const { t } = this.props;
  168. const className = 'video-background-preview-entry';
  169. if (this.state.loading) {
  170. return this._loadVideoPreview();
  171. }
  172. if (!data) {
  173. return (
  174. <div
  175. className = { className }
  176. video-preview-container = { true }>
  177. <div className = 'video-preview-error'>{t('deviceSelection.previewUnavailable')}</div>
  178. </div>
  179. );
  180. }
  181. const props: Object = {
  182. className
  183. };
  184. return (
  185. <div { ...props }>
  186. <Video
  187. className = { videoClassName }
  188. playsinline = { true }
  189. videoTrack = {{ jitsiTrack: data }} />
  190. </div>
  191. );
  192. }
  193. /**
  194. * Implements React's {@link Component#componentDidMount}.
  195. *
  196. * @inheritdoc
  197. */
  198. componentDidMount() {
  199. this._setTracks();
  200. }
  201. /**
  202. * Implements React's {@link Component#componentWillUnmount}.
  203. *
  204. * @inheritdoc
  205. */
  206. componentWillUnmount() {
  207. this._componentWasUnmounted = true;
  208. this._stopStream(this.state.jitsiTrack);
  209. }
  210. /**
  211. * Implements React's {@link Component#componentDidUpdate}.
  212. *
  213. * @inheritdoc
  214. */
  215. async componentDidUpdate(prevProps) {
  216. if (!equals(this.props._currentCameraDeviceId, prevProps._currentCameraDeviceId)) {
  217. this._setTracks();
  218. }
  219. if (!equals(this.props.options, prevProps.options) && this.state.localTrackLoaded) {
  220. if (prevProps.options.backgroundType === VIRTUAL_BACKGROUND_TYPE.DESKTOP_SHARE) {
  221. prevProps.options.url.dispose();
  222. }
  223. this._applyBackgroundEffect();
  224. }
  225. if (this.props.options.url?.videoType === VIDEO_TYPE.DESKTOP) {
  226. localTrackStopped(this.props.dispatch, this.props.options.url, this.state.jitsiTrack);
  227. }
  228. }
  229. /**
  230. * Implements React's {@link Component#render}.
  231. *
  232. * @inheritdoc
  233. */
  234. render() {
  235. const { jitsiTrack } = this.state;
  236. return jitsiTrack
  237. ? <div className = 'video-preview'>{this._renderPreviewEntry(jitsiTrack)}</div>
  238. : <div className = 'video-preview-loader'>{this._loadVideoPreview()}</div>
  239. ;
  240. }
  241. }
  242. /**
  243. * Maps (parts of) the redux state to the associated props for the
  244. * {@code VirtualBackgroundPreview} component.
  245. *
  246. * @param {Object} state - The Redux state.
  247. * @private
  248. * @returns {{Props}}
  249. */
  250. function _mapStateToProps(state): Object {
  251. return {
  252. _currentCameraDeviceId: getCurrentCameraDeviceId(state)
  253. };
  254. }
  255. export default translate(connect(_mapStateToProps)(VirtualBackgroundPreview));