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

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