mirror of https://github.com/jitsi/jitsi-meet
Rewrite the non-React RingOverlay into the React Component CallOverlay in a way which makes it easier to port to React Native.pull/1610/merge jitsi-meet_2135
parent
409255f056
commit
d437f3db03
@ -1,178 +0,0 @@ |
||||
/* global $, APP */ |
||||
|
||||
import UIEvents from "../../../service/UI/UIEvents"; |
||||
|
||||
/** |
||||
* Store the current ring overlay instance. |
||||
* Note: We want to have only 1 instance at a time. |
||||
*/ |
||||
let overlay = null; |
||||
|
||||
/** |
||||
* Handler for UIEvents.LARGE_VIDEO_AVATAR_VISIBLE event. |
||||
* @param {boolean} shown indicates whether the avatar on the large video is |
||||
* currently displayed or not. |
||||
*/ |
||||
function onAvatarVisible(shown) { |
||||
overlay._changeBackground(shown); |
||||
} |
||||
|
||||
/** |
||||
* Shows ring overlay |
||||
*/ |
||||
class RingOverlay { |
||||
/** |
||||
* |
||||
* @param callee The callee (Object) as defined by the JWT support. |
||||
* @param {boolean} disableRinging if true the ringing sound wont be played. |
||||
*/ |
||||
constructor(callee, disableRinging) { |
||||
this._containerId = 'ringOverlay'; |
||||
this._audioContainerId = 'ringOverlayRinging'; |
||||
this.isRinging = true; |
||||
this.callee = callee; |
||||
this.disableRinging = disableRinging; |
||||
this.render(); |
||||
if (!disableRinging) |
||||
this._initAudio(); |
||||
this._timeout |
||||
= setTimeout( |
||||
() => { |
||||
this.destroy(); |
||||
this.render(); |
||||
}, |
||||
30000); |
||||
} |
||||
|
||||
/** |
||||
* Initializes the audio element and setups the interval for playing it. |
||||
*/ |
||||
_initAudio() { |
||||
this.audio = document.getElementById(this._audioContainerId); |
||||
this.audio.play(); |
||||
this.interval = setInterval(() => this.audio.play(), 5000); |
||||
} |
||||
|
||||
/** |
||||
* Chagnes the background of the ring overlay. |
||||
* @param {boolean} solid - if true the new background will be the solid |
||||
* one, otherwise the background will be default one. |
||||
* NOTE: The method just toggles solidBG css class. |
||||
*/ |
||||
_changeBackground(solid) { |
||||
const container = $("#" + this._containerId); |
||||
|
||||
if (solid) { |
||||
container.addClass("solidBG"); |
||||
} else { |
||||
container.removeClass("solidBG"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Builds and appends the ring overlay to the html document |
||||
*/ |
||||
_getHtmlStr(callee) { |
||||
let callingLabel = this.isRinging ? "<p>Calling...</p>" : ""; |
||||
let callerStateLabel = this.isRinging ? "" : " isn't available"; |
||||
let audioHTML = this.disableRinging ? "" |
||||
: "<audio id=\"" + this._audioContainerId |
||||
+ "\" src=\"./sounds/ring.ogg\" />"; |
||||
|
||||
return ` |
||||
<div id="${this._containerId}" class='ringing' > |
||||
<div class='ringing__content'> |
||||
${callingLabel} |
||||
<img class='ringing__avatar' src="${callee.avatarUrl}" /> |
||||
<div class="ringing__caller-info"> |
||||
<p>${callee.name}${callerStateLabel}</p> |
||||
</div> |
||||
</div> |
||||
${audioHTML} |
||||
</div>`; |
||||
} |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
render() { |
||||
this.htmlStr = this._getHtmlStr(this.callee); |
||||
this._attach(); |
||||
} |
||||
|
||||
/** |
||||
* Destroys and clears all the objects (html elements and audio interval) |
||||
* related to the ring overlay. |
||||
*/ |
||||
destroy() { |
||||
this.isRinging = false; |
||||
this._stopAudio(); |
||||
this._detach(); |
||||
} |
||||
|
||||
_attach() { |
||||
$("body").append(this.htmlStr); |
||||
} |
||||
|
||||
_detach() { |
||||
$(`#${this._containerId}`).remove(); |
||||
} |
||||
|
||||
/** |
||||
* Stops the ringing and clears related timers. |
||||
*/ |
||||
_stopAudio() { |
||||
if (this.interval) { |
||||
clearInterval(this.interval); |
||||
} |
||||
if (this._timeout) { |
||||
clearTimeout(this._timeout); |
||||
} |
||||
} |
||||
} |
||||
|
||||
export default { |
||||
/** |
||||
* Shows the ring overlay for the passed callee. |
||||
* |
||||
* @param {Object} callee - The callee. Object containing data about |
||||
* callee. |
||||
* @param {boolean} disableRinging - If true the ringing sound won't be |
||||
* played. |
||||
* @returns {void} |
||||
*/ |
||||
show(callee, disableRinging = false) { |
||||
if (overlay) { |
||||
this.hide(); |
||||
} |
||||
|
||||
overlay = new RingOverlay(callee, disableRinging); |
||||
APP.UI.addListener(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE, |
||||
onAvatarVisible); |
||||
}, |
||||
|
||||
/** |
||||
* Hides the ring overlay. Destroys all the elements related to the ring |
||||
* overlay. |
||||
*/ |
||||
hide() { |
||||
if (!overlay) { |
||||
return false; |
||||
} |
||||
overlay.destroy(); |
||||
overlay = null; |
||||
APP.UI.removeListener(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE, |
||||
onAvatarVisible); |
||||
return true; |
||||
}, |
||||
|
||||
/** |
||||
* Checks whether or not the ring overlay is currently displayed. |
||||
* |
||||
* @returns {boolean} true if the ring overlay is currently displayed or |
||||
* false otherwise. |
||||
*/ |
||||
isVisible() { |
||||
return overlay !== null; |
||||
} |
||||
}; |
@ -0,0 +1,288 @@ |
||||
/* @flow */ |
||||
|
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { Audio } from '../../base/media'; |
||||
import { Avatar } from '../../base/participants'; |
||||
import { Container, Text } from '../../base/react'; |
||||
import UIEvents from '../../../../service/UI/UIEvents'; |
||||
|
||||
declare var $: Object; |
||||
declare var APP: Object; |
||||
declare var interfaceConfig: Object; |
||||
|
||||
/** |
||||
* Implements a React {@link Component} which depicts the establishment of a |
||||
* call with a specific remote callee. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class CallOverlay extends Component { |
||||
/** |
||||
* The (reference to the) {@link Audio} which plays/renders the audio |
||||
* depicting the ringing phase of the call establishment represented by this |
||||
* {@code CallOverlay}. |
||||
*/ |
||||
_audio: ?Audio |
||||
|
||||
_onLargeVideoAvatarVisible: Function |
||||
|
||||
_playAudioInterval: ?number |
||||
|
||||
_ringingTimeout: ?number |
||||
|
||||
_setAudio: Function |
||||
|
||||
state: { |
||||
|
||||
/** |
||||
* The CSS class (name), if any, to add to this {@code CallOverlay}. |
||||
* |
||||
* @type {string} |
||||
*/ |
||||
className: ?string, |
||||
|
||||
/** |
||||
* The indicator which determines whether this {@code CallOverlay} |
||||
* should play/render audio to indicate the ringing phase of the |
||||
* call establishment between the local participant and the |
||||
* associated remote callee. |
||||
* |
||||
* @type {boolean} |
||||
*/ |
||||
renderAudio: boolean, |
||||
|
||||
/** |
||||
* The indicator which determines whether this {@code CallOverlay} |
||||
* is depicting the ringing phase of the call establishment between |
||||
* the local participant and the associated remote callee or the |
||||
* phase afterwards when the callee has not answered the call for a |
||||
* period of time and, consequently, is considered unavailable. |
||||
* |
||||
* @type {boolean} |
||||
*/ |
||||
ringing: boolean |
||||
} |
||||
|
||||
/** |
||||
* {@code CallOverlay} component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
_callee: React.PropTypes.object |
||||
}; |
||||
|
||||
/** |
||||
* Initializes a new {@code CallOverlay} instance. |
||||
* |
||||
* @param {Object} props - The read-only React {@link Component} props with |
||||
* which the new instance is to be initialized. |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
this.state = { |
||||
className: undefined, |
||||
renderAudio: |
||||
typeof interfaceConfig !== 'object' |
||||
|| !interfaceConfig.DISABLE_RINGING, |
||||
ringing: true |
||||
}; |
||||
|
||||
this._onLargeVideoAvatarVisible |
||||
= this._onLargeVideoAvatarVisible.bind(this); |
||||
this._setAudio = this._setAudio.bind(this); |
||||
|
||||
if (typeof APP === 'object') { |
||||
APP.UI.addListener( |
||||
UIEvents.LARGE_VIDEO_AVATAR_VISIBLE, |
||||
this._onLargeVideoAvatarVisible); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets up timeouts such as the timeout to end the ringing phase of the call |
||||
* establishment depicted by this {@code CallOverlay}. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
componentDidMount() { |
||||
// Set up the timeout to end the ringing phase of the call establishment
|
||||
// depicted by this CallOverlay.
|
||||
if (this.state.ringing && !this._ringingTimeout) { |
||||
this._ringingTimeout |
||||
= setTimeout( |
||||
() => { |
||||
this._pauseAudio(); |
||||
|
||||
this._ringingTimeout = undefined; |
||||
this.setState({ |
||||
ringing: false |
||||
}); |
||||
}, |
||||
30000); |
||||
} |
||||
|
||||
this._playAudio(); |
||||
} |
||||
|
||||
/** |
||||
* Cleans up before this {@code Calleverlay} is unmounted and destroyed. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
componentWillUnmount() { |
||||
this._pauseAudio(); |
||||
|
||||
if (this._ringingTimeout) { |
||||
clearTimeout(this._ringingTimeout); |
||||
this._ringingTimeout = undefined; |
||||
} |
||||
|
||||
if (typeof APP === 'object') { |
||||
APP.UI.removeListener( |
||||
UIEvents.LARGE_VIDEO_AVATAR_VISIBLE, |
||||
this._onLargeVideoAvatarVisible); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { className, ringing } = this.state; |
||||
const { avatarUrl, name } = this.props._callee; |
||||
|
||||
return ( |
||||
<Container |
||||
className = { `ringing ${className || ''}` } |
||||
id = 'ringOverlay'> |
||||
<Container className = 'ringing__content'> |
||||
{ ringing ? <Text>Calling...</Text> : null } |
||||
<Avatar |
||||
className = 'ringing__avatar' |
||||
uri = { avatarUrl } /> |
||||
<Container className = 'ringing__caller-info'> |
||||
<Text> |
||||
{ name } |
||||
{ ringing ? null : ' isn\'t available' } |
||||
</Text> |
||||
</Container> |
||||
</Container> |
||||
{ this._renderAudio() } |
||||
</Container> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Notifies this {@code CallOverlay} that the visibility of the |
||||
* participant's avatar in the large video has changed. |
||||
* |
||||
* @param {boolean} largeVideoAvatarVisible - If the avatar in the large |
||||
* video (i.e. of the participant on the stage) is visible, then |
||||
* {@code true}; otherwise, {@code false}. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onLargeVideoAvatarVisible(largeVideoAvatarVisible: boolean) { |
||||
this.setState({ |
||||
className: largeVideoAvatarVisible ? 'solidBG' : undefined |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Stops the playback of the audio which represents the ringing phase of the |
||||
* call establishment depicted by this {@code CallOverlay}. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_pauseAudio() { |
||||
const audio = this._audio; |
||||
|
||||
if (audio) { |
||||
audio.pause(); |
||||
} |
||||
if (this._playAudioInterval) { |
||||
clearInterval(this._playAudioInterval); |
||||
this._playAudioInterval = undefined; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Starts the playback of the audio which represents the ringing phase of |
||||
* the call establishment depicted by this {@code CallOverlay}. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_playAudio() { |
||||
if (this._audio) { |
||||
this._audio.play(); |
||||
if (!this._playAudioInterval) { |
||||
this._playAudioInterval |
||||
= setInterval(() => this._playAudio(), 5000); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Renders an audio element to represent the ringing phase of the call |
||||
* establishment represented by this {@code CallOverlay}. |
||||
* |
||||
* @private |
||||
* @returns {ReactElement} |
||||
*/ |
||||
_renderAudio() { |
||||
if (this.state.renderAudio && this.state.ringing) { |
||||
return ( |
||||
<Audio |
||||
ref = { this._setAudio } |
||||
src = './sounds/ring.ogg' /> |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Sets the (reference to the) {@link Audio} which renders the ringing phase |
||||
* of the call establishment represented by this {@code CallOverlay}. |
||||
* |
||||
* @param {Audio} audio - The (reference to the) {@code Audio} which |
||||
* plays/renders the audio depicting the ringing phase of the call |
||||
* establishment represented by this {@code CallOverlay}. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_setAudio(audio) { |
||||
this._audio = audio; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the redux state to {@code CallOverlay}'s props. |
||||
* |
||||
* @param {Object} state - The redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _callee: Object |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
return { |
||||
/** |
||||
* |
||||
* @private |
||||
* @type {Object} |
||||
*/ |
||||
_callee: state['features/jwt'].callee |
||||
}; |
||||
} |
||||
|
||||
export default connect(_mapStateToProps)(CallOverlay); |
@ -0,0 +1 @@ |
||||
export { default as CallOverlay } from './CallOverlay'; |
Loading…
Reference in new issue