Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. import PropTypes from 'prop-types';
  2. import React, { Component } from 'react';
  3. import { View } from 'react-native';
  4. import { connect } from 'react-redux';
  5. import { sendAnalyticsEvent } from '../../analytics';
  6. import { AspectRatioAware, isNarrowAspectRatio } from '../../base/aspect-ratio';
  7. import { toggleAudioOnly } from '../../base/conference';
  8. import {
  9. MEDIA_TYPE,
  10. setAudioMuted,
  11. setVideoMuted,
  12. toggleCameraFacingMode,
  13. VIDEO_MUTISM_AUTHORITY
  14. } from '../../base/media';
  15. import { Container } from '../../base/react';
  16. import { ColorPalette } from '../../base/styles';
  17. import { beginRoomLockRequest } from '../../room-lock';
  18. import { beginShareRoom } from '../../share-room';
  19. import {
  20. abstractMapDispatchToProps,
  21. abstractMapStateToProps
  22. } from '../functions';
  23. import styles from './styles';
  24. import ToolbarButton from './ToolbarButton';
  25. /**
  26. * The indicator which determines (at bundle time) whether there should be a
  27. * {@code ToolbarButton} in {@code Toolbox} to expose the functionality of the
  28. * feature share-room in the user interface of the app.
  29. *
  30. * @private
  31. * @type {boolean}
  32. */
  33. const _SHARE_ROOM_TOOLBAR_BUTTON = true;
  34. /**
  35. * Implements the conference toolbox on React Native.
  36. */
  37. class Toolbox extends Component {
  38. /**
  39. * Toolbox component's property types.
  40. *
  41. * @static
  42. */
  43. static propTypes = {
  44. /**
  45. * Flag showing that audio is muted.
  46. */
  47. _audioMuted: PropTypes.bool,
  48. /**
  49. * Flag showing whether the audio-only mode is in use.
  50. */
  51. _audioOnly: PropTypes.bool,
  52. /**
  53. * Flag showing whether room is locked.
  54. */
  55. _locked: PropTypes.bool,
  56. /**
  57. * Handler for hangup.
  58. */
  59. _onHangup: PropTypes.func,
  60. /**
  61. * Sets the lock i.e. password protection of the conference/room.
  62. */
  63. _onRoomLock: PropTypes.func,
  64. /**
  65. * Begins the UI procedure to share the conference/room URL.
  66. */
  67. _onShareRoom: PropTypes.func,
  68. /**
  69. * Toggles the audio-only flag of the conference.
  70. */
  71. _onToggleAudioOnly: PropTypes.func,
  72. /**
  73. * Switches between the front/user-facing and back/environment-facing
  74. * cameras.
  75. */
  76. _onToggleCameraFacingMode: PropTypes.func,
  77. /**
  78. * Flag showing whether video is muted.
  79. */
  80. _videoMuted: PropTypes.bool,
  81. /**
  82. * Flag showing whether toolbar is visible.
  83. */
  84. _visible: PropTypes.bool,
  85. dispatch: PropTypes.func
  86. };
  87. /**
  88. * Initializes a new {@code Toolbox} instance.
  89. *
  90. * @param {Object} props - The read-only React {@code Component} props with
  91. * which the new instance is to be initialized.
  92. */
  93. constructor(props) {
  94. super(props);
  95. // Bind event handlers so they are only bound once per instance.
  96. this._onToggleAudio = this._onToggleAudio.bind(this);
  97. this._onToggleVideo = this._onToggleVideo.bind(this);
  98. }
  99. /**
  100. * Implements React's {@link Component#render()}.
  101. *
  102. * @inheritdoc
  103. * @returns {ReactElement}
  104. */
  105. render() {
  106. if (!this.props._visible) {
  107. return null;
  108. }
  109. return (
  110. <Container
  111. style = {
  112. isNarrowAspectRatio(this)
  113. ? styles.toolbarContainerNarrow
  114. : styles.toolbarContainerWide } >
  115. {
  116. isNarrowAspectRatio(this)
  117. ? this._renderSecondaryToolbar()
  118. : this._renderPrimaryToolbar()
  119. }
  120. {
  121. isNarrowAspectRatio(this)
  122. ? this._renderPrimaryToolbar()
  123. : this._renderSecondaryToolbar()
  124. }
  125. </Container>
  126. );
  127. }
  128. /**
  129. * Gets the styles for a button that toggles the mute state of a specific
  130. * media type.
  131. *
  132. * @param {string} mediaType - The {@link MEDIA_TYPE} associated with the
  133. * button to get styles for.
  134. * @protected
  135. * @returns {{
  136. * iconName: string,
  137. * iconStyle: Object,
  138. * style: Object
  139. * }}
  140. */
  141. _getMuteButtonStyles(mediaType) {
  142. let iconName;
  143. let iconStyle;
  144. let style;
  145. if (this.props[`_${mediaType}Muted`]) {
  146. iconName = this[`${mediaType}MutedIcon`];
  147. iconStyle = styles.whitePrimaryToolbarButtonIcon;
  148. style = styles.whitePrimaryToolbarButton;
  149. } else {
  150. iconName = this[`${mediaType}Icon`];
  151. iconStyle = styles.primaryToolbarButtonIcon;
  152. style = styles.primaryToolbarButton;
  153. }
  154. return {
  155. iconName,
  156. iconStyle,
  157. style
  158. };
  159. }
  160. /**
  161. * Dispatches an action to toggle the mute state of the audio/microphone.
  162. *
  163. * @private
  164. * @returns {void}
  165. */
  166. _onToggleAudio() {
  167. const mute = !this.props._audioMuted;
  168. sendAnalyticsEvent(`toolbar.audio.${mute ? 'muted' : 'unmuted'}`);
  169. // The user sees the reality i.e. the state of base/tracks and intends
  170. // to change reality by tapping on the respective button i.e. the user
  171. // sets the state of base/media. Whether the user's intention will turn
  172. // into reality is a whole different story which is of no concern to the
  173. // tapping.
  174. this.props.dispatch(
  175. setAudioMuted(
  176. mute,
  177. VIDEO_MUTISM_AUTHORITY.USER,
  178. /* ensureTrack */ true));
  179. }
  180. /**
  181. * Dispatches an action to toggle the mute state of the video/camera.
  182. *
  183. * @private
  184. * @returns {void}
  185. */
  186. _onToggleVideo() {
  187. const mute = !this.props._videoMuted;
  188. sendAnalyticsEvent(`toolbar.video.${mute ? 'muted' : 'unmuted'}`);
  189. // The user sees the reality i.e. the state of base/tracks and intends
  190. // to change reality by tapping on the respective button i.e. the user
  191. // sets the state of base/media. Whether the user's intention will turn
  192. // into reality is a whole different story which is of no concern to the
  193. // tapping.
  194. this.props.dispatch(
  195. setVideoMuted(
  196. !this.props._videoMuted,
  197. VIDEO_MUTISM_AUTHORITY.USER,
  198. /* ensureTrack */ true));
  199. }
  200. /**
  201. * Renders the toolbar which contains the primary buttons such as hangup,
  202. * audio and video mute.
  203. *
  204. * @private
  205. * @returns {ReactElement}
  206. */
  207. _renderPrimaryToolbar() {
  208. const audioButtonStyles = this._getMuteButtonStyles(MEDIA_TYPE.AUDIO);
  209. const videoButtonStyles = this._getMuteButtonStyles(MEDIA_TYPE.VIDEO);
  210. /* eslint-disable react/jsx-handler-names */
  211. return (
  212. <View style = { styles.primaryToolbar }>
  213. <ToolbarButton
  214. iconName = { audioButtonStyles.iconName }
  215. iconStyle = { audioButtonStyles.iconStyle }
  216. onClick = { this._onToggleAudio }
  217. style = { audioButtonStyles.style } />
  218. <ToolbarButton
  219. iconName = 'hangup'
  220. iconStyle = { styles.whitePrimaryToolbarButtonIcon }
  221. onClick = { this.props._onHangup }
  222. style = { styles.hangup }
  223. underlayColor = { ColorPalette.buttonUnderlay } />
  224. <ToolbarButton
  225. disabled = { this.props._audioOnly }
  226. iconName = { videoButtonStyles.iconName }
  227. iconStyle = { videoButtonStyles.iconStyle }
  228. onClick = { this._onToggleVideo }
  229. style = { videoButtonStyles.style } />
  230. </View>
  231. );
  232. /* eslint-enable react/jsx-handler-names */
  233. }
  234. /**
  235. * Renders the toolbar which contains the secondary buttons such as toggle
  236. * camera facing mode.
  237. *
  238. * @private
  239. * @returns {ReactElement}
  240. */
  241. _renderSecondaryToolbar() {
  242. const iconStyle = styles.secondaryToolbarButtonIcon;
  243. const style = styles.secondaryToolbarButton;
  244. const underlayColor = 'transparent';
  245. const {
  246. _audioOnly: audioOnly,
  247. _videoMuted: videoMuted
  248. } = this.props;
  249. /* eslint-disable react/jsx-curly-spacing,react/jsx-handler-names */
  250. return (
  251. <View style = { styles.secondaryToolbar }>
  252. <ToolbarButton
  253. disabled = { audioOnly || videoMuted }
  254. iconName = 'switch-camera'
  255. iconStyle = { iconStyle }
  256. onClick = { this.props._onToggleCameraFacingMode }
  257. style = { style }
  258. underlayColor = { underlayColor } />
  259. <ToolbarButton
  260. iconName = {
  261. this.props._locked ? 'security-locked' : 'security'
  262. }
  263. iconStyle = { iconStyle }
  264. onClick = { this.props._onRoomLock }
  265. style = { style }
  266. underlayColor = { underlayColor } />
  267. <ToolbarButton
  268. iconName = { audioOnly ? 'visibility-off' : 'visibility' }
  269. iconStyle = { iconStyle }
  270. onClick = { this.props._onToggleAudioOnly }
  271. style = { style }
  272. underlayColor = { underlayColor } />
  273. {
  274. _SHARE_ROOM_TOOLBAR_BUTTON
  275. && <ToolbarButton
  276. iconName = 'link'
  277. iconStyle = { iconStyle }
  278. onClick = { this.props._onShareRoom }
  279. style = { style }
  280. underlayColor = { underlayColor } />
  281. }
  282. </View>
  283. );
  284. /* eslint-enable react/jsx-curly-spacing,react/jsx-handler-names */
  285. }
  286. }
  287. /**
  288. * Additional properties for various icons, which are now platform-dependent.
  289. * This is done to have common logic of generating styles for web and native.
  290. * TODO As soon as we have common font sets for web and native, this will no
  291. * longer be required.
  292. */
  293. Object.assign(Toolbox.prototype, {
  294. audioIcon: 'microphone',
  295. audioMutedIcon: 'mic-disabled',
  296. videoIcon: 'camera',
  297. videoMutedIcon: 'camera-disabled'
  298. });
  299. /**
  300. * Maps actions to React component props.
  301. *
  302. * @param {Function} dispatch - Redux action dispatcher.
  303. * @returns {{
  304. * _onRoomLock: Function,
  305. * _onToggleAudioOnly: Function,
  306. * _onToggleCameraFacingMode: Function,
  307. * }}
  308. * @private
  309. */
  310. function _mapDispatchToProps(dispatch) {
  311. return {
  312. ...abstractMapDispatchToProps(dispatch),
  313. /**
  314. * Sets the lock i.e. password protection of the conference/room.
  315. *
  316. * @private
  317. * @returns {void}
  318. * @type {Function}
  319. */
  320. _onRoomLock() {
  321. dispatch(beginRoomLockRequest());
  322. },
  323. /**
  324. * Begins the UI procedure to share the conference/room URL.
  325. *
  326. * @private
  327. * @returns {void}
  328. * @type {Function}
  329. */
  330. _onShareRoom() {
  331. dispatch(beginShareRoom());
  332. },
  333. /**
  334. * Toggles the audio-only flag of the conference.
  335. *
  336. * @private
  337. * @returns {void}
  338. * @type {Function}
  339. */
  340. _onToggleAudioOnly() {
  341. dispatch(toggleAudioOnly());
  342. },
  343. /**
  344. * Switches between the front/user-facing and back/environment-facing
  345. * cameras.
  346. *
  347. * @private
  348. * @returns {void}
  349. * @type {Function}
  350. */
  351. _onToggleCameraFacingMode() {
  352. dispatch(toggleCameraFacingMode());
  353. }
  354. };
  355. }
  356. /**
  357. * Maps part of Redux store to React component props.
  358. *
  359. * @param {Object} state - Redux store.
  360. * @returns {{
  361. * _audioOnly: boolean,
  362. * _locked: boolean
  363. * }}
  364. * @private
  365. */
  366. function _mapStateToProps(state) {
  367. const conference = state['features/base/conference'];
  368. return {
  369. ...abstractMapStateToProps(state),
  370. /**
  371. * The indicator which determines whether the conference is in
  372. * audio-only mode.
  373. *
  374. * @protected
  375. * @type {boolean}
  376. */
  377. _audioOnly: Boolean(conference.audioOnly),
  378. /**
  379. * The indicator which determines whether the conference is
  380. * locked/password-protected.
  381. *
  382. * @protected
  383. * @type {boolean}
  384. */
  385. _locked: Boolean(conference.locked)
  386. };
  387. }
  388. export default connect(_mapStateToProps, _mapDispatchToProps)(
  389. AspectRatioAware(Toolbox));