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.

CallOverlay.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // @flow
  2. import PropTypes from 'prop-types';
  3. import React, { Component } from 'react';
  4. import { connect } from 'react-redux';
  5. import { Audio } from '../../media';
  6. import { Avatar } from '../../participants';
  7. import { Container, Text } from '../../react';
  8. import UIEvents from '../../../../../service/UI/UIEvents';
  9. import styles from './styles';
  10. declare var $: Object;
  11. declare var APP: Object;
  12. declare var interfaceConfig: Object;
  13. /**
  14. * Implements a React {@link Component} which depicts the establishment of a
  15. * call with a specific remote callee.
  16. *
  17. * @extends Component
  18. */
  19. class CallOverlay extends Component<*, *> {
  20. /**
  21. * {@code CallOverlay} component's property types.
  22. *
  23. * @static
  24. */
  25. static propTypes = {
  26. _callee: PropTypes.object
  27. };
  28. /**
  29. * Determines whether this overlay needs to be rendered (according to a
  30. * specific redux state). Called by {@link OverlayContainer}.
  31. *
  32. * @param {Object} state - The redux state.
  33. * @returns {boolean} - If this overlay needs to be rendered, {@code true};
  34. * {@code false}, otherwise.
  35. */
  36. static needsRender(state) {
  37. return state['features/base/jwt'].callOverlayVisible;
  38. }
  39. /**
  40. * The (reference to the) {@link Audio} which plays/renders the audio
  41. * depicting the ringing phase of the call establishment represented by this
  42. * {@code CallOverlay}.
  43. */
  44. _audio: ?Audio
  45. _onLargeVideoAvatarVisible: Function
  46. _playAudioInterval: ?number
  47. _ringingTimeout: ?number
  48. _setAudio: Function
  49. state: {
  50. /**
  51. * The CSS class (name), if any, to add to this {@code CallOverlay}.
  52. *
  53. * @type {string}
  54. */
  55. className: ?string,
  56. /**
  57. * The indicator which determines whether this {@code CallOverlay}
  58. * should play/render audio to indicate the ringing phase of the
  59. * call establishment between the local participant and the
  60. * associated remote callee.
  61. *
  62. * @type {boolean}
  63. */
  64. renderAudio: boolean,
  65. /**
  66. * The indicator which determines whether this {@code CallOverlay}
  67. * is depicting the ringing phase of the call establishment between
  68. * the local participant and the associated remote callee or the
  69. * phase afterwards when the callee has not answered the call for a
  70. * period of time and, consequently, is considered unavailable.
  71. *
  72. * @type {boolean}
  73. */
  74. ringing: boolean
  75. }
  76. /**
  77. * Initializes a new {@code CallOverlay} instance.
  78. *
  79. * @param {Object} props - The read-only React {@link Component} props with
  80. * which the new instance is to be initialized.
  81. */
  82. constructor(props) {
  83. super(props);
  84. this.state = {
  85. className: undefined,
  86. renderAudio:
  87. typeof interfaceConfig !== 'object'
  88. || !interfaceConfig.DISABLE_RINGING,
  89. ringing: true
  90. };
  91. this._onLargeVideoAvatarVisible
  92. = this._onLargeVideoAvatarVisible.bind(this);
  93. this._setAudio = this._setAudio.bind(this);
  94. if (typeof APP === 'object') {
  95. APP.UI.addListener(
  96. UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
  97. this._onLargeVideoAvatarVisible);
  98. }
  99. }
  100. /**
  101. * Sets up timeouts such as the timeout to end the ringing phase of the call
  102. * establishment depicted by this {@code CallOverlay}.
  103. *
  104. * @inheritdoc
  105. */
  106. componentDidMount() {
  107. // Set up the timeout to end the ringing phase of the call establishment
  108. // depicted by this CallOverlay.
  109. if (this.state.ringing && !this._ringingTimeout) {
  110. this._ringingTimeout
  111. = setTimeout(
  112. () => {
  113. this._pauseAudio();
  114. this._ringingTimeout = undefined;
  115. this.setState({
  116. ringing: false
  117. });
  118. },
  119. 30000);
  120. }
  121. this._playAudio();
  122. }
  123. /**
  124. * Cleans up before this {@code Calleverlay} is unmounted and destroyed.
  125. *
  126. * @inheritdoc
  127. */
  128. componentWillUnmount() {
  129. this._pauseAudio();
  130. if (this._ringingTimeout) {
  131. clearTimeout(this._ringingTimeout);
  132. this._ringingTimeout = undefined;
  133. }
  134. if (typeof APP === 'object') {
  135. APP.UI.removeListener(
  136. UIEvents.LARGE_VIDEO_AVATAR_VISIBLE,
  137. this._onLargeVideoAvatarVisible);
  138. }
  139. }
  140. /**
  141. * Implements React's {@link Component#render()}.
  142. *
  143. * @inheritdoc
  144. * @returns {ReactElement}
  145. */
  146. render() {
  147. const { className, ringing } = this.state;
  148. const { avatarUrl, avatar, name } = this.props._callee;
  149. return (
  150. <Container
  151. { ...this._style('ringing', className) }
  152. id = 'ringOverlay'>
  153. <Container
  154. { ...this._style('ringing__content') }>
  155. <Text { ...this._style('ringing__text') }>
  156. { ringing ? 'Calling...' : '' }
  157. </Text>
  158. <Avatar
  159. { ...this._style('ringing__avatar') }
  160. uri = { avatarUrl || avatar } />
  161. <Container
  162. { ...this._style('ringing__caller-info') }>
  163. <Text
  164. { ...this._style('ringing__text') }>
  165. { name }
  166. { ringing ? '' : ' isn\'t available' }
  167. </Text>
  168. </Container>
  169. </Container>
  170. { this._renderAudio() }
  171. </Container>
  172. );
  173. }
  174. /**
  175. * Notifies this {@code CallOverlay} that the visibility of the
  176. * participant's avatar in the large video has changed.
  177. *
  178. * @param {boolean} largeVideoAvatarVisible - If the avatar in the large
  179. * video (i.e. of the participant on the stage) is visible, then
  180. * {@code true}; otherwise, {@code false}.
  181. * @private
  182. * @returns {void}
  183. */
  184. _onLargeVideoAvatarVisible(largeVideoAvatarVisible: boolean) {
  185. this.setState({
  186. className: largeVideoAvatarVisible ? 'solidBG' : undefined
  187. });
  188. }
  189. /**
  190. * Stops the playback of the audio which represents the ringing phase of the
  191. * call establishment depicted by this {@code CallOverlay}.
  192. *
  193. * @private
  194. * @returns {void}
  195. */
  196. _pauseAudio() {
  197. const audio = this._audio;
  198. if (audio) {
  199. audio.pause();
  200. }
  201. if (this._playAudioInterval) {
  202. clearInterval(this._playAudioInterval);
  203. this._playAudioInterval = undefined;
  204. }
  205. }
  206. /**
  207. * Starts the playback of the audio which represents the ringing phase of
  208. * the call establishment depicted by this {@code CallOverlay}.
  209. *
  210. * @private
  211. * @returns {void}
  212. */
  213. _playAudio() {
  214. if (this._audio) {
  215. this._audio.play();
  216. if (!this._playAudioInterval) {
  217. this._playAudioInterval
  218. = setInterval(() => this._playAudio(), 5000);
  219. }
  220. }
  221. }
  222. /**
  223. * Renders an audio element to represent the ringing phase of the call
  224. * establishment represented by this {@code CallOverlay}.
  225. *
  226. * @private
  227. * @returns {ReactElement}
  228. */
  229. _renderAudio() {
  230. if (this.state.renderAudio && this.state.ringing) {
  231. return (
  232. <Audio
  233. ref = { this._setAudio }
  234. src = './sounds/ring.ogg' />
  235. );
  236. }
  237. return null;
  238. }
  239. /**
  240. * Sets the (reference to the) {@link Audio} which renders the ringing phase
  241. * of the call establishment represented by this {@code CallOverlay}.
  242. *
  243. * @param {Audio} audio - The (reference to the) {@code Audio} which
  244. * plays/renders the audio depicting the ringing phase of the call
  245. * establishment represented by this {@code CallOverlay}.
  246. * @private
  247. * @returns {void}
  248. */
  249. _setAudio(audio) {
  250. this._audio = audio;
  251. }
  252. /**
  253. * Attempts to convert specified CSS class names into React
  254. * {@link Component} props {@code style} or {@code className}.
  255. *
  256. * @param {Array<string>} classNames - The CSS class names to convert
  257. * into React {@code Component} props {@code style} or {@code className}.
  258. * @returns {{
  259. * className: string,
  260. * style: Object
  261. * }}
  262. */
  263. _style(...classNames: Array<?string>) {
  264. let className = '';
  265. let style;
  266. for (const aClassName of classNames) {
  267. if (aClassName) {
  268. // Attemp to convert aClassName into style.
  269. if (styles && aClassName in styles) {
  270. // React Native will accept an Array as the value of the
  271. // style prop. However, I do not know about React.
  272. style = {
  273. ...style,
  274. ...styles[aClassName]
  275. };
  276. } else {
  277. // Otherwise, leave it as className.
  278. className += aClassName;
  279. }
  280. }
  281. }
  282. // Choose which of the className and/or style props has a value and,
  283. // consequently, must be returned.
  284. const props = {};
  285. if (className) {
  286. props.className = className;
  287. }
  288. if (style) {
  289. props.style = style;
  290. }
  291. return props;
  292. }
  293. }
  294. /**
  295. * Maps (parts of) the redux state to {@code CallOverlay}'s props.
  296. *
  297. * @param {Object} state - The redux state.
  298. * @private
  299. * @returns {{
  300. * _callee: Object
  301. * }}
  302. */
  303. function _mapStateToProps(state) {
  304. return {
  305. /**
  306. *
  307. * @private
  308. * @type {Object}
  309. */
  310. _callee: state['features/base/jwt'].callee
  311. };
  312. }
  313. export default connect(_mapStateToProps)(CallOverlay);