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 4.9KB

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