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 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. // @flow
  2. import React, { Component } from 'react';
  3. /**
  4. * Constants to describe the dimensions of the video. Landscape videos
  5. * are wider than they are taller and portrait videos are taller than they
  6. * are wider. The dimensions will determine how {@code LargeVideoBackground}
  7. * will strech to fill its container.
  8. *
  9. * @type {Object}
  10. */
  11. export const ORIENTATION = {
  12. LANDSCAPE: 'landscape',
  13. PORTRAIT: 'portrait'
  14. };
  15. /**
  16. * The type of the React {@code Component} props of
  17. * {@link LargeVideoBackgroundCanvas}.
  18. */
  19. type Props = {
  20. /**
  21. * Additional CSS class names to add to the root of the component.
  22. */
  23. className: String,
  24. /**
  25. * Whether or not the background should have its visibility hidden.
  26. */
  27. hidden: boolean,
  28. /**
  29. * Whether or not the video should display flipped horizontally, so left
  30. * becomes right and right becomes left.
  31. */
  32. mirror: boolean,
  33. /**
  34. * Whether the component should ensure full width of the video is displayed
  35. * (landscape) or full height (portrait).
  36. */
  37. orientationFit: string,
  38. /**
  39. * Whether or not to display a filter on the video to visually indicate a
  40. * problem with the video being displayed.
  41. */
  42. showRemoteProblemFilter: boolean,
  43. /**
  44. * The video stream to display.
  45. */
  46. videoElement: Object
  47. };
  48. /**
  49. * Implements a React Component which shows a video element intended to be used
  50. * as a background to fill the empty space of container with another video.
  51. *
  52. * @extends Component
  53. */
  54. export class LargeVideoBackground extends Component<Props> {
  55. _canvasEl: Object;
  56. _updateCanvasInterval: *;
  57. /**
  58. * Initializes new {@code LargeVideoBackground} instance.
  59. *
  60. * @param {*} props - The read-only properties with which the new instance
  61. * is to be initialized.
  62. */
  63. constructor(props: Props) {
  64. super(props);
  65. // Bind event handlers so they are only bound once per instance.
  66. this._setCanvasEl = this._setCanvasEl.bind(this);
  67. this._updateCanvas = this._updateCanvas.bind(this);
  68. }
  69. /**
  70. * If the canvas is not hidden, sets the initial interval to update the
  71. * image displayed in the canvas.
  72. *
  73. * @inheritdoc
  74. * @returns {void}
  75. */
  76. componentDidMount() {
  77. if (this.props.videoElement && !this.props.hidden) {
  78. this._updateCanvas();
  79. this._setUpdateCanvasInterval();
  80. }
  81. }
  82. /**
  83. * Starts or stops the interval to update the image displayed in the canvas.
  84. *
  85. * @inheritdoc
  86. */
  87. componentDidUpdate(prevProps: Props) {
  88. if (prevProps.hidden && !this.props.hidden) {
  89. this._clearCanvas();
  90. this._setUpdateCanvasInterval();
  91. }
  92. if ((!prevProps.hidden && this.props.hidden)
  93. || !this.props.videoElement) {
  94. this._clearCanvas();
  95. this._clearUpdateCanvasInterval();
  96. }
  97. }
  98. /**
  99. * Clears the interval for updating the image displayed in the canvas.
  100. *
  101. * @inheritdoc
  102. */
  103. componentWillUnmount() {
  104. this._clearUpdateCanvasInterval();
  105. }
  106. /**
  107. * Implements React's {@link Component#render()}.
  108. *
  109. * @inheritdoc
  110. * @returns {ReactElement}
  111. */
  112. render() {
  113. const {
  114. hidden,
  115. mirror,
  116. showRemoteProblemFilter
  117. } = this.props;
  118. const classNames = `large-video-background ${mirror ? 'flip-x' : ''} ${
  119. hidden ? 'invisible' : ''} ${
  120. showRemoteProblemFilter ? 'remoteVideoProblemFilter' : ''}`;
  121. return (
  122. <div className = { classNames }>
  123. <canvas
  124. id = 'largeVideoBackground'
  125. ref = { this._setCanvasEl } />
  126. </div>
  127. );
  128. }
  129. /**
  130. * Removes any image displayed on the canvas.
  131. *
  132. * @private
  133. * @returns {void}
  134. */
  135. _clearCanvas() {
  136. const cavnasContext = this._canvasEl.getContext('2d');
  137. cavnasContext.clearRect(
  138. 0, 0, this._canvasEl.width, this._canvasEl.height);
  139. }
  140. /**
  141. * Clears the interval for updating the image displayed in the canvas.
  142. *
  143. * @private
  144. * @returns {void}
  145. */
  146. _clearUpdateCanvasInterval() {
  147. clearInterval(this._updateCanvasInterval);
  148. }
  149. _setCanvasEl: () => void;
  150. /**
  151. * Sets the instance variable for the component's canvas element so it can
  152. * be accessed directly for drawing on.
  153. *
  154. * @param {Object} element - The DOM element for the component's canvas.
  155. * @private
  156. * @returns {void}
  157. */
  158. _setCanvasEl(element) {
  159. this._canvasEl = element;
  160. }
  161. /**
  162. * Starts the interval for updating the image displayed in the canvas.
  163. *
  164. * @private
  165. * @returns {void}
  166. */
  167. _setUpdateCanvasInterval() {
  168. this._clearUpdateCanvasInterval();
  169. this._updateCanvasInterval = setInterval(this._updateCanvas, 200);
  170. }
  171. _updateCanvas: () => void;
  172. /**
  173. * Draws the current frame of the passed in video element onto the canvas.
  174. *
  175. * @private
  176. * @returns {void}
  177. */
  178. _updateCanvas() {
  179. const { videoElement } = this.props;
  180. const { videoWidth, videoHeight } = videoElement;
  181. const {
  182. height: canvasHeight,
  183. width: canvasWidth
  184. } = this._canvasEl;
  185. const cavnasContext = this._canvasEl.getContext('2d');
  186. if (this.props.orientationFit === ORIENTATION.LANDSCAPE) {
  187. const heightScaledToFit = (canvasWidth / videoWidth) * videoHeight;
  188. cavnasContext.drawImage(
  189. videoElement, 0, 0, canvasWidth, heightScaledToFit);
  190. } else {
  191. const widthScaledToFit = (canvasHeight / videoHeight) * videoWidth;
  192. cavnasContext.drawImage(
  193. videoElement, 0, 0, widthScaledToFit, canvasHeight);
  194. }
  195. }
  196. }