jitsi-meet-electron/app/features/conference/components/Conference.js
csett86 22b3406613
feat(agc): Add setting to disable automatic gain control (#582)
In some OS/Chromium combinations the automatic gain control goes slightly
crazy, but normally its fine. Thus keep the default as is, but add an option
for the users to disable it if required.

Closes: #564
2021-05-11 17:08:47 +02:00

423 lines
10 KiB
JavaScript

// @flow
import Spinner from '@atlaskit/spinner';
import React, { Component } from 'react';
import type { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import i18n from '../../../i18n';
import config from '../../config';
import { getSetting, setEmail, setName } from '../../settings';
import { conferenceEnded, conferenceJoined } from '../actions';
import JitsiMeetExternalAPI from '../external_api';
import { LoadingIndicator, Wrapper } from '../styled';
const ENABLE_REMOTE_CONTROL = false;
type Props = {
/**
* Redux dispatch.
*/
dispatch: Dispatch<*>;
/**
* React Router location object.
*/
location: Object;
/**
* AlwaysOnTop Window Enabled.
*/
_alwaysOnTopWindowEnabled: boolean;
/**
* Disable automatic gain control.
*/
_disableAGC: boolean;
/**
* Email of user.
*/
_email: string;
/**
* Name of user.
*/
_name: string;
/**
* Default Jitsi Server URL.
*/
_serverURL: string;
/**
* Default Jitsi Server Timeout.
*/
_serverTimeout: number;
/**
* Start with Audio Muted.
*/
_startWithAudioMuted: boolean;
/**
* Start with Video Muted.
*/
_startWithVideoMuted: boolean;
};
type State = {
/**
* If the conference is loading or not.
*/
isLoading: boolean;
};
/**
* Conference component.
*/
class Conference extends Component<Props, State> {
/**
* External API object.
*/
_api: Object;
/**
* Conference Object.
*/
_conference: Object;
/**
* Timer to cancel the joining if it takes too long.
*/
_loadTimer: ?TimeoutID;
/**
* Reference to the element of this component.
*/
_ref: Object;
/**
* Initializes a new {@code Conference} instance.
*
* @inheritdoc
*/
constructor() {
super();
this.state = {
isLoading: true
};
this._ref = React.createRef();
this._onIframeLoad = this._onIframeLoad.bind(this);
this._onVideoConferenceEnded = this._onVideoConferenceEnded.bind(this);
}
/**
* Attach the script to this component.
*
* @returns {void}
*/
componentDidMount() {
const room = this.props.location.state.room;
const serverTimeout = this.props._serverTimeout || config.defaultServerTimeout;
const serverURL = this.props.location.state.serverURL
|| this.props._serverURL
|| config.defaultServerURL;
this._conference = {
room,
serverURL
};
this._loadConference();
// Set a timer for a timeout duration, if we haven't loaded the iframe by then,
// give up.
this._loadTimer = setTimeout(() => {
this._navigateToHome(
// $FlowFixMe
{
error: 'Loading error',
type: 'error'
},
room,
serverURL);
}, serverTimeout * 1000);
}
/**
* 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._email !== prevProps._email) {
this._setEmail(props._email);
}
if (props._name !== prevProps._name) {
this._setName(props._name);
}
}
/**
* Remove conference on unmounting.
*
* @returns {void}
*/
componentWillUnmount() {
if (this._loadTimer) {
clearTimeout(this._loadTimer);
}
if (this._api) {
this._api.dispose();
}
}
/**
* Implements React's {@link Component#render()}.
*
* @returns {ReactElement}
*/
render() {
return (
<Wrapper innerRef = { this._ref }>
{ this._maybeRenderLoadingIndicator() }
</Wrapper>
);
}
/**
* Load the conference by creating the iframe element in this component
* and attaching utils from jitsi-meet-electron-utils.
*
* @returns {void}
*/
_loadConference() {
const url = new URL(this._conference.room, this._conference.serverURL);
const roomName = url.pathname.split('/').pop();
const host = this._conference.serverURL.replace(/https?:\/\//, '');
const searchParameters = Object.fromEntries(url.searchParams);
const locale = { lng: i18n.language };
const urlParameters = {
...searchParameters,
...locale
};
const configOverwrite = {
disableAGC: this.props._disableAGC,
startWithAudioMuted: this.props._startWithAudioMuted,
startWithVideoMuted: this.props._startWithVideoMuted
};
const options = {
configOverwrite,
onload: this._onIframeLoad,
parentNode: this._ref.current,
roomName
};
this._api = new JitsiMeetExternalAPI(host, {
...options,
...urlParameters
});
this._api.on('suspendDetected', this._onVideoConferenceEnded);
this._api.on('readyToClose', this._onVideoConferenceEnded);
this._api.on('videoConferenceJoined',
(conferenceInfo: Object) => {
this.props.dispatch(conferenceJoined(this._conference));
this._onVideoConferenceJoined(conferenceInfo);
}
);
const { RemoteControl,
setupScreenSharingRender,
setupAlwaysOnTopRender,
initPopupsConfigurationRender,
setupWiFiStats,
setupPowerMonitorRender
} = window.jitsiNodeAPI.jitsiMeetElectronUtils;
initPopupsConfigurationRender(this._api);
const iframe = this._api.getIFrame();
setupScreenSharingRender(this._api);
if (ENABLE_REMOTE_CONTROL) {
new RemoteControl(iframe); // eslint-disable-line no-new
}
// Allow window to be on top if enabled in settings
if (this.props._alwaysOnTopWindowEnabled) {
setupAlwaysOnTopRender(this._api);
}
setupWiFiStats(iframe);
setupPowerMonitorRender(this._api);
}
/**
* It renders a loading indicator, if appropriate.
*
* @returns {?ReactElement}
*/
_maybeRenderLoadingIndicator() {
if (this.state.isLoading) {
return (
<LoadingIndicator>
<Spinner size = 'large' />
</LoadingIndicator>
);
}
}
/**
* Navigates to home screen (Welcome).
*
* @param {Event} event - Event by which the function is called.
* @param {string} room - Room name.
* @param {string} serverURL - Server URL.
* @returns {void}
*/
_navigateToHome(event: Event, room: ?string, serverURL: ?string) {
this.props.dispatch(push('/', {
error: event.type === 'error',
room,
serverURL
}));
}
_onVideoConferenceEnded: (*) => void;
/**
* Dispatches conference ended and navigates to home screen.
*
* @param {Event} event - Event by which the function is called.
* @returns {void}
* @private
*/
_onVideoConferenceEnded(event: Event) {
this.props.dispatch(conferenceEnded(this._conference));
this._navigateToHome(event);
}
/**
* 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));
}
}
/**
* 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));
}
}
_onIframeLoad: (*) => void;
/**
* Sets state of loading to false when iframe has completely loaded.
*
* @returns {void}
*/
_onIframeLoad() {
if (this._loadTimer) {
clearTimeout(this._loadTimer);
this._loadTimer = null;
}
this.setState({
isLoading: false
});
}
/**
* Saves conference info on joining it.
*
* @param {Object} conferenceInfo - Contains information about the current
* conference.
* @returns {void}
*/
_onVideoConferenceJoined(conferenceInfo: Object) {
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 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 {Props}
*/
function _mapStateToProps(state: Object) {
return {
_alwaysOnTopWindowEnabled: getSetting(state, 'alwaysOnTopWindowEnabled', true),
_disableAGC: state.settings.disableAGC,
_email: state.settings.email,
_name: state.settings.name,
_serverURL: state.settings.serverURL,
_serverTimeout: state.settings.serverTimeout,
_startWithAudioMuted: state.settings.startWithAudioMuted,
_startWithVideoMuted: state.settings.startWithVideoMuted
};
}
export default connect(_mapStateToProps)(Conference);