You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LargeVideoBackground.web.js 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // @flow
  2. import React, { Component } from 'react';
  3. import { connect } from '../../base/redux';
  4. import { shouldDisplayTileView } from '../../video-layout';
  5. /**
  6. * Constants to describe the dimensions of the video. Landscape videos
  7. * are wider than they are taller and portrait videos are taller than they
  8. * are wider. The dimensions will determine how {@code LargeVideoBackground}
  9. * will stretch to fill its container.
  10. *
  11. * @type {Object}
  12. */
  13. export const ORIENTATION = {
  14. LANDSCAPE: 'landscape',
  15. PORTRAIT: 'portrait'
  16. };
  17. /**
  18. * The type of the React {@code Component} props of
  19. * {@link LargeVideoBackgroundCanvas}.
  20. */
  21. type Props = {
  22. /**
  23. * Whether or not the layout should change to support tile view mode.
  24. *
  25. * @protected
  26. * @type {boolean}
  27. */
  28. _shouldDisplayTileView: boolean,
  29. /**
  30. * Additional CSS class names to add to the root of the component.
  31. */
  32. className: String,
  33. /**
  34. * Whether or not the background should have its visibility hidden.
  35. */
  36. hidden: boolean,
  37. /**
  38. * Whether or not the video should display flipped horizontally, so left
  39. * becomes right and right becomes left.
  40. */
  41. mirror: boolean,
  42. /**
  43. * Whether the component should ensure full width of the video is displayed
  44. * (landscape) or full height (portrait).
  45. */
  46. orientationFit: string,
  47. /**
  48. * The video stream to display.
  49. */
  50. videoElement: Object
  51. };
  52. /**
  53. * Implements a React Component which shows a video element intended to be used
  54. * as a background to fill the empty space of container with another video.
  55. *
  56. * @extends Component
  57. */
  58. export class LargeVideoBackground extends Component<Props> {
  59. _canvasEl: Object;
  60. _updateCanvasInterval: *;
  61. /**
  62. * Initializes new {@code LargeVideoBackground} instance.
  63. *
  64. * @param {*} props - The read-only properties with which the new instance
  65. * is to be initialized.
  66. */
  67. constructor(props: Props) {
  68. super(props);
  69. // Bind event handlers so they are only bound once per instance.
  70. this._setCanvasEl = this._setCanvasEl.bind(this);
  71. this._updateCanvas = this._updateCanvas.bind(this);
  72. }
  73. /**
  74. * If the canvas is not hidden, sets the initial interval to update the
  75. * image displayed in the canvas.
  76. *
  77. * @inheritdoc
  78. * @returns {void}
  79. */
  80. componentDidMount() {
  81. const { _shouldDisplayTileView, hidden, videoElement } = this.props;
  82. if (videoElement && !hidden && !_shouldDisplayTileView) {
  83. this._updateCanvas();
  84. this._setUpdateCanvasInterval();
  85. }
  86. }
  87. /**
  88. * Starts or stops the interval to update the image displayed in the canvas.
  89. *
  90. * @inheritdoc
  91. */
  92. componentDidUpdate(prevProps: Props) {
  93. const wasCanvasUpdating = !prevProps.hidden && !prevProps._shouldDisplayTileView && prevProps.videoElement;
  94. const shouldCanvasUpdating
  95. = !this.props.hidden && !this.props._shouldDisplayTileView && this.props.videoElement;
  96. if (wasCanvasUpdating !== shouldCanvasUpdating) {
  97. if (shouldCanvasUpdating) {
  98. this._clearCanvas();
  99. this._setUpdateCanvasInterval();
  100. } else {
  101. this._clearCanvas();
  102. this._clearUpdateCanvasInterval();
  103. }
  104. }
  105. }
  106. /**
  107. * Clears the interval for updating the image displayed in the canvas.
  108. *
  109. * @inheritdoc
  110. */
  111. componentWillUnmount() {
  112. this._clearUpdateCanvasInterval();
  113. }
  114. /**
  115. * Implements React's {@link Component#render()}.
  116. *
  117. * @inheritdoc
  118. * @returns {ReactElement}
  119. */
  120. render() {
  121. const {
  122. hidden,
  123. mirror
  124. } = this.props;
  125. const classNames = `large-video-background ${mirror ? 'flip-x' : ''} ${hidden ? 'invisible' : ''}`;
  126. return (
  127. <div className = { classNames }>
  128. <canvas
  129. id = 'largeVideoBackground'
  130. ref = { this._setCanvasEl } />
  131. </div>
  132. );
  133. }
  134. /**
  135. * Removes any image displayed on the canvas.
  136. *
  137. * @private
  138. * @returns {void}
  139. */
  140. _clearCanvas() {
  141. const cavnasContext = this._canvasEl.getContext('2d');
  142. cavnasContext.clearRect(
  143. 0, 0, this._canvasEl.width, this._canvasEl.height);
  144. }
  145. /**
  146. * Clears the interval for updating the image displayed in the canvas.
  147. *
  148. * @private
  149. * @returns {void}
  150. */
  151. _clearUpdateCanvasInterval() {
  152. clearInterval(this._updateCanvasInterval);
  153. }
  154. _setCanvasEl: () => void;
  155. /**
  156. * Sets the instance variable for the component's canvas element so it can
  157. * be accessed directly for drawing on.
  158. *
  159. * @param {Object} element - The DOM element for the component's canvas.
  160. * @private
  161. * @returns {void}
  162. */
  163. _setCanvasEl(element) {
  164. this._canvasEl = element;
  165. }
  166. /**
  167. * Starts the interval for updating the image displayed in the canvas.
  168. *
  169. * @private
  170. * @returns {void}
  171. */
  172. _setUpdateCanvasInterval() {
  173. this._clearUpdateCanvasInterval();
  174. this._updateCanvasInterval = setInterval(this._updateCanvas, 200);
  175. }
  176. _updateCanvas: () => void;
  177. /**
  178. * Draws the current frame of the passed in video element onto the canvas.
  179. *
  180. * @private
  181. * @returns {void}
  182. */
  183. _updateCanvas() {
  184. // On Electron 7 there is a memory leak if we try to draw into a hidden canvas that is part of the DOM tree.
  185. // See: https://github.com/electron/electron/issues/22417
  186. // Trying to detect all the cases when the page will be hidden because of something not in our control
  187. // (for example when the page is loaded in an iframe which is hidden due to the host page styles) to solve
  188. // the memory leak. Currently we are not handling the use case when the page is hidden with visibility:hidden
  189. // because we don't have a good way to do it.
  190. // All other cases when the canvas is not visible are handled through the component props
  191. // (hidden, _shouldDisplayTileView).
  192. if (!this._canvasEl || this._canvasEl.offsetParent === null
  193. || window.innerHeight === 0 || window.innerWidth === 0) {
  194. return;
  195. }
  196. const { videoElement } = this.props;
  197. const { videoWidth, videoHeight } = videoElement;
  198. const {
  199. height: canvasHeight,
  200. width: canvasWidth
  201. } = this._canvasEl;
  202. const cavnasContext = this._canvasEl.getContext('2d');
  203. if (this.props.orientationFit === ORIENTATION.LANDSCAPE) {
  204. const heightScaledToFit = (canvasWidth / videoWidth) * videoHeight;
  205. cavnasContext.drawImage(
  206. videoElement, 0, 0, canvasWidth, heightScaledToFit);
  207. } else {
  208. const widthScaledToFit = (canvasHeight / videoHeight) * videoWidth;
  209. cavnasContext.drawImage(
  210. videoElement, 0, 0, widthScaledToFit, canvasHeight);
  211. }
  212. }
  213. }
  214. /**
  215. * Maps (parts of) the Redux state to the associated LargeVideoBackground props.
  216. *
  217. * @param {Object} state - The Redux state.
  218. * @private
  219. * @returns {{
  220. * _shouldDisplayTileView: boolean
  221. * }}
  222. */
  223. function _mapStateToProps(state) {
  224. return {
  225. _shouldDisplayTileView: shouldDisplayTileView(state)
  226. };
  227. }
  228. export default connect(_mapStateToProps)(LargeVideoBackground);