feat(reload): Preserve local track mute state.

This commit is contained in:
Hristo Terezov 2020-05-07 17:26:37 -05:00
parent a48aa2b999
commit d388a7bd3c
15 changed files with 81 additions and 29 deletions

View File

@ -1,7 +1,7 @@
/* global config, createConnectionExternally */ /* global config, createConnectionExternally */
import getRoomName from '../react/features/base/config/getRoomName'; import getRoomName from '../react/features/base/config/getRoomName';
import parseURLParams from '../react/features/base/config/parseURLParams'; import { parseURLParams } from '../react/features/base/util/parseURLParams';
/** /**
* Implements external connect using createConnectionExternally function defined * Implements external connect using createConnectionExternally function defined

View File

@ -1,10 +1,10 @@
// XXX The function parseURLParams is exported by the feature base/config (as // XXX The function parseURLParams is exported by the feature base/util (as
// defined in the terminology of react/). However, this file is (very likely) // defined in the terminology of react/). However, this file is (very likely)
// bundled in external_api in addition to app.bundle and, consequently, it is // bundled in external_api in addition to app.bundle and, consequently, it is
// best to import as little as possible here (rather than the whole feature // best to import as little as possible here (rather than the whole feature
// base/config) in order to minimize the amount of source code bundled into // base/util) in order to minimize the amount of source code bundled into
// multiple bundles. // multiple bundles.
import parseURLParams from '../../react/features/base/config/parseURLParams'; import { parseURLParams } from '../../react/features/base/util/parseURLParams';
/** /**
* JitsiMeetExternalAPI id - unique for a webpage. * JitsiMeetExternalAPI id - unique for a webpage.

View File

@ -13,8 +13,11 @@ import {
} from '../base/config'; } from '../base/config';
import { connect, disconnect, setLocationURL } from '../base/connection'; import { connect, disconnect, setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet'; import { loadConfig } from '../base/lib-jitsi-meet';
import { createDesiredLocalTracks } from '../base/tracks'; import { MEDIA_TYPE } from '../base/media';
import { toState } from '../base/redux';
import { createDesiredLocalTracks, isLocalVideoTrackMuted, isLocalTrackMuted } from '../base/tracks';
import { import {
addHashParamsToURL,
getBackendSafeRoomName, getBackendSafeRoomName,
getLocationContextRoot, getLocationContextRoot,
parseURIString, parseURIString,
@ -191,18 +194,42 @@ export function reloadNow() {
return (dispatch: Dispatch<Function>, getState: Function) => { return (dispatch: Dispatch<Function>, getState: Function) => {
dispatch(setFatalError(undefined)); dispatch(setFatalError(undefined));
const { locationURL } = getState()['features/base/connection']; const state = getState();
const { locationURL } = state['features/base/connection'];
// Preserve the local tracks muted state after the reload.
const newURL = addTrackStateToURL(locationURL, state);
logger.info(`Reloading the conference using URL: ${locationURL}`); logger.info(`Reloading the conference using URL: ${locationURL}`);
if (navigator.product === 'ReactNative') { if (navigator.product === 'ReactNative') {
dispatch(appNavigate(toURLString(locationURL))); dispatch(appNavigate(toURLString(newURL)));
} else { } else {
dispatch(reloadWithStoredParams()); dispatch(reloadWithStoredParams());
} }
}; };
} }
/**
* Adds the current track state to the passed URL.
*
* @param {URL} url - The URL that will be modified.
* @param {Function|Object} stateful - The redux store or {@code getState} function.
* @returns {URL} - Returns the modified URL.
*/
function addTrackStateToURL(url, stateful) {
const state = toState(stateful);
const tracks = state['features/base/tracks'];
const isVideoMuted = isLocalVideoTrackMuted(tracks);
const isAudioMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
return addHashParamsToURL(new URL(url), { // use new URL object in order to not pollute the passed parameter.
'config.startWithAudioMuted': isAudioMuted,
'config.startWithVideoMuted': isVideoMuted
});
}
/** /**
* Reloads the page by restoring the original URL. * Reloads the page by restoring the original URL.
* *
@ -210,17 +237,20 @@ export function reloadNow() {
*/ */
export function reloadWithStoredParams() { export function reloadWithStoredParams() {
return (dispatch: Dispatch<any>, getState: Function) => { return (dispatch: Dispatch<any>, getState: Function) => {
const { locationURL } = getState()['features/base/connection']; const state = getState();
const { locationURL } = state['features/base/connection'];
// Preserve the local tracks muted states.
const newURL = addTrackStateToURL(locationURL, state);
const windowLocation = window.location; const windowLocation = window.location;
const oldSearchString = windowLocation.search; const oldSearchString = windowLocation.search;
windowLocation.replace(locationURL.toString()); windowLocation.replace(newURL.toString());
if (window.self !== window.top if (newURL.search === oldSearchString) {
&& locationURL.search === oldSearchString) {
// NOTE: Assuming that only the hash or search part of the URL will // NOTE: Assuming that only the hash or search part of the URL will
// be changed! // be changed!
// location.reload will not trigger redirect/reload for iframe when // location.replace will not trigger redirect/reload when
// only the hash params are changed. That's why we need to call // only the hash params are changed. That's why we need to call
// reload in addition to replace. // reload in addition to replace.
windowLocation.reload(); windowLocation.reload();

View File

@ -6,15 +6,14 @@ import _ from 'lodash';
import CONFIG_WHITELIST from './configWhitelist'; import CONFIG_WHITELIST from './configWhitelist';
import { _CONFIG_STORE_PREFIX } from './constants'; import { _CONFIG_STORE_PREFIX } from './constants';
import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist'; import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
import parseURLParams from './parseURLParams'; import { parseURLParams } from '../util';
import logger from './logger'; import logger from './logger';
// XXX The functions getRoomName and parseURLParams are split out of // XXX The function getRoomName is split out of
// functions.js because they are bundled in both app.bundle and // functions.js because it is bundled in both app.bundle and
// do_external_connect, webpack 1 does not support tree shaking, and we don't // do_external_connect, webpack 1 does not support tree shaking, and we don't
// want all functions to be bundled in do_external_connect. // want all functions to be bundled in do_external_connect.
export { default as getRoomName } from './getRoomName'; export { default as getRoomName } from './getRoomName';
export { parseURLParams };
/** /**
* Create a "fake" configuration object for the given base URL. This is used in case the config * Create a "fake" configuration object for the given base URL. This is used in case the config

View File

@ -1,6 +1,6 @@
// @flow // @flow
import { parseURLParams } from '../config'; import { parseURLParams } from '../util';
import JitsiMeetJS from '../lib-jitsi-meet'; import JitsiMeetJS from '../lib-jitsi-meet';
import { updateSettings } from '../settings'; import { updateSettings } from '../settings';

View File

@ -1,6 +1,6 @@
/* @flow */ /* @flow */
import { parseURLParams } from '../config'; import { parseURLParams } from '../util';
/** /**
* Retrieves the JSON Web Token (JWT), if any, defined by a specific * Retrieves the JSON Web Token (JWT), if any, defined by a specific

View File

@ -1,6 +1,7 @@
// @flow // @flow
import { CONFIG_WHITELIST, parseURLParams } from '../config'; import { CONFIG_WHITELIST } from '../config';
import { toState } from '../redux'; import { toState } from '../redux';
import { parseURLParams } from '../util';
import { DEFAULT_SERVER_URL } from './constants'; import { DEFAULT_SERVER_URL } from './constants';

View File

@ -3,7 +3,7 @@ import _ from 'lodash';
import { APP_WILL_MOUNT } from '../app'; import { APP_WILL_MOUNT } from '../app';
import { setAudioOnly } from '../audio-only'; import { setAudioOnly } from '../audio-only';
import parseURLParams from '../config/parseURLParams'; // minimize imports to avoid circular imports import { parseURLParams } from '../util';
import { SET_LOCATION_URL } from '../connection/actionTypes'; // minimize imports to avoid circular imports import { SET_LOCATION_URL } from '../connection/actionTypes'; // minimize imports to avoid circular imports
import { getLocalParticipant, participantUpdated } from '../participants'; import { getLocalParticipant, participantUpdated } from '../participants';
import { MiddlewareRegistry } from '../redux'; import { MiddlewareRegistry } from '../redux';

View File

@ -3,3 +3,4 @@ export * from './httpUtils';
export * from './loadScript'; export * from './loadScript';
export * from './openURLInBrowser'; export * from './openURLInBrowser';
export * from './uri'; export * from './uri';
export * from './parseURLParams';

View File

@ -1,19 +1,19 @@
/* @flow */ /* @flow */
import { reportError } from '../util'; import { reportError } from './helpers';
/** /**
* Parses the query/search or fragment/hash parameters out of a specific URL and * Parses the query/search or fragment/hash parameters out of a specific URL and
* returns them as a JS object. * returns them as a JS object.
* *
* @param {string} url - The URL to parse. * @param {URL} url - The URL to parse.
* @param {boolean} dontParse - If falsy, some transformations (for parsing the * @param {boolean} dontParse - If falsy, some transformations (for parsing the
* value as JSON) will be executed. * value as JSON) will be executed.
* @param {string} source - If {@code 'search'}, the parameters will parsed out * @param {string} source - If {@code 'search'}, the parameters will parsed out
* of {@code url.search}; otherwise, out of {@code url.hash}. * of {@code url.search}; otherwise, out of {@code url.hash}.
* @returns {Object} * @returns {Object}
*/ */
export default function parseURLParams( export function parseURLParams(
url: URL, url: URL,
dontParse: boolean = false, dontParse: boolean = false,
source: string = 'hash'): Object { source: string = 'hash'): Object {

View File

@ -1,5 +1,6 @@
// @flow // @flow
import { parseURLParams } from './parseURLParams';
import { normalizeNFKC } from './strings'; import { normalizeNFKC } from './strings';
/** /**
@ -551,3 +552,24 @@ export function urlObjectToString(o: Object): ?string {
return url.toString() || undefined; return url.toString() || undefined;
} }
/**
* Adds hash params to URL.
*
* @param {URL} url - The URL.
* @param {Object} hashParamsToAdd - A map with the parameters to be set.
* @returns {URL} - The new URL.
*/
export function addHashParamsToURL(url: URL, hashParamsToAdd: Object = {}) {
const params = parseURLParams(url);
const urlParamsArray = _objectToURLParamsArray({
...params,
...hashParamsToAdd
});
if (urlParamsArray.length) {
url.hash = `#${urlParamsArray.join('&')}`;
}
return url;
}

View File

@ -6,8 +6,7 @@ import type { Dispatch } from 'redux';
import { createDeferred } from '../../../../modules/util/helpers'; import { createDeferred } from '../../../../modules/util/helpers';
import parseURLParams from '../../base/config/parseURLParams'; import { parseStandardURIString, parseURLParams } from '../../base/util';
import { parseStandardURIString } from '../../base/util';
import { getShareInfoText } from '../../invite'; import { getShareInfoText } from '../../invite';
import { setCalendarAPIAuthState } from '../actions'; import { setCalendarAPIAuthState } from '../actions';

View File

@ -2,10 +2,10 @@
import { Dropbox } from 'dropbox'; import { Dropbox } from 'dropbox';
import { parseURLParams } from '../base/config';
import { import {
getJitsiMeetGlobalNS, getJitsiMeetGlobalNS,
parseStandardURIString parseStandardURIString,
parseURLParams
} from '../base/util'; } from '../base/util';
/** /**

View File

@ -2,9 +2,9 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next'; import { I18nextProvider } from 'react-i18next';
import parseURLParams from '../../../base/config/parseURLParams';
import { i18next } from '../../../base/i18n'; import { i18next } from '../../../base/i18n';
import { isMobileBrowser } from '../../../base/environment/utils'; import { isMobileBrowser } from '../../../base/environment/utils';
import { parseURLParams } from '../../../base/util/parseURLParams';
import { DialInSummary } from '../dial-in-summary'; import { DialInSummary } from '../dial-in-summary';
import NoRoomError from './NoRoomError'; import NoRoomError from './NoRoomError';

View File

@ -20,8 +20,8 @@ import {
setVideoInputDevice setVideoInputDevice
} from '../../../../../modules/API/external/functions'; } from '../../../../../modules/API/external/functions';
import parseURLParams from '../../../base/config/parseURLParams';
import DialogWithTabs from '../../../base/dialog/components/web/DialogWithTabs'; import DialogWithTabs from '../../../base/dialog/components/web/DialogWithTabs';
import { parseURLParams } from '../../../base/util/parseURLParams';
import DeviceSelection from '../../../device-selection/components/DeviceSelection'; import DeviceSelection from '../../../device-selection/components/DeviceSelection';
/** /**