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

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