[React] Call/ring overlay
Rewrite the non-React RingOverlay into the React Component CallOverlay in a way which makes it easier to port to React Native.
This commit is contained in:
parent
409255f056
commit
d437f3db03
|
@ -386,9 +386,8 @@ class ConferenceConnector {
|
|||
_onConferenceFailed(err, ...params) {
|
||||
APP.store.dispatch(conferenceFailed(room, err, ...params));
|
||||
logger.error('CONFERENCE FAILED:', err, ...params);
|
||||
APP.UI.hideRingOverlay();
|
||||
switch (err) {
|
||||
|
||||
switch (err) {
|
||||
case ConferenceErrors.CONNECTION_ERROR:
|
||||
{
|
||||
let [msg] = params;
|
||||
|
@ -2027,7 +2026,7 @@ export default {
|
|||
*/
|
||||
hangup(requestFeedback = false) {
|
||||
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
|
||||
APP.UI.hideRingOverlay();
|
||||
|
||||
let requestFeedbackPromise = requestFeedback
|
||||
? APP.UI.requestFeedbackOnHangup()
|
||||
// false - because the thank you dialog shouldn't be displayed
|
||||
|
|
|
@ -70,7 +70,13 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
|
|||
ENABLE_FEEDBACK_ANIMATION: false,
|
||||
DISABLE_FOCUS_INDICATOR: false,
|
||||
DISABLE_DOMINANT_SPEAKER_INDICATOR: false,
|
||||
// disables the ringing sound when the RingOverlay is shown.
|
||||
|
||||
/**
|
||||
* Whether the ringing sound in the call/ring overlay is disabled. If
|
||||
* {@code undefined}, defaults to {@code false}.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
DISABLE_RINGING: false,
|
||||
AUDIO_LEVEL_PRIMARY_COLOR: "rgba(255,255,255,0.4)",
|
||||
AUDIO_LEVEL_SECONDARY_COLOR: "rgba(255,255,255,0.2)",
|
||||
|
@ -82,8 +88,8 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
|
|||
|
||||
/**
|
||||
* Whether the mobile app Jitsi Meet is to be promoted to participants
|
||||
* attempting to join a conference in a mobile Web browser. If undefined,
|
||||
* default to true.
|
||||
* attempting to join a conference in a mobile Web browser. If
|
||||
* {@code undefined}, defaults to {@code true}.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
|
|
|
@ -19,18 +19,11 @@ import Filmstrip from "./videolayout/Filmstrip";
|
|||
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
|
||||
import Profile from "./side_pannels/profile/Profile";
|
||||
import Settings from "./../settings/Settings";
|
||||
import RingOverlay from "./ring_overlay/RingOverlay";
|
||||
import UIErrors from './UIErrors';
|
||||
import { debounce } from "../util/helpers";
|
||||
|
||||
|
||||
import {
|
||||
updateDeviceList
|
||||
} from '../../react/features/base/devices';
|
||||
import {
|
||||
setAudioMuted,
|
||||
setVideoMuted
|
||||
} from '../../react/features/base/media';
|
||||
import { updateDeviceList } from '../../react/features/base/devices';
|
||||
import { setAudioMuted, setVideoMuted } from '../../react/features/base/media';
|
||||
import {
|
||||
openDeviceSelectionDialog
|
||||
} from '../../react/features/device-selection';
|
||||
|
@ -368,10 +361,6 @@ UI.start = function () {
|
|||
"closeHtml": '<button type="button" tabIndex="-1">×</button>'
|
||||
};
|
||||
}
|
||||
|
||||
const { callee } = APP.store.getState()['features/jwt'];
|
||||
|
||||
callee && UI.showRingOverlay();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -492,7 +481,7 @@ UI.getSharedDocumentManager = () => etherpadManager;
|
|||
UI.addUser = function (user) {
|
||||
var id = user.getId();
|
||||
var displayName = user.getDisplayName();
|
||||
UI.hideRingOverlay();
|
||||
|
||||
if (UI.ContactList)
|
||||
UI.ContactList.addContact(id);
|
||||
|
||||
|
@ -1371,17 +1360,6 @@ UI.setCameraButtonEnabled
|
|||
UI.setMicrophoneButtonEnabled
|
||||
= enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
|
||||
|
||||
UI.showRingOverlay = function () {
|
||||
const { callee } = APP.store.getState()['features/jwt'];
|
||||
|
||||
callee && RingOverlay.show(callee, interfaceConfig.DISABLE_RINGING);
|
||||
|
||||
Filmstrip.toggleFilmstrip(false, false);
|
||||
};
|
||||
|
||||
UI.hideRingOverlay
|
||||
= () => RingOverlay.hide() && Filmstrip.toggleFilmstrip(true, false);
|
||||
|
||||
/**
|
||||
* Indicates if any the "top" overlays are currently visible. The check includes
|
||||
* the call/ring overlay, the suspended overlay, the GUM permissions overlay,
|
||||
|
@ -1390,16 +1368,11 @@ UI.hideRingOverlay
|
|||
* @returns {*|boolean} {true} if an overlay is visible; {false}, otherwise
|
||||
*/
|
||||
UI.isOverlayVisible = function () {
|
||||
return this.isRingOverlayVisible() || this.overlayVisible;
|
||||
return (
|
||||
this.overlayVisible
|
||||
|| APP.store.getState()['features/jwt'].callOverlayVisible);
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates if the ring overlay is currently visible.
|
||||
*
|
||||
* @returns {*|boolean} {true} if the ring overlay is visible, {false} otherwise
|
||||
*/
|
||||
UI.isRingOverlayVisible = () => RingOverlay.isVisible();
|
||||
|
||||
/**
|
||||
* Handles user's features changes.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -77,7 +77,6 @@ export function connect() {
|
|||
}
|
||||
})
|
||||
.catch(error => {
|
||||
APP.UI.hideRingOverlay();
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
logger.error(error);
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
/* @flow */
|
||||
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { SET_CALL_OVERLAY_VISIBLE } from '../jwt';
|
||||
|
||||
import Filmstrip from '../../../modules/UI/videolayout/Filmstrip';
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
|
||||
import {
|
||||
SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY,
|
||||
|
@ -10,19 +14,33 @@ import {
|
|||
declare var APP: Object;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
MiddlewareRegistry.register(({ getState }) => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
|
||||
case SET_FILMSTRIP_VISIBILITY: {
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.UI.emitEvent(UIEvents.UPDATED_FILMSTRIP_DISPLAY);
|
||||
case SET_CALL_OVERLAY_VISIBLE:
|
||||
if (typeof APP === 'undefined') {
|
||||
const oldValue
|
||||
= Boolean(getState()['features/jwt'].callOverlayVisible);
|
||||
const result = next(action);
|
||||
const newValue
|
||||
= Boolean(getState()['features/jwt'].callOverlayVisible);
|
||||
|
||||
oldValue === newValue
|
||||
|| Filmstrip.toggleFilmstrip(!newValue, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case SET_FILMSTRIP_REMOTE_VIDEOS_VISIBLITY:
|
||||
case SET_FILMSTRIP_VISIBILITY: {
|
||||
const result = next(action);
|
||||
|
||||
typeof APP === 'undefined'
|
||||
|| APP.UI.emitEvent(UIEvents.UPDATED_FILMSTRIP_DISPLAY);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return next(action);
|
||||
});
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
/**
|
||||
* The type of redux action which sets the visibility of {@code CallOverlay}.
|
||||
*
|
||||
* {
|
||||
* type: SET_CALL_OVERLAY_VISIBLE,
|
||||
* callOverlayVisible: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_CALL_OVERLAY_VISIBLE = Symbol('SET_CALL_OVERLAY_VISIBLE');
|
||||
|
||||
/**
|
||||
* The type of redux action which stores a specific JSON Web Token (JWT) into
|
||||
* the redux store.
|
||||
|
|
|
@ -1,6 +1,27 @@
|
|||
/* @flow */
|
||||
|
||||
import { SET_JWT } from './actionTypes';
|
||||
import { SET_CALL_OVERLAY_VISIBLE, SET_JWT } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Sets the visibility of {@code CallOverlay}.
|
||||
*
|
||||
* @param {boolean|undefined} callOverlayVisible - If {@code CallOverlay} is to
|
||||
* be displayed/visible, then {@code true}; otherwise, {@code false} or
|
||||
* {@code undefined}.
|
||||
* @returns {{
|
||||
* type: SET_CALL_OVERLAY_VISIBLE,
|
||||
* callOverlayVisible: (boolean|undefined)
|
||||
* }}
|
||||
*/
|
||||
export function setCallOverlayVisible(callOverlayVisible: boolean) {
|
||||
return (dispatch: Dispatch<*>, getState: Function) => {
|
||||
getState()['features/jwt'].callOverlayVisible === callOverlayVisible
|
||||
|| dispatch({
|
||||
type: SET_CALL_OVERLAY_VISIBLE,
|
||||
callOverlayVisible
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a specific JSON Web Token (JWT) into the redux store.
|
||||
|
|
|
@ -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';
|
|
@ -1,4 +1,5 @@
|
|||
export * from './actions';
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
|
|
|
@ -1,22 +1,38 @@
|
|||
import jwtDecode from 'jwt-decode';
|
||||
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
SET_ROOM
|
||||
} from '../base/conference';
|
||||
import { SET_CONFIG } from '../base/config';
|
||||
import { SET_LOCATION_URL } from '../base/connection';
|
||||
import { LIB_INIT_ERROR } from '../base/lib-jitsi-meet';
|
||||
import { PARTICIPANT_JOINED } from '../base/participants';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { setJWT } from './actions';
|
||||
import { setCallOverlayVisible, setJWT } from './actions';
|
||||
import { SET_JWT } from './actionTypes';
|
||||
import { parseJWTFromURLParams } from './functions';
|
||||
|
||||
/**
|
||||
* Middleware to parse token data upon setting a new room URL.
|
||||
*
|
||||
* @param {Store} store - The Redux store.
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_FAILED:
|
||||
case CONFERENCE_LEFT:
|
||||
case CONFERENCE_WILL_LEAVE:
|
||||
case LIB_INIT_ERROR:
|
||||
case PARTICIPANT_JOINED:
|
||||
case SET_ROOM:
|
||||
return _maybeSetCallOverlayVisible(store, next, action);
|
||||
|
||||
case SET_CONFIG:
|
||||
case SET_LOCATION_URL:
|
||||
// XXX The JSON Web Token (JWT) is not the only piece of state that we
|
||||
|
@ -33,16 +49,87 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies the feature jwt that a specific {@code action} is being dispatched
|
||||
* within a specific redux {@code store} which may have an effect on the
|
||||
* visiblity of (the) {@code CallOverlay}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action which is being dispatched in the
|
||||
* specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified {@code action}.
|
||||
*/
|
||||
function _maybeSetCallOverlayVisible({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const state = getState();
|
||||
const stateFeaturesJWT = state['features/jwt'];
|
||||
let callOverlayVisible;
|
||||
|
||||
if (stateFeaturesJWT.callee) {
|
||||
const { conference, leaving, room } = state['features/base/conference'];
|
||||
|
||||
// XXX The CallOverlay is to be displayed/visible as soon as
|
||||
// possible including even before the conference is joined.
|
||||
if (room && (!conference || conference !== leaving)) {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_FAILED:
|
||||
case CONFERENCE_LEFT:
|
||||
case CONFERENCE_WILL_LEAVE:
|
||||
case LIB_INIT_ERROR:
|
||||
// Because the CallOverlay is to be displayed/visible as soon as
|
||||
// possible even before the connection is established and/or the
|
||||
// conference is joined, it is very complicated to figure out
|
||||
// based on the current state alone. In order to reduce the
|
||||
// risks of displaying the CallOverly at inappropirate times, do
|
||||
// not even attempt to figure out based on the current state.
|
||||
// The (redux) actions listed above are also the equivalents of
|
||||
// the execution ponints at which APP.UI.hideRingOverlay() used
|
||||
// to be invoked.
|
||||
break;
|
||||
|
||||
default: {
|
||||
// The CallOverlay it to no longer be displayed/visible as soon
|
||||
// as another participant joins.
|
||||
const participants = state['features/base/participants'];
|
||||
|
||||
callOverlayVisible
|
||||
= Boolean(
|
||||
participants
|
||||
&& participants.length === 1
|
||||
&& participants[0].local);
|
||||
|
||||
// However, the CallDialog is not to be displayed/visible again
|
||||
// after all remote participants leave.
|
||||
if (callOverlayVisible
|
||||
&& stateFeaturesJWT.callOverlayVisible === false) {
|
||||
callOverlayVisible = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch(setCallOverlayVisible(callOverlayVisible));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature jwt that the action {@link SET_CONFIG} or
|
||||
* {@link SET_LOCATION_URL} is being dispatched within a specific Redux
|
||||
* {@link SET_LOCATION_URL} is being dispatched within a specific redux
|
||||
* {@code store}.
|
||||
*
|
||||
* @param {Store} store - The Redux store in which the specified {@code action}
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The Redux dispatch function to dispatch the
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The Redux action {@code SET_CONFIG} or
|
||||
* @param {Action} action - The redux action {@code SET_CONFIG} or
|
||||
* {@code SET_LOCATION_URL} which is being dispatched in the specified
|
||||
* {@code store}.
|
||||
* @private
|
||||
|
@ -65,26 +152,26 @@ function _setConfigOrLocationURL({ dispatch, getState }, next, action) {
|
|||
|
||||
/**
|
||||
* Notifies the feature jwt that the action {@link SET_JWT} is being dispatched
|
||||
* within a specific Redux {@code store}.
|
||||
* within a specific redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The Redux store in which the specified {@code action}
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The Redux dispatch function to dispatch the
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The Redux action {@code SET_JWT} which is being
|
||||
* @param {Action} action - The redux action {@code SET_JWT} which is being
|
||||
* dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified {@code action}.
|
||||
*/
|
||||
function _setJWT({ getState }, next, action) {
|
||||
function _setJWT(store, next, action) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { jwt, type, ...actionPayload } = action;
|
||||
|
||||
if (jwt && !Object.keys(actionPayload).length) {
|
||||
const {
|
||||
enableUserRolesBasedOnToken
|
||||
} = getState()['features/base/config'];
|
||||
} = store.getState()['features/base/config'];
|
||||
|
||||
action.isGuest = !enableUserRolesBasedOnToken;
|
||||
|
||||
|
@ -103,5 +190,5 @@ function _setJWT({ getState }, next, action) {
|
|||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
return _maybeSetCallOverlayVisible(store, next, action);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { equals, ReducerRegistry } from '../base/redux';
|
||||
import { equals, set, ReducerRegistry } from '../base/redux';
|
||||
|
||||
import { SET_JWT } from './actionTypes';
|
||||
import { SET_CALL_OVERLAY_VISIBLE, SET_JWT } from './actionTypes';
|
||||
|
||||
/**
|
||||
* The initial redux state of the feature jwt.
|
||||
|
@ -11,6 +11,14 @@ import { SET_JWT } from './actionTypes';
|
|||
* }}
|
||||
*/
|
||||
const _INITIAL_STATE = {
|
||||
/**
|
||||
* The indicator which determines whether (the) {@code CallOverlay} is
|
||||
* visible.
|
||||
*
|
||||
* @type {boolean|undefined}
|
||||
*/
|
||||
callOverlayVisible: undefined,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the local participant is a guest
|
||||
* in the conference.
|
||||
|
@ -31,6 +39,9 @@ const _INITIAL_STATE = {
|
|||
*/
|
||||
ReducerRegistry.register('features/jwt', (state = _INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SET_CALL_OVERLAY_VISIBLE:
|
||||
return set(state, 'callOverlayVisible', action.callOverlayVisible);
|
||||
|
||||
case SET_JWT: {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { type, ...payload } = action;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { CallOverlay } from '../../jwt';
|
||||
|
||||
import PageReloadFilmstripOnlyOverlay from './PageReloadFilmstripOnlyOverlay';
|
||||
import PageReloadOverlay from './PageReloadOverlay';
|
||||
import SuspendedFilmstripOnlyOverlay from './SuspendedFilmstripOnlyOverlay';
|
||||
|
@ -33,6 +35,15 @@ class OverlayContainer extends Component {
|
|||
*/
|
||||
_browser: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the {@link CallOverlay} is
|
||||
* displayed/visible.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_callOverlayVisible: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the status of the
|
||||
* JitsiConnection object has been "established" or not.
|
||||
|
@ -172,6 +183,8 @@ class OverlayContainer extends Component {
|
|||
props = {
|
||||
browser: this.props._browser
|
||||
};
|
||||
} else if (this.props._callOverlayVisible) {
|
||||
overlayComponent = CallOverlay;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -182,17 +195,18 @@ class OverlayContainer extends Component {
|
|||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated OverlayContainer's props.
|
||||
* Maps (parts of) the redux state to the associated OverlayContainer's props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {{
|
||||
* _browser: string,
|
||||
* _connectionEstablished: bool,
|
||||
* _haveToReload: bool,
|
||||
* _isNetworkFailure: bool,
|
||||
* _isMediaPermissionPromptVisible: bool,
|
||||
* _callOverlayVisible: boolean,
|
||||
* _connectionEstablished: boolean,
|
||||
* _haveToReload: boolean,
|
||||
* _isNetworkFailure: boolean,
|
||||
* _isMediaPermissionPromptVisible: boolean,
|
||||
* _reason: string,
|
||||
* _suspendDetected: bool
|
||||
* _suspendDetected: boolean
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
|
@ -203,18 +217,27 @@ function _mapStateToProps(state) {
|
|||
/**
|
||||
* The browser which is used currently.
|
||||
*
|
||||
* NOTE: Used by UserMediaPermissionsOverlay only.
|
||||
* NOTE: Used by {@link UserMediaPermissionsOverlay} only.
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
_browser: stateFeaturesOverlay.browser,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the {@link CallOverlay} is
|
||||
* displayed/visible.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_callOverlayVisible: Boolean(state['features/jwt'].callOverlayVisible),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the status of the
|
||||
* JitsiConnection object has been "established" or not.
|
||||
*
|
||||
* NOTE: Used by PageReloadOverlay only.
|
||||
* NOTE: Used by {@link PageReloadOverlay} only.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
|
@ -225,7 +248,7 @@ function _mapStateToProps(state) {
|
|||
* The indicator which determines whether a critical error for reload
|
||||
* has been received.
|
||||
*
|
||||
* NOTE: Used by PageReloadOverlay only.
|
||||
* NOTE: Used by {@link PageReloadOverlay} only.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
|
@ -236,7 +259,7 @@ function _mapStateToProps(state) {
|
|||
* The indicator which determines whether the GUM permissions prompt is
|
||||
* displayed or not.
|
||||
*
|
||||
* NOTE: Used by UserMediaPermissionsOverlay only.
|
||||
* NOTE: Used by {@link UserMediaPermissionsOverlay} only.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
|
@ -248,7 +271,7 @@ function _mapStateToProps(state) {
|
|||
* The indicator which determines whether the reload was caused by
|
||||
* network failure.
|
||||
*
|
||||
* NOTE: Used by PageReloadOverlay only.
|
||||
* NOTE: Used by {@link PageReloadOverlay} only.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
|
@ -258,7 +281,7 @@ function _mapStateToProps(state) {
|
|||
/**
|
||||
* The reason for the error that will cause the reload.
|
||||
*
|
||||
* NOTE: Used by PageReloadOverlay only.
|
||||
* NOTE: Used by {@link PageReloadOverlay} only.
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
|
@ -269,7 +292,7 @@ function _mapStateToProps(state) {
|
|||
* The indicator which determines whether the GUM permissions prompt is
|
||||
* displayed or not.
|
||||
*
|
||||
* NOTE: Used by SuspendedOverlay only.
|
||||
* NOTE: Used by {@link SuspendedOverlay} only.
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
|
|
|
@ -178,7 +178,7 @@ export function hideToolbox(force: boolean = false): Function {
|
|||
|
||||
if (!force
|
||||
&& (hovered
|
||||
|| APP.UI.isRingOverlayVisible()
|
||||
|| state['features/jwt'].callOverlayVisible
|
||||
|| SideContainerToggler.isVisible())) {
|
||||
dispatch(
|
||||
setToolboxTimeout(
|
||||
|
|
Loading…
Reference in New Issue