diff --git a/react/features/base/conference/actions.js b/react/features/base/conference/actions.js index 216eb964a..71619acd7 100644 --- a/react/features/base/conference/actions.js +++ b/react/features/base/conference/actions.js @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { JitsiConferenceEvents } from '../lib-jitsi-meet'; import { setVideoMuted } from '../media'; import { @@ -255,21 +254,12 @@ export function createConference() { dispatch(_conferenceWillJoin(room)); - const config = state['features/base/config']; - const configOverride = { - p2p: { - preferH264: true - } - }; const conference = connection.initJitsiConference( // XXX Lib-jitsi-meet does not accept uppercase letters. room.toLowerCase(), - - // We use lodash's merge here because it will recursively merge - // objects allowing partial overrides. - _.merge({}, config, configOverride)); + state['features/base/config']); _addConferenceListeners(conference, dispatch); diff --git a/react/features/base/config/reducer.js b/react/features/base/config/reducer.js index 5c1ad5b63..41cc9ddbc 100644 --- a/react/features/base/config/reducer.js +++ b/react/features/base/config/reducer.js @@ -1,17 +1,34 @@ -import { ReducerRegistry } from '../redux'; +/* @flow */ + +import _ from 'lodash'; + +import { equals, ReducerRegistry, set } from '../redux'; import { SET_CONFIG } from './actionTypes'; /** - * The initial state of the feature base/config. The mandatory configuration to - * be passed to JitsiMeetJS#init(). The app will download config.js from the - * Jitsi Meet deployment and take its values into account but the values bellow - * will be enforced (because they are essential to the correct execution of the + * The initial state of the feature base/config when executing in a + * non-React Native environment. The mandatory configuration to be passed to + * JitsiMeetJS#init(). The app will download config.js from the Jitsi Meet + * deployment and take its values into account but the values bellow will be + * enforced (because they are essential to the correct execution of the * application). * * @type {Object} */ -const INITIAL_STATE = { +const INITIAL_NON_RN_STATE = { +}; + +/** + * The initial state of the feature base/config when executing in a React Native + * environment. The mandatory configuration to be passed to JitsiMeetJS#init(). + * The app will download config.js from the Jitsi Meet deployment and take its + * values into account but the values bellow will be enforced (because they are + * essential to the correct execution of the application). + * + * @type {Object} + */ +const INITIAL_RN_STATE = { // FIXME The support for audio levels in lib-jitsi-meet polls the statistics // of WebRTC at a short interval multiple times a second. Unfortunately, // React Native is slow to fetch these statistics from the native WebRTC @@ -26,12 +43,16 @@ const INITIAL_STATE = { // Fortunately, these pieces of JavaScript currently involve third parties // and we can temporarily disable them (until we implement an alternative to // async script elements on React Native). - disableThirdPartyRequests: true + disableThirdPartyRequests: true, + + p2p: { + preferH264: true + } }; ReducerRegistry.register( 'features/base/config', - (state = INITIAL_STATE, action) => { + (state = _getInitialState(), action) => { switch (action.type) { case SET_CONFIG: return _setConfig(state, action); @@ -41,6 +62,22 @@ ReducerRegistry.register( } }); +/** + * Gets the initial state of the feature base/config. The mandatory + * configuration to be passed to JitsiMeetJS#init(). The app will download + * config.js from the Jitsi Meet deployment and take its values into account but + * the values bellow will be enforced (because they are essential to the correct + * execution of the application). + * + * @returns {Object} + */ +function _getInitialState() { + return ( + navigator.userAgent.match(/react[ \s-]*native/i) + ? INITIAL_RN_STATE + : INITIAL_NON_RN_STATE); +} + /** * Reduces a specific Redux action SET_CONFIG of the feature * base/lib-jitsi-meet. @@ -52,19 +89,80 @@ ReducerRegistry.register( * reduction of the specified action. */ function _setConfig(state, action) { - return { - ...action.config, + let { config } = action; - // The config of INITIAL_STATE is meant to override the config + // The mobile app bundles jitsi-meet and lib-jitsi-meet at build time and + // does not download them at runtime from the deployment on which it will + // join a conference. The downloading is planned for implementation in the + // future (later rather than sooner) but is not implemented yet at the time + // of this writing and, consequently, we must provide legacy support in the + // meantime. + config = _translateLegacyConfig(config); + + const newState = _.merge( + {}, + config, + + // The config of _getInitialState() is meant to override the config // downloaded from the Jitsi Meet deployment because the former contains // values that are mandatory. - // - // FIXME At the time of this writing the hard-coded overriding values - // are specific to mobile/React Native but the source code here is - // executed on Web/React as well. The latter is not a practical problem - // right now because the rest of the Web/React source code does not read - // the overridden properties/values, it still relies on the global - // variable config. - ...INITIAL_STATE - }; + _getInitialState() + ); + + return equals(state, newState) ? state : newState; +} + +/** + * Constructs a new config {@code Object}, if necessary, out of a specific + * config {@code Object} which is in the latest format supported by jitsi-meet. + * Such a translation from an old config format to a new/the latest config + * format is necessary because the mobile app bundles jitsi-meet and + * lib-jitsi-meet at build time and does not download them at runtime from the + * deployment on which it will join a conference. + * + * @param {Object} oldValue - The config {@code Object} which may or may not be + * in the latest form supported by jitsi-meet and from which a new config + * {@code Object} is to be constructed if necessary. + * @returns {Object} A config {@code Object} which is in the latest format + * supported by jitsi-meet. + */ +function _translateLegacyConfig(oldValue: Object) { + // jitsi/jitsi-meet#3ea2f005787c9f49c48febaeed9dc0340fe0a01b + + let newValue = oldValue; + + // At the time of this writing lib-jitsi-meet will rely on config having a + // property with the name p2p and with a value of type Object. + if (typeof oldValue.p2p !== 'object') { + newValue = set(newValue, 'p2p', {}); + } + + // Translate the old config properties into the new config.p2p properties. + for (const [ oldKey, newKey ] + of [ + [ 'backToP2PDelay', 'backToP2PDelay' ], + [ 'enableP2P', 'enabled' ], + [ 'p2pStunServers', 'stunServers' ] + ]) { + if (oldKey in newValue) { + const v = newValue[oldKey]; + + // Do not modify oldValue. + if (newValue === oldValue) { + newValue = { + ...newValue + }; + } + delete newValue[oldKey]; + + // Do not modify p2p because it may be from oldValue i.e. do not + // modify oldValue. + newValue.p2p = { + ...newValue.p2p, + [newKey]: v + }; + } + } + + return newValue; } diff --git a/react/features/base/connection/actions.native.js b/react/features/base/connection/actions.native.js index efa36dbcd..cd11084bd 100644 --- a/react/features/base/connection/actions.native.js +++ b/react/features/base/connection/actions.native.js @@ -20,24 +20,34 @@ import { export function connect() { return (dispatch: Dispatch<*>, getState: Function) => { const state = getState(); - const { options } = state['features/base/connection']; + let { options } = state['features/base/connection']; + + options = { + // Lib-jitsi-meet wants the config passed in multiple places and + // here is the latest one I have discovered. + ...state['features/base/config'], + + // TODO It is probable that config should override the options that + // have been automatically constructed by the app. Unfortunately, + // config may specify URLs such as bosh at the time of this writing + // which react-native cannot parse (because they do not have a + // protocol/scheme). + ...options + }; + const { issuer, jwt } = state['features/jwt']; const { room } = state['features/base/conference']; + + // XXX The Jitsi Meet deployments require the room argument to be in + // lower case at the time of this writing but, unfortunately, they do + // not ignore case themselves. + options.bosh += room ? `?room=${room.toLowerCase()}` : ''; + const connection = new JitsiMeetJS.JitsiConnection( options.appId, jwt && issuer && issuer !== 'anonymous' ? jwt : undefined, - { - ...options, - bosh: - options.bosh - - // XXX The Jitsi Meet deployments require the room - // argument to be in lower case at the time of this - // writing but, unfortunately, they do not ignore - // case themselves. - + (room ? `?room=${room.toLowerCase()}` : '') - }); + options); connection.addEventListener( JitsiConnectionEvents.CONNECTION_DISCONNECTED, diff --git a/react/features/base/lib-jitsi-meet/native/polyfills-browser.js b/react/features/base/lib-jitsi-meet/native/polyfills-browser.js index 3107e576d..c7c35cdb0 100644 --- a/react/features/base/lib-jitsi-meet/native/polyfills-browser.js +++ b/react/features/base/lib-jitsi-meet/native/polyfills-browser.js @@ -263,7 +263,7 @@ function _visitNode(node, callback) { }; } - const navigator = global.navigator; + const { navigator } = global; if (navigator) { // platform @@ -372,13 +372,17 @@ function _visitNode(node, callback) { // Required by: // - lib-jitsi-meet // - Strophe - global.clearTimeout = window.clearTimeout + global.clearTimeout + = window.clearTimeout = BackgroundTimer.clearTimeout.bind(BackgroundTimer); - global.clearInterval = window.clearInterval + global.clearInterval + = window.clearInterval = BackgroundTimer.clearInterval.bind(BackgroundTimer); - global.setInterval = window.setInterval + global.setInterval + = window.setInterval = BackgroundTimer.setInterval.bind(BackgroundTimer); - global.setTimeout = window.setTimeout + global.setTimeout + = window.setTimeout = BackgroundTimer.setTimeout.bind(BackgroundTimer); })(global || window || this); // eslint-disable-line no-invalid-this