You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

reducer.js 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. // @flow
  2. import _ from 'lodash';
  3. import { equals, ReducerRegistry } from '../redux';
  4. import {
  5. UPDATE_CONFIG,
  6. CONFIG_WILL_LOAD,
  7. LOAD_CONFIG_ERROR,
  8. SET_CONFIG,
  9. OVERWRITE_CONFIG
  10. } from './actionTypes';
  11. import { _cleanupConfig } from './functions';
  12. declare var interfaceConfig: Object;
  13. /**
  14. * The initial state of the feature base/config when executing in a
  15. * non-React Native environment. The mandatory configuration to be passed to
  16. * JitsiMeetJS#init(). The app will download config.js from the Jitsi Meet
  17. * deployment and take its values into account but the values below will be
  18. * enforced (because they are essential to the correct execution of the
  19. * application).
  20. *
  21. * @type {Object}
  22. */
  23. const INITIAL_NON_RN_STATE = {
  24. };
  25. /**
  26. * The initial state of the feature base/config when executing in a React Native
  27. * environment. The mandatory configuration to be passed to JitsiMeetJS#init().
  28. * The app will download config.js from the Jitsi Meet deployment and take its
  29. * values into account but the values below will be enforced (because they are
  30. * essential to the correct execution of the application).
  31. *
  32. * @type {Object}
  33. */
  34. const INITIAL_RN_STATE = {
  35. analytics: {},
  36. // FIXME The support for audio levels in lib-jitsi-meet polls the statistics
  37. // of WebRTC at a short interval multiple times a second. Unfortunately,
  38. // React Native is slow to fetch these statistics from the native WebRTC
  39. // API, through the React Native bridge and eventually to JavaScript.
  40. // Because the audio levels are of no interest to the mobile app, it is
  41. // fastest to merely disable them.
  42. disableAudioLevels: true,
  43. p2p: {
  44. disabledCodec: '',
  45. disableH264: false, // deprecated
  46. preferredCodec: 'H264',
  47. preferH264: true // deprecated
  48. }
  49. };
  50. ReducerRegistry.register('features/base/config', (state = _getInitialState(), action) => {
  51. switch (action.type) {
  52. case UPDATE_CONFIG:
  53. return _updateConfig(state, action);
  54. case CONFIG_WILL_LOAD:
  55. return {
  56. error: undefined,
  57. /**
  58. * The URL of the location associated with/configured by this
  59. * configuration.
  60. *
  61. * @type URL
  62. */
  63. locationURL: action.locationURL
  64. };
  65. case LOAD_CONFIG_ERROR:
  66. // XXX LOAD_CONFIG_ERROR is one of the settlement execution paths of
  67. // the asynchronous "loadConfig procedure/process" started with
  68. // CONFIG_WILL_LOAD. Due to the asynchronous nature of it, whoever
  69. // is settling the process needs to provide proof that they have
  70. // started it and that the iteration of the process being completed
  71. // now is still of interest to the app.
  72. if (state.locationURL === action.locationURL) {
  73. return {
  74. /**
  75. * The {@link Error} which prevented the loading of the
  76. * configuration of the associated {@code locationURL}.
  77. *
  78. * @type Error
  79. */
  80. error: action.error
  81. };
  82. }
  83. break;
  84. case SET_CONFIG:
  85. return _setConfig(state, action);
  86. case OVERWRITE_CONFIG:
  87. return {
  88. ...state,
  89. ...action.config
  90. };
  91. }
  92. return state;
  93. });
  94. /**
  95. * Gets the initial state of the feature base/config. The mandatory
  96. * configuration to be passed to JitsiMeetJS#init(). The app will download
  97. * config.js from the Jitsi Meet deployment and take its values into account but
  98. * the values below will be enforced (because they are essential to the correct
  99. * execution of the application).
  100. *
  101. * @returns {Object}
  102. */
  103. function _getInitialState() {
  104. return (
  105. navigator.product === 'ReactNative'
  106. ? INITIAL_RN_STATE
  107. : INITIAL_NON_RN_STATE);
  108. }
  109. /**
  110. * Reduces a specific Redux action SET_CONFIG of the feature
  111. * base/lib-jitsi-meet.
  112. *
  113. * @param {Object} state - The Redux state of the feature base/config.
  114. * @param {Action} action - The Redux action SET_CONFIG to reduce.
  115. * @private
  116. * @returns {Object} The new state after the reduction of the specified action.
  117. */
  118. function _setConfig(state, { config }) {
  119. // The mobile app bundles jitsi-meet and lib-jitsi-meet at build time and
  120. // does not download them at runtime from the deployment on which it will
  121. // join a conference. The downloading is planned for implementation in the
  122. // future (later rather than sooner) but is not implemented yet at the time
  123. // of this writing and, consequently, we must provide legacy support in the
  124. // meantime.
  125. // eslint-disable-next-line no-param-reassign
  126. config = _translateLegacyConfig(config);
  127. const { audioQuality } = config;
  128. const hdAudioOptions = {};
  129. if (audioQuality?.stereo) {
  130. Object.assign(hdAudioOptions, {
  131. disableAP: true,
  132. enableNoAudioDetection: false,
  133. enableNoisyMicDetection: false,
  134. enableTalkWhileMuted: false
  135. });
  136. }
  137. const newState = _.merge(
  138. {},
  139. config,
  140. hdAudioOptions,
  141. { error: undefined },
  142. // The config of _getInitialState() is meant to override the config
  143. // downloaded from the Jitsi Meet deployment because the former contains
  144. // values that are mandatory.
  145. _getInitialState()
  146. );
  147. _cleanupConfig(newState);
  148. return equals(state, newState) ? state : newState;
  149. }
  150. /**
  151. * Constructs a new config {@code Object}, if necessary, out of a specific
  152. * config {@code Object} which is in the latest format supported by jitsi-meet.
  153. * Such a translation from an old config format to a new/the latest config
  154. * format is necessary because the mobile app bundles jitsi-meet and
  155. * lib-jitsi-meet at build time and does not download them at runtime from the
  156. * deployment on which it will join a conference.
  157. *
  158. * @param {Object} oldValue - The config {@code Object} which may or may not be
  159. * in the latest form supported by jitsi-meet and from which a new config
  160. * {@code Object} is to be constructed if necessary.
  161. * @returns {Object} A config {@code Object} which is in the latest format
  162. * supported by jitsi-meet.
  163. */
  164. function _translateLegacyConfig(oldValue: Object) {
  165. const newValue = oldValue;
  166. if (!Array.isArray(oldValue.toolbarButtons)
  167. && typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) {
  168. newValue.toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS;
  169. }
  170. if (!oldValue.connectionIndicators
  171. && typeof interfaceConfig === 'object'
  172. && (interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_DISABLED')
  173. || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_ENABLED')
  174. || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT'))) {
  175. newValue.connectionIndicators = {
  176. disabled: interfaceConfig.CONNECTION_INDICATOR_DISABLED,
  177. autoHide: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
  178. autoHideTimeout: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT
  179. };
  180. }
  181. newValue.disabledSounds = newValue.disabledSounds || [];
  182. if (oldValue.disableJoinLeaveSounds) {
  183. newValue.disabledSounds.unshift('PARTICIPANT_LEFT_SOUND', 'PARTICIPANT_JOINED_SOUND');
  184. }
  185. if (oldValue.disableRecordAudioNotification) {
  186. newValue.disabledSounds.unshift(
  187. 'RECORDING_ON_SOUND',
  188. 'RECORDING_OFF_SOUND',
  189. 'LIVE_STREAMING_ON_SOUND',
  190. 'LIVE_STREAMING_OFF_SOUND'
  191. );
  192. }
  193. if (oldValue.disableIncomingMessageSound) {
  194. newValue.disabledSounds.unshift('INCOMING_MSG_SOUND');
  195. }
  196. if (oldValue.stereo || oldValue.opusMaxAverageBitrate) {
  197. newValue.audioQuality = {
  198. opusMaxAverageBitrate: oldValue.audioQuality?.opusMaxAverageBitrate ?? oldValue.opusMaxAverageBitrate,
  199. stereo: oldValue.audioQuality?.stereo ?? oldValue.stereo
  200. };
  201. }
  202. return newValue;
  203. }
  204. /**
  205. * Updates the stored configuration with the given extra options.
  206. *
  207. * @param {Object} state - The Redux state of the feature base/config.
  208. * @param {Action} action - The Redux action to reduce.
  209. * @private
  210. * @returns {Object} The new state after the reduction of the specified action.
  211. */
  212. function _updateConfig(state, { config }) {
  213. const newState = _.merge({}, state, config);
  214. _cleanupConfig(newState);
  215. return equals(state, newState) ? state : newState;
  216. }