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.

middleware.ts 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import { NativeEventEmitter, NativeModules } from 'react-native';
  2. import { AnyAction } from 'redux';
  3. import { IStore } from '../../app/types';
  4. import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
  5. import { SET_AUDIO_ONLY } from '../../base/audio-only/actionTypes';
  6. import {
  7. CONFERENCE_FAILED,
  8. CONFERENCE_JOINED,
  9. CONFERENCE_LEFT
  10. } from '../../base/conference/actionTypes';
  11. import { getCurrentConference } from '../../base/conference/functions';
  12. import { SET_CONFIG } from '../../base/config/actionTypes';
  13. import { AUDIO_FOCUS_DISABLED } from '../../base/flags/constants';
  14. import { getFeatureFlag } from '../../base/flags/functions';
  15. import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
  16. import { parseURIString } from '../../base/util/uri';
  17. import { _SET_AUDIOMODE_DEVICES, _SET_AUDIOMODE_SUBSCRIPTIONS } from './actionTypes';
  18. import logger from './logger';
  19. const { AudioMode } = NativeModules;
  20. const AudioModeEmitter = new NativeEventEmitter(AudioMode);
  21. /**
  22. * Middleware that captures conference actions and sets the correct audio mode
  23. * based on the type of conference. Audio-only conferences don't use the speaker
  24. * by default, and video conferences do.
  25. *
  26. * @param {Store} store - The redux store.
  27. * @returns {Function}
  28. */
  29. MiddlewareRegistry.register(store => next => action => {
  30. /* eslint-disable no-fallthrough */
  31. switch (action.type) {
  32. case _SET_AUDIOMODE_SUBSCRIPTIONS:
  33. _setSubscriptions(store);
  34. break;
  35. case APP_WILL_UNMOUNT: {
  36. store.dispatch({
  37. type: _SET_AUDIOMODE_SUBSCRIPTIONS,
  38. subscriptions: undefined
  39. });
  40. break;
  41. }
  42. case APP_WILL_MOUNT:
  43. _appWillMount(store);
  44. case CONFERENCE_FAILED:
  45. case CONFERENCE_LEFT:
  46. /*
  47. * NOTE: We moved the audio mode setting from CONFERENCE_WILL_JOIN to
  48. * CONFERENCE_JOINED because in case of a locked room, the app goes
  49. * through CONFERENCE_FAILED state and gets to CONFERENCE_JOINED only
  50. * after a correct password, so we want to make sure we have the correct
  51. * audio mode set up when we finally get to the conf, but also make sure
  52. * that the app is in the right audio mode if the user leaves the
  53. * conference after the password prompt appears.
  54. */
  55. case CONFERENCE_JOINED:
  56. case SET_AUDIO_ONLY:
  57. return _updateAudioMode(store, next, action);
  58. case SET_CONFIG: {
  59. const { locationURL } = store.getState()['features/base/connection'];
  60. const location = parseURIString(locationURL?.href ?? '');
  61. /**
  62. * Don't touch the current value if there is no room in the URL. This
  63. * avoids audio cutting off for a moment right after the user leaves
  64. * a meeting. The next meeting join will set it to the right value.
  65. */
  66. if (location.room) {
  67. const { startSilent } = action.config;
  68. AudioMode.setDisabled?.(Boolean(startSilent));
  69. }
  70. break;
  71. }
  72. }
  73. /* eslint-enable no-fallthrough */
  74. return next(action);
  75. });
  76. /**
  77. * Notifies this feature that the action {@link APP_WILL_MOUNT} is being
  78. * dispatched within a specific redux {@code store}.
  79. *
  80. * @param {Store} store - The redux store in which the specified {@code action}
  81. * is being dispatched.
  82. * @private
  83. * @returns {void}
  84. */
  85. function _appWillMount(store: IStore) {
  86. const subscriptions = [
  87. AudioModeEmitter.addListener(AudioMode.DEVICE_CHANGE_EVENT, _onDevicesUpdate, store)
  88. ];
  89. store.dispatch({
  90. type: _SET_AUDIOMODE_SUBSCRIPTIONS,
  91. subscriptions
  92. });
  93. }
  94. /**
  95. * Handles audio device changes. The list will be stored on the redux store.
  96. *
  97. * @param {Object} devices - The current list of devices.
  98. * @private
  99. * @returns {void}
  100. */
  101. function _onDevicesUpdate(devices: any) {
  102. // @ts-ignore
  103. const { dispatch } = this; // eslint-disable-line @typescript-eslint/no-invalid-this
  104. dispatch({
  105. type: _SET_AUDIOMODE_DEVICES,
  106. devices
  107. });
  108. }
  109. /**
  110. * Notifies this feature that the action
  111. * {@link _SET_AUDIOMODE_SUBSCRIPTIONS} is being dispatched within
  112. * a specific redux {@code store}.
  113. *
  114. * @param {Store} store - The redux store in which the specified {@code action}
  115. * is being dispatched.
  116. * @private
  117. * @returns {void}
  118. */
  119. function _setSubscriptions({ getState }: IStore) {
  120. const { subscriptions } = getState()['features/mobile/audio-mode'];
  121. if (subscriptions) {
  122. for (const subscription of subscriptions) {
  123. subscription.remove();
  124. }
  125. }
  126. }
  127. /**
  128. * Updates the audio mode based on the current (redux) state.
  129. *
  130. * @param {Store} store - The redux store in which the specified {@code action}
  131. * is being dispatched.
  132. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  133. * specified {@code action} in the specified {@code store}.
  134. * @param {Action} action - The redux action which is
  135. * being dispatched in the specified {@code store}.
  136. * @private
  137. * @returns {*} The value returned by {@code next(action)}.
  138. */
  139. function _updateAudioMode({ getState }: IStore, next: Function, action: AnyAction) {
  140. const result = next(action);
  141. const state = getState();
  142. const conference = getCurrentConference(state);
  143. const { enabled: audioOnly } = state['features/base/audio-only'];
  144. let mode: string;
  145. if (getFeatureFlag(state, AUDIO_FOCUS_DISABLED, false)) {
  146. return result;
  147. } else if (conference) {
  148. mode = audioOnly ? AudioMode.AUDIO_CALL : AudioMode.VIDEO_CALL;
  149. } else {
  150. mode = AudioMode.DEFAULT;
  151. }
  152. AudioMode.setMode(mode).catch((err: any) => logger.error(`Failed to set audio mode ${String(mode)}: ${err}`));
  153. return result;
  154. }