2018-05-24 16:35:26 +02:00
|
|
|
// @flow
|
|
|
|
|
2018-06-27 22:50:42 +02:00
|
|
|
import Spinner from '@atlaskit/spinner';
|
|
|
|
|
2018-05-26 23:51:12 +02:00
|
|
|
import React, { Component } from 'react';
|
2018-06-03 04:36:16 +02:00
|
|
|
import type { Dispatch } from 'redux';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { push } from 'react-router-redux';
|
2018-05-21 21:17:24 +02:00
|
|
|
|
|
|
|
import {
|
|
|
|
RemoteControl,
|
|
|
|
setupScreenSharingForWindow,
|
|
|
|
setupAlwaysOnTopRender,
|
2018-06-13 00:39:56 +02:00
|
|
|
initPopupsConfigurationRender,
|
2018-05-21 21:17:24 +02:00
|
|
|
setupWiFiStats
|
|
|
|
} from 'jitsi-meet-electron-utils';
|
|
|
|
|
2018-05-21 22:06:55 +02:00
|
|
|
import config from '../../config';
|
2018-06-09 18:10:00 +02:00
|
|
|
import { setEmail, setName } from '../../settings';
|
2018-05-21 21:17:24 +02:00
|
|
|
|
2018-07-25 13:35:50 +02:00
|
|
|
import { conferenceEnded, conferenceJoined } from '../actions';
|
2018-06-27 22:50:42 +02:00
|
|
|
import { LoadingIndicator, Wrapper } from '../styled';
|
2018-06-28 01:52:07 +02:00
|
|
|
import { getExternalApiURL } from '../../utils';
|
2018-05-26 23:51:12 +02:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
|
2018-06-09 18:10:00 +02:00
|
|
|
/**
|
|
|
|
* Redux dispatch.
|
|
|
|
*/
|
|
|
|
dispatch: Dispatch<*>;
|
|
|
|
|
2018-05-26 23:51:12 +02:00
|
|
|
/**
|
2018-06-25 10:22:15 +02:00
|
|
|
* React Router location object.
|
2018-05-26 23:51:12 +02:00
|
|
|
*/
|
2018-06-25 10:22:15 +02:00
|
|
|
location: Object;
|
2018-06-03 04:36:16 +02:00
|
|
|
|
|
|
|
/**
|
2018-06-09 18:10:00 +02:00
|
|
|
* Avatar URL.
|
2018-06-03 04:36:16 +02:00
|
|
|
*/
|
2018-06-09 18:10:00 +02:00
|
|
|
_avatarURL: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Email of user.
|
|
|
|
*/
|
|
|
|
_email: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Name of user.
|
|
|
|
*/
|
|
|
|
_name: string;
|
|
|
|
|
2018-06-23 02:51:50 +02:00
|
|
|
/**
|
|
|
|
* Default Jitsi Server URL.
|
|
|
|
*/
|
|
|
|
_serverURL: string;
|
2018-07-01 06:25:06 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Start with Audio Muted.
|
|
|
|
*/
|
|
|
|
_startWithAudioMuted: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start with Video Muted.
|
|
|
|
*/
|
|
|
|
_startWithVideoMuted: boolean;
|
2018-05-26 23:51:12 +02:00
|
|
|
};
|
|
|
|
|
2018-06-27 22:50:42 +02:00
|
|
|
type State = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If the conference is loading or not.
|
|
|
|
*/
|
|
|
|
isLoading: boolean;
|
|
|
|
};
|
|
|
|
|
2018-05-21 21:17:24 +02:00
|
|
|
/**
|
2018-05-24 17:04:58 +02:00
|
|
|
* Conference component.
|
2018-05-21 21:17:24 +02:00
|
|
|
*/
|
2018-06-27 22:50:42 +02:00
|
|
|
class Conference extends Component<Props, State> {
|
2018-05-26 23:51:12 +02:00
|
|
|
/**
|
|
|
|
* External API object.
|
|
|
|
*/
|
|
|
|
_api: Object;
|
|
|
|
|
2018-07-25 13:35:50 +02:00
|
|
|
/**
|
|
|
|
* Conference Object.
|
|
|
|
*/
|
|
|
|
_conference: Object;
|
|
|
|
|
2018-09-23 10:49:17 +02:00
|
|
|
/**
|
|
|
|
* Timer to cancel the joining if it takes too long.
|
|
|
|
*/
|
|
|
|
_loadTimer: ?TimeoutID;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reference to the element of this component.
|
|
|
|
*/
|
|
|
|
_ref: Object;
|
|
|
|
|
2018-05-21 21:17:24 +02:00
|
|
|
/**
|
2018-05-26 23:51:12 +02:00
|
|
|
* Initializes a new {@code Conference} instance.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
|
2018-06-27 22:50:42 +02:00
|
|
|
this.state = {
|
|
|
|
isLoading: true
|
|
|
|
};
|
|
|
|
|
2018-05-26 23:51:12 +02:00
|
|
|
this._ref = React.createRef();
|
2018-10-10 10:20:44 +02:00
|
|
|
|
|
|
|
this._onIframeLoad = this._onIframeLoad.bind(this);
|
2018-05-26 23:51:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attach the script to this component.
|
2018-06-07 09:47:42 +02:00
|
|
|
*
|
|
|
|
* @returns {void}
|
2018-05-21 21:17:24 +02:00
|
|
|
*/
|
|
|
|
componentDidMount() {
|
2018-05-26 23:51:12 +02:00
|
|
|
const parentNode = this._ref.current;
|
2018-06-25 10:22:15 +02:00
|
|
|
const room = this.props.location.state.room;
|
|
|
|
const serverURL = this.props.location.state.serverURL
|
2018-06-23 02:51:50 +02:00
|
|
|
|| this.props._serverURL
|
2018-06-25 10:22:15 +02:00
|
|
|
|| config.defaultServerURL;
|
2018-05-26 23:51:12 +02:00
|
|
|
|
2018-07-25 13:35:50 +02:00
|
|
|
this._conference = {
|
|
|
|
room,
|
|
|
|
serverURL
|
|
|
|
};
|
|
|
|
|
2018-05-21 21:17:24 +02:00
|
|
|
const script = document.createElement('script');
|
|
|
|
|
|
|
|
script.async = true;
|
2018-07-25 13:35:50 +02:00
|
|
|
script.onload = () => this._onScriptLoad(parentNode);
|
2018-07-08 08:37:31 +02:00
|
|
|
script.onerror = (event: Event) =>
|
|
|
|
this._navigateToHome(event, room, serverURL);
|
2018-06-28 01:52:07 +02:00
|
|
|
script.src = getExternalApiURL(serverURL);
|
2018-05-21 21:17:24 +02:00
|
|
|
|
2018-05-26 23:51:12 +02:00
|
|
|
this._ref.current.appendChild(script);
|
2018-09-23 10:49:17 +02:00
|
|
|
|
2018-10-10 10:20:44 +02:00
|
|
|
// Set a timer for 10s, if we haven't loaded the iframe by then,
|
|
|
|
// give up.
|
2018-09-23 10:49:17 +02:00
|
|
|
this._loadTimer = setTimeout(() => {
|
|
|
|
this._navigateToHome(
|
|
|
|
|
|
|
|
// $FlowFixMe
|
|
|
|
{
|
|
|
|
error: 'Loading error',
|
|
|
|
type: 'error'
|
|
|
|
},
|
|
|
|
room,
|
|
|
|
serverURL);
|
|
|
|
}, 10000);
|
2018-05-21 21:17:24 +02:00
|
|
|
}
|
|
|
|
|
2018-06-09 18:10:00 +02:00
|
|
|
/**
|
|
|
|
* Keep profile settings in sync with Conference.
|
|
|
|
*
|
|
|
|
* @param {Props} prevProps - Component's prop values before update.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
componentDidUpdate(prevProps) {
|
|
|
|
const { props } = this;
|
|
|
|
|
|
|
|
if (props._avatarURL !== prevProps._avatarURL) {
|
|
|
|
this._setAvatarURL(props._avatarURL);
|
|
|
|
}
|
|
|
|
if (props._email !== prevProps._email) {
|
|
|
|
this._setEmail(props._email);
|
|
|
|
}
|
|
|
|
if (props._name !== prevProps._name) {
|
|
|
|
this._setName(props._name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-26 23:51:12 +02:00
|
|
|
/**
|
|
|
|
* Remove conference on unmounting.
|
2018-06-07 09:47:42 +02:00
|
|
|
*
|
|
|
|
* @returns {void}
|
2018-05-26 23:51:12 +02:00
|
|
|
*/
|
|
|
|
componentWillUnmount() {
|
2018-09-23 10:49:17 +02:00
|
|
|
if (this._loadTimer) {
|
|
|
|
clearTimeout(this._loadTimer);
|
|
|
|
}
|
2018-05-26 23:51:12 +02:00
|
|
|
if (this._api) {
|
|
|
|
this._api.dispose();
|
|
|
|
}
|
|
|
|
}
|
2018-05-21 22:06:55 +02:00
|
|
|
|
|
|
|
/**
|
2018-05-26 23:51:12 +02:00
|
|
|
* Implements React's {@link Component#render()}.
|
2018-05-21 22:06:55 +02:00
|
|
|
*
|
2018-05-26 23:51:12 +02:00
|
|
|
* @returns {ReactElement}
|
2018-05-21 22:06:55 +02:00
|
|
|
*/
|
|
|
|
render() {
|
2018-06-27 22:50:42 +02:00
|
|
|
return (
|
|
|
|
<Wrapper innerRef = { this._ref }>
|
|
|
|
{ this._maybeRenderLoadingIndicator() }
|
|
|
|
</Wrapper>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* It renders a loading indicator, if appropriate.
|
|
|
|
*
|
|
|
|
* @returns {?ReactElement}
|
|
|
|
*/
|
|
|
|
_maybeRenderLoadingIndicator() {
|
|
|
|
if (this.state.isLoading) {
|
|
|
|
return (
|
|
|
|
<LoadingIndicator>
|
|
|
|
<Spinner size = 'large' />
|
|
|
|
</LoadingIndicator>
|
|
|
|
);
|
|
|
|
}
|
2018-05-21 22:06:55 +02:00
|
|
|
}
|
|
|
|
|
2018-05-21 21:17:24 +02:00
|
|
|
/**
|
2018-05-26 23:51:12 +02:00
|
|
|
* Navigates to home screen (Welcome).
|
2018-06-07 09:47:42 +02:00
|
|
|
*
|
2018-07-08 08:37:31 +02:00
|
|
|
* @param {Event} event - Event by which the function is called.
|
|
|
|
* @param {string} room - Room name.
|
|
|
|
* @param {string} serverURL - Server URL.
|
2018-06-07 09:47:42 +02:00
|
|
|
* @returns {void}
|
2018-05-21 21:17:24 +02:00
|
|
|
*/
|
2018-07-08 08:37:31 +02:00
|
|
|
_navigateToHome(event: Event, room: ?string, serverURL: ?string) {
|
|
|
|
this.props.dispatch(push('/', {
|
|
|
|
error: event.type === 'error',
|
|
|
|
room,
|
|
|
|
serverURL
|
|
|
|
}));
|
2018-05-26 23:51:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When the script is loaded create the iframe element in this component
|
|
|
|
* and attach utils from jitsi-meet-electron-utils.
|
2018-06-07 09:47:42 +02:00
|
|
|
*
|
|
|
|
* @param {Object} parentNode - Node to which iframe has to be attached.
|
|
|
|
* @returns {void}
|
2018-05-26 23:51:12 +02:00
|
|
|
*/
|
2018-07-25 13:35:50 +02:00
|
|
|
_onScriptLoad(parentNode: Object) {
|
2018-05-21 21:17:24 +02:00
|
|
|
const JitsiMeetExternalAPI = window.JitsiMeetExternalAPI;
|
|
|
|
|
2018-07-25 13:35:50 +02:00
|
|
|
const host = this._conference.serverURL.replace(/https?:\/\//, '');
|
2018-06-25 10:22:15 +02:00
|
|
|
|
2018-07-01 06:25:06 +02:00
|
|
|
const configOverwrite = {
|
|
|
|
startWithAudioMuted: this.props._startWithAudioMuted,
|
|
|
|
startWithVideoMuted: this.props._startWithVideoMuted
|
|
|
|
};
|
|
|
|
|
2018-06-25 10:22:15 +02:00
|
|
|
this._api = new JitsiMeetExternalAPI(host, {
|
2018-07-01 06:25:06 +02:00
|
|
|
configOverwrite,
|
2018-10-10 10:20:44 +02:00
|
|
|
onload: this._onIframeLoad,
|
2018-05-26 23:51:12 +02:00
|
|
|
parentNode,
|
2018-07-25 13:35:50 +02:00
|
|
|
roomName: this._conference.room
|
2018-05-26 23:51:12 +02:00
|
|
|
});
|
2018-06-13 00:39:56 +02:00
|
|
|
initPopupsConfigurationRender(this._api);
|
|
|
|
|
2018-05-26 23:51:12 +02:00
|
|
|
const iframe = this._api.getIFrame();
|
2018-05-21 21:17:24 +02:00
|
|
|
|
|
|
|
setupScreenSharingForWindow(iframe);
|
2018-05-24 17:12:27 +02:00
|
|
|
new RemoteControl(iframe); // eslint-disable-line no-new
|
2018-05-26 23:51:12 +02:00
|
|
|
setupAlwaysOnTopRender(this._api);
|
2018-05-21 21:17:24 +02:00
|
|
|
setupWiFiStats(iframe);
|
2018-05-26 23:51:12 +02:00
|
|
|
|
2018-07-25 13:35:50 +02:00
|
|
|
this._api.on('readyToClose', (event: Event) => {
|
|
|
|
this.props.dispatch(conferenceEnded(this._conference));
|
|
|
|
this._navigateToHome(event);
|
|
|
|
});
|
2018-06-09 18:10:00 +02:00
|
|
|
this._api.on('videoConferenceJoined',
|
2018-07-25 13:35:50 +02:00
|
|
|
(conferenceInfo: Object) => {
|
|
|
|
this.props.dispatch(conferenceJoined(this._conference));
|
|
|
|
this._onVideoConferenceJoined(conferenceInfo);
|
|
|
|
}
|
|
|
|
);
|
2018-06-09 18:10:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates redux state's user name from conference.
|
|
|
|
*
|
|
|
|
* @param {Object} params - Returned object from event.
|
|
|
|
* @param {string} id - Local Participant ID.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_onDisplayNameChange(params: Object, id: string) {
|
|
|
|
if (params.id === id) {
|
|
|
|
this.props.dispatch(setName(params.displayname));
|
|
|
|
}
|
2018-05-21 21:17:24 +02:00
|
|
|
}
|
2018-06-09 18:10:00 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates redux state's email from conference.
|
|
|
|
*
|
|
|
|
* @param {Object} params - Returned object from event.
|
|
|
|
* @param {string} id - Local Participant ID.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_onEmailChange(params: Object, id: string) {
|
|
|
|
if (params.id === id) {
|
|
|
|
this.props.dispatch(setEmail(params.email));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-10 10:20:44 +02:00
|
|
|
_onIframeLoad: (*) => void;
|
|
|
|
|
2018-06-09 18:10:00 +02:00
|
|
|
/**
|
2018-10-10 10:20:44 +02:00
|
|
|
* Sets state of loading to false when iframe has completely loaded.
|
2018-06-09 18:10:00 +02:00
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2018-10-10 10:20:44 +02:00
|
|
|
_onIframeLoad() {
|
2018-09-23 10:49:17 +02:00
|
|
|
if (this._loadTimer) {
|
|
|
|
clearTimeout(this._loadTimer);
|
|
|
|
this._loadTimer = null;
|
|
|
|
}
|
|
|
|
|
2018-09-23 10:29:25 +02:00
|
|
|
this.setState({
|
|
|
|
isLoading: false
|
|
|
|
});
|
2018-10-10 10:20:44 +02:00
|
|
|
}
|
2018-09-23 10:29:25 +02:00
|
|
|
|
2018-10-10 10:20:44 +02:00
|
|
|
/**
|
|
|
|
* Saves conference info on joining it.
|
|
|
|
*
|
|
|
|
* @param {Object} conferenceInfo - Contains information about the current
|
|
|
|
* conference.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_onVideoConferenceJoined(conferenceInfo: Object) {
|
2018-06-09 18:10:00 +02:00
|
|
|
this._setAvatarURL(this.props._avatarURL);
|
|
|
|
this._setEmail(this.props._email);
|
|
|
|
this._setName(this.props._name);
|
|
|
|
|
|
|
|
const { id } = conferenceInfo;
|
|
|
|
|
|
|
|
this._api.on('displayNameChange',
|
|
|
|
(params: Object) => this._onDisplayNameChange(params, id));
|
|
|
|
this._api.on('emailChange',
|
|
|
|
(params: Object) => this._onEmailChange(params, id));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set Avatar URL from settings to conference.
|
|
|
|
*
|
|
|
|
* @param {string} avatarURL - Avatar URL.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_setAvatarURL(avatarURL: string) {
|
|
|
|
this._api.executeCommand('avatarUrl', avatarURL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set email from settings to conference.
|
|
|
|
*
|
|
|
|
* @param {string} email - Email of user.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_setEmail(email: string) {
|
|
|
|
this._api.executeCommand('email', email);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set name from settings to conference.
|
|
|
|
*
|
|
|
|
* @param {string} name - Name of user.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_setName(name: string) {
|
|
|
|
this._api.executeCommand('displayName', name);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maps (parts of) the redux state to the React props.
|
|
|
|
*
|
|
|
|
* @param {Object} state - The redux state.
|
|
|
|
* @returns {{
|
|
|
|
* _avatarURL: string,
|
|
|
|
* _email: string,
|
2018-06-23 02:51:50 +02:00
|
|
|
* _name: string,
|
2018-07-01 06:25:06 +02:00
|
|
|
* _serverURL: string,
|
|
|
|
* _startWithAudioMuted: boolean,
|
|
|
|
* _startWithVideoMuted: boolean
|
2018-06-09 18:10:00 +02:00
|
|
|
* }}
|
|
|
|
*/
|
|
|
|
function _mapStateToProps(state: Object) {
|
|
|
|
return {
|
|
|
|
_avatarURL: state.settings.avatarURL,
|
|
|
|
_email: state.settings.email,
|
2018-06-23 02:51:50 +02:00
|
|
|
_name: state.settings.name,
|
2018-07-01 06:25:06 +02:00
|
|
|
_serverURL: state.settings.serverURL,
|
|
|
|
_startWithAudioMuted: state.settings.startWithAudioMuted,
|
|
|
|
_startWithVideoMuted: state.settings.startWithVideoMuted
|
2018-06-09 18:10:00 +02:00
|
|
|
};
|
2018-05-21 21:17:24 +02:00
|
|
|
}
|
2018-06-03 04:36:16 +02:00
|
|
|
|
2018-06-09 18:10:00 +02:00
|
|
|
export default connect(_mapStateToProps)(Conference);
|