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.

CalleeInfo.js 9.7KB

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