Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

Video.tsx 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. import React, { Component, ReactEventHandler } from 'react';
  2. import { ITrack } from '../../../tracks/types';
  3. import logger from '../../logger';
  4. /**
  5. * The type of the React {@code Component} props of {@link Video}.
  6. */
  7. interface IProps {
  8. /**
  9. * Used to determine the value of the autoplay attribute of the underlying
  10. * video element.
  11. */
  12. autoPlay: boolean;
  13. /**
  14. * CSS classes to add to the video element.
  15. */
  16. className: string;
  17. /**
  18. * A map of the event handlers for the video HTML element.
  19. */
  20. eventHandlers?: {
  21. /**
  22. * OnAbort event handler.
  23. */
  24. onAbort?: ReactEventHandler<HTMLVideoElement>;
  25. /**
  26. * OnCanPlay event handler.
  27. */
  28. onCanPlay?: ReactEventHandler<HTMLVideoElement>;
  29. /**
  30. * OnCanPlayThrough event handler.
  31. */
  32. onCanPlayThrough?: ReactEventHandler<HTMLVideoElement>;
  33. /**
  34. * OnEmptied event handler.
  35. */
  36. onEmptied?: ReactEventHandler<HTMLVideoElement>;
  37. /**
  38. * OnEnded event handler.
  39. */
  40. onEnded?: ReactEventHandler<HTMLVideoElement>;
  41. /**
  42. * OnError event handler.
  43. */
  44. onError?: ReactEventHandler<HTMLVideoElement>;
  45. /**
  46. * OnLoadStart event handler.
  47. */
  48. onLoadStart?: ReactEventHandler<HTMLVideoElement>;
  49. /**
  50. * OnLoadedData event handler.
  51. */
  52. onLoadedData?: ReactEventHandler<HTMLVideoElement>;
  53. /**
  54. * OnLoadedMetadata event handler.
  55. */
  56. onLoadedMetadata?: ReactEventHandler<HTMLVideoElement>;
  57. /**
  58. * OnPause event handler.
  59. */
  60. onPause?: ReactEventHandler<HTMLVideoElement>;
  61. /**
  62. * OnPlay event handler.
  63. */
  64. onPlay?: ReactEventHandler<HTMLVideoElement>;
  65. /**
  66. * OnPlaying event handler.
  67. */
  68. onPlaying?: ReactEventHandler<HTMLVideoElement>;
  69. /**
  70. * OnRateChange event handler.
  71. */
  72. onRateChange?: ReactEventHandler<HTMLVideoElement>;
  73. /**
  74. * OnStalled event handler.
  75. */
  76. onStalled?: ReactEventHandler<HTMLVideoElement>;
  77. /**
  78. * OnSuspend event handler.
  79. */
  80. onSuspend?: ReactEventHandler<HTMLVideoElement>;
  81. /**
  82. * OnWaiting event handler.
  83. */
  84. onWaiting?: ReactEventHandler<HTMLVideoElement>;
  85. };
  86. /**
  87. * The value of the id attribute of the video. Used by the torture tests to
  88. * locate video elements.
  89. */
  90. id: string;
  91. /**
  92. * Used on native.
  93. */
  94. mirror?: boolean;
  95. /**
  96. * The value of the muted attribute for the underlying video element.
  97. */
  98. muted?: boolean;
  99. /**
  100. * Used on native.
  101. */
  102. onPlaying?: Function;
  103. /**
  104. * Used on native.
  105. */
  106. onPress?: Function;
  107. /**
  108. * Optional callback to invoke once the video starts playing.
  109. */
  110. onVideoPlaying?: Function;
  111. /**
  112. * Used to determine the value of the autoplay attribute of the underlying
  113. * video element.
  114. */
  115. playsinline: boolean;
  116. /**
  117. * Used on native.
  118. */
  119. stream?: any;
  120. /**
  121. * A styles that will be applied on the video element.
  122. */
  123. style?: Object;
  124. /**
  125. * The JitsiLocalTrack to display.
  126. */
  127. videoTrack?: Partial<ITrack>;
  128. /**
  129. * Used on native.
  130. */
  131. zOrder?: number;
  132. /**
  133. * Used on native.
  134. */
  135. zoomEnabled?: boolean;
  136. }
  137. /**
  138. * Component that renders a video element for a passed in video track.
  139. *
  140. * @augments Component
  141. */
  142. class Video extends Component<IProps> {
  143. _videoElement?: HTMLVideoElement | null;
  144. _mounted: boolean;
  145. /**
  146. * Default values for {@code Video} component's properties.
  147. *
  148. * @static
  149. */
  150. static defaultProps = {
  151. className: '',
  152. autoPlay: true,
  153. id: '',
  154. playsinline: true
  155. };
  156. /**
  157. * Initializes a new {@code Video} instance.
  158. *
  159. * @param {Object} props - The read-only properties with which the new
  160. * instance is to be initialized.
  161. */
  162. constructor(props: IProps) {
  163. super(props);
  164. /**
  165. * The internal reference to the DOM/HTML element intended for
  166. * displaying a video.
  167. *
  168. * @private
  169. * @type {HTMLVideoElement}
  170. */
  171. this._videoElement = null;
  172. // Bind event handlers so they are only bound once for every instance.
  173. this._onVideoPlaying = this._onVideoPlaying.bind(this);
  174. this._setVideoElement = this._setVideoElement.bind(this);
  175. }
  176. /**
  177. * Invokes the library for rendering the video on initial display. Sets the
  178. * volume level to zero to ensure no sound plays.
  179. *
  180. * @inheritdoc
  181. * @returns {void}
  182. */
  183. componentDidMount() {
  184. this._mounted = true;
  185. if (this._videoElement) {
  186. this._videoElement.volume = 0;
  187. this._videoElement.onplaying = this._onVideoPlaying;
  188. }
  189. this._attachTrack(this.props.videoTrack).finally(() => {
  190. if (this._videoElement && this.props.autoPlay) {
  191. // Ensure the video gets play() called on it. This may be necessary in the
  192. // case where the local video container was moved and re-attached, in which
  193. // case video does not autoplay.
  194. this._videoElement.play()
  195. .catch(error => {
  196. // Prevent uncaught "DOMException: The play() request was interrupted by a new load request"
  197. // when video playback takes long to start and it starts after the component was unmounted.
  198. if (this._mounted) {
  199. throw error;
  200. }
  201. });
  202. }
  203. });
  204. }
  205. /**
  206. * Remove any existing associations between the current video track and the
  207. * component's video element.
  208. *
  209. * @inheritdoc
  210. * @returns {void}
  211. */
  212. componentWillUnmount() {
  213. this._mounted = false;
  214. this._detachTrack(this.props.videoTrack);
  215. }
  216. /**
  217. * Updates the video display only if a new track is added. This component's
  218. * updating is blackboxed from React to prevent re-rendering of video
  219. * element, as the lib uses {@code track.attach(videoElement)} instead.
  220. *
  221. * @inheritdoc
  222. * @returns {boolean} - False is always returned to blackbox this component
  223. * from React.
  224. */
  225. shouldComponentUpdate(nextProps: IProps) {
  226. const currentJitsiTrack = this.props.videoTrack?.jitsiTrack;
  227. const nextJitsiTrack = nextProps.videoTrack?.jitsiTrack;
  228. if (currentJitsiTrack !== nextJitsiTrack) {
  229. this._detachTrack(this.props.videoTrack);
  230. this._attachTrack(nextProps.videoTrack).catch((_error: Error) => {
  231. // Ignore the error. We are already logging it.
  232. });
  233. }
  234. if (this.props.style !== nextProps.style || this.props.className !== nextProps.className) {
  235. return true;
  236. }
  237. return false;
  238. }
  239. /**
  240. * Renders the video element.
  241. *
  242. * @override
  243. * @returns {ReactElement}
  244. */
  245. render() {
  246. const {
  247. autoPlay,
  248. className,
  249. id,
  250. muted,
  251. playsinline,
  252. style,
  253. eventHandlers
  254. } = this.props;
  255. return (
  256. <video
  257. autoPlay = { autoPlay }
  258. className = { className }
  259. id = { id }
  260. muted = { muted }
  261. playsInline = { playsinline }
  262. ref = { this._setVideoElement }
  263. style = { style }
  264. { ...eventHandlers } />
  265. );
  266. }
  267. /**
  268. * Calls into the passed in track to associate the track with the
  269. * component's video element and render video.
  270. *
  271. * @param {Object} videoTrack - The redux representation of the
  272. * {@code JitsiLocalTrack}.
  273. * @private
  274. * @returns {void}
  275. */
  276. _attachTrack(videoTrack?: Partial<ITrack>) {
  277. const { id } = this.props;
  278. if (!videoTrack?.jitsiTrack) {
  279. logger.warn(`Attach is called on video element ${id} without tracks passed!`);
  280. // returning Promise.resolve just keep the previous logic.
  281. // TODO: Check if it make sense to call play on this element or we can just return promise.reject().
  282. return Promise.resolve();
  283. }
  284. return videoTrack.jitsiTrack.attach(this._videoElement)
  285. .catch((error: Error) => {
  286. logger.error(
  287. `Attaching the remote track ${videoTrack.jitsiTrack} to video with id ${id} has failed with `,
  288. error);
  289. });
  290. }
  291. /**
  292. * Removes the association to the component's video element from the passed
  293. * in redux representation of jitsi video track to stop the track from
  294. * rendering.
  295. *
  296. * @param {Object} videoTrack - The redux representation of the
  297. * {@code JitsiLocalTrack}.
  298. * @private
  299. * @returns {void}
  300. */
  301. _detachTrack(videoTrack?: Partial<ITrack>) {
  302. if (this._videoElement && videoTrack && videoTrack.jitsiTrack) {
  303. videoTrack.jitsiTrack.detach(this._videoElement);
  304. }
  305. }
  306. /**
  307. * Invokes the onvideoplaying callback if defined.
  308. *
  309. * @private
  310. * @returns {void}
  311. */
  312. _onVideoPlaying() {
  313. if (this.props.onVideoPlaying) {
  314. this.props.onVideoPlaying();
  315. }
  316. }
  317. /**
  318. * Sets an instance variable for the component's video element so it can be
  319. * referenced later for attaching and detaching a JitsiLocalTrack.
  320. *
  321. * @param {Object} element - DOM element for the component's video display.
  322. * @private
  323. * @returns {void}
  324. */
  325. _setVideoElement(element: HTMLVideoElement | null) {
  326. this._videoElement = element;
  327. }
  328. }
  329. export default Video;