Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

VideoSettingsContent.js 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // @flow
  2. import React, { Component } from 'react';
  3. import { translate } from '../../../../base/i18n';
  4. import Video from '../../../../base/media/components/Video';
  5. import { equals } from '../../../../base/redux';
  6. import { createLocalVideoTracks } from '../../../functions';
  7. const videoClassName = 'video-preview-video flipVideoX';
  8. /**
  9. * The type of the React {@code Component} props of {@link VideoSettingsContent}.
  10. */
  11. export type Props = {
  12. /**
  13. * The deviceId of the camera device currently being used.
  14. */
  15. currentCameraDeviceId: string,
  16. /**
  17. * Callback invoked to change current camera.
  18. */
  19. setVideoInputDevice: Function,
  20. /**
  21. * Invoked to obtain translated strings.
  22. */
  23. t: Function,
  24. /**
  25. * Callback invoked to toggle the settings popup visibility.
  26. */
  27. toggleVideoSettings: Function,
  28. /**
  29. * All the camera device ids currently connected.
  30. */
  31. videoDeviceIds: string[],
  32. };
  33. /**
  34. * The type of the React {@code Component} state of {@link VideoSettingsContent}.
  35. */
  36. type State = {
  37. /**
  38. * An array of all the jitsiTracks and eventual errors.
  39. */
  40. trackData: Object[],
  41. };
  42. /**
  43. * Implements a React {@link Component} which displays a list of video
  44. * previews to choose from.
  45. *
  46. * @extends Component
  47. */
  48. class VideoSettingsContent extends Component<Props, State> {
  49. _componentWasUnmounted: boolean;
  50. _videoContentRef: Object;
  51. /**
  52. * Initializes a new {@code VideoSettingsContent} instance.
  53. *
  54. * @param {Object} props - The read-only properties with which the new
  55. * instance is to be initialized.
  56. */
  57. constructor(props) {
  58. super(props);
  59. this._onEscClick = this._onEscClick.bind(this);
  60. this._videoContentRef = React.createRef();
  61. this.state = {
  62. trackData: new Array(props.videoDeviceIds.length).fill({
  63. jitsiTrack: null
  64. })
  65. };
  66. }
  67. _onEscClick: (KeyboardEvent) => void;
  68. /**
  69. * Click handler for the video entries.
  70. *
  71. * @param {KeyboardEvent} event - Esc key click to close the popup.
  72. * @returns {void}
  73. */
  74. _onEscClick(event) {
  75. if (event.key === 'Escape') {
  76. event.preventDefault();
  77. event.stopPropagation();
  78. this._videoContentRef.current.style.display = 'none';
  79. }
  80. }
  81. /**
  82. * Creates and updates the track data.
  83. *
  84. * @returns {void}
  85. */
  86. async _setTracks() {
  87. this._disposeTracks(this.state.trackData);
  88. const trackData = await createLocalVideoTracks(this.props.videoDeviceIds, 5000);
  89. // In case the component gets unmounted before the tracks are created
  90. // avoid a leak by not setting the state
  91. if (this._componentWasUnmounted) {
  92. this._disposeTracks(trackData);
  93. } else {
  94. this.setState({
  95. trackData
  96. });
  97. }
  98. }
  99. /**
  100. * Destroys all the tracks from trackData object.
  101. *
  102. * @param {Object[]} trackData - An array of tracks that are to be disposed.
  103. * @returns {Promise<void>}
  104. */
  105. _disposeTracks(trackData) {
  106. trackData.forEach(({ jitsiTrack }) => {
  107. jitsiTrack && jitsiTrack.dispose();
  108. });
  109. }
  110. /**
  111. * Returns the click handler used when selecting the video preview.
  112. *
  113. * @param {string} deviceId - The id of the camera device.
  114. * @returns {Function}
  115. */
  116. _onEntryClick(deviceId) {
  117. return () => {
  118. this.props.setVideoInputDevice(deviceId);
  119. this.props.toggleVideoSettings();
  120. };
  121. }
  122. /**
  123. * Renders a preview entry.
  124. *
  125. * @param {Object} data - The track data.
  126. * @param {number} index - The index of the entry.
  127. * @returns {React$Node}
  128. */
  129. _renderPreviewEntry(data, index) {
  130. const { error, jitsiTrack, deviceId } = data;
  131. const { currentCameraDeviceId, t } = this.props;
  132. const isSelected = deviceId === currentCameraDeviceId;
  133. const key = `vp-${index}`;
  134. const className = 'video-preview-entry';
  135. const tabIndex = '0';
  136. if (error) {
  137. return (
  138. <div
  139. className = { className }
  140. key = { key }
  141. tabIndex = { -1 } >
  142. <div className = 'video-preview-error'>{t(error)}</div>
  143. </div>
  144. );
  145. }
  146. const props: Object = {
  147. className,
  148. key,
  149. tabIndex
  150. };
  151. const label = jitsiTrack && jitsiTrack.getTrackLabel();
  152. if (isSelected) {
  153. props['aria-checked'] = true;
  154. props.className = `${className} video-preview-entry--selected`;
  155. } else {
  156. props.onClick = this._onEntryClick(deviceId);
  157. props.onKeyPress = e => {
  158. if (e.key === ' ' || e.key === 'Enter') {
  159. e.preventDefault();
  160. props.onClick();
  161. }
  162. };
  163. }
  164. return (
  165. <div
  166. { ...props }
  167. role = 'radio'>
  168. <div className = 'video-preview-label'>
  169. {label && <div className = 'video-preview-label-container'>
  170. <div className = 'video-preview-label-text'>
  171. <span>{label}</span></div>
  172. </div>}
  173. </div>
  174. <div className = 'video-preview-overlay' />
  175. <Video
  176. className = { videoClassName }
  177. playsinline = { true }
  178. videoTrack = {{ jitsiTrack }} />
  179. </div>
  180. );
  181. }
  182. /**
  183. * Implements React's {@link Component#componentDidMount}.
  184. *
  185. * @inheritdoc
  186. */
  187. componentDidMount() {
  188. this._setTracks();
  189. }
  190. /**
  191. * Implements React's {@link Component#componentWillUnmount}.
  192. *
  193. * @inheritdoc
  194. */
  195. componentWillUnmount() {
  196. this._componentWasUnmounted = true;
  197. this._disposeTracks(this.state.trackData);
  198. }
  199. /**
  200. * Implements React's {@link Component#componentDidUpdate}.
  201. *
  202. * @inheritdoc
  203. */
  204. componentDidUpdate(prevProps) {
  205. if (!equals(this.props.videoDeviceIds, prevProps.videoDeviceIds)) {
  206. this._setTracks();
  207. }
  208. }
  209. /**
  210. * Implements React's {@link Component#render}.
  211. *
  212. * @inheritdoc
  213. */
  214. render() {
  215. const { trackData } = this.state;
  216. return (
  217. <div
  218. aria-labelledby = 'video-settings-button'
  219. className = 'video-preview-container'
  220. id = 'video-settings-dialog'
  221. onKeyDown = { this._onEscClick }
  222. ref = { this._videoContentRef }
  223. role = 'radiogroup'
  224. tabIndex = '-1'>
  225. {trackData.map((data, i) => this._renderPreviewEntry(data, i))}
  226. </div>
  227. );
  228. }
  229. }
  230. export default translate(VideoSettingsContent);