您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

middleware.js 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // @flow
  2. import {
  3. createStartAudioOnlyEvent,
  4. createStartMutedConfigurationEvent,
  5. createSyncTrackStateEvent,
  6. createTrackMutedEvent,
  7. sendAnalytics
  8. } from '../../analytics';
  9. import { APP_STATE_CHANGED } from '../../mobile/background';
  10. import { SET_AUDIO_ONLY, setAudioOnly } from '../audio-only';
  11. import { isRoomValid, SET_ROOM } from '../conference';
  12. import JitsiMeetJS from '../lib-jitsi-meet';
  13. import { MiddlewareRegistry } from '../redux';
  14. import { getPropertyValue } from '../settings';
  15. import { setTrackMuted, TRACK_ADDED } from '../tracks';
  16. import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
  17. import { CAMERA_FACING_MODE, VIDEO_MUTISM_AUTHORITY } from './constants';
  18. import {
  19. _AUDIO_INITIAL_MEDIA_STATE,
  20. _VIDEO_INITIAL_MEDIA_STATE
  21. } from './reducer';
  22. const logger = require('jitsi-meet-logger').getLogger(__filename);
  23. /**
  24. * Implements the entry point of the middleware of the feature base/media.
  25. *
  26. * @param {Store} store - The redux store.
  27. * @returns {Function}
  28. */
  29. MiddlewareRegistry.register(store => next => action => {
  30. switch (action.type) {
  31. case APP_STATE_CHANGED:
  32. return _appStateChanged(store, next, action);
  33. case SET_AUDIO_ONLY:
  34. return _setAudioOnly(store, next, action);
  35. case SET_ROOM:
  36. return _setRoom(store, next, action);
  37. case TRACK_ADDED: {
  38. const result = next(action);
  39. const { track } = action;
  40. track.local && _syncTrackMutedState(store, track);
  41. return result;
  42. }
  43. }
  44. return next(action);
  45. });
  46. /**
  47. * Adjusts the video muted state based on the app state.
  48. *
  49. * @param {Store} store - The redux store in which the specified {@code action}
  50. * is being dispatched.
  51. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  52. * specified {@code action} to the specified {@code store}.
  53. * @param {Action} action - The redux action {@code APP_STATE_CHANGED} which is
  54. * being dispatched in the specified {@code store}.
  55. * @private
  56. * @returns {Object} The value returned by {@code next(action)}.
  57. */
  58. function _appStateChanged({ dispatch }, next, action) {
  59. const { appState } = action;
  60. const mute = appState !== 'active'; // Note that 'background' and 'inactive' are treated equal.
  61. sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
  62. dispatch(setVideoMuted(mute, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
  63. return next(action);
  64. }
  65. /**
  66. * Adjusts the video muted state based on the audio-only state.
  67. *
  68. * @param {Store} store - The redux store in which the specified {@code action}
  69. * is being dispatched.
  70. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  71. * specified {@code action} to the specified {@code store}.
  72. * @param {Action} action - The redux action {@code SET_AUDIO_ONLY} which is
  73. * being dispatched in the specified {@code store}.
  74. * @private
  75. * @returns {Object} The value returned by {@code next(action)}.
  76. */
  77. function _setAudioOnly({ dispatch }, next, action) {
  78. const { audioOnly } = action;
  79. sendAnalytics(createTrackMutedEvent('video', 'audio-only mode', audioOnly));
  80. dispatch(setVideoMuted(audioOnly, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY));
  81. return next(action);
  82. }
  83. /**
  84. * Notifies the feature base/media that the action {@link SET_ROOM} is being
  85. * dispatched within a specific redux {@code store}.
  86. *
  87. * @param {Store} store - The redux store in which the specified {@code action}
  88. * is being dispatched.
  89. * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
  90. * specified {@code action} to the specified {@code store}.
  91. * @param {Action} action - The redux action, {@code SET_ROOM}, which is being
  92. * dispatched in the specified {@code store}.
  93. * @private
  94. * @returns {Object} The new state that is the result of the reduction of the
  95. * specified {@code action}.
  96. */
  97. function _setRoom({ dispatch, getState }, next, action) {
  98. // Figure out the desires/intents i.e. the state of base/media. There are
  99. // multiple desires/intents ordered by precedence such as server-side
  100. // config, config overrides in the user-supplied URL, user's own app
  101. // settings, etc.
  102. const state = getState();
  103. const { room } = action;
  104. const roomIsValid = isRoomValid(room);
  105. // XXX The configurations/preferences/settings startWithAudioMuted,
  106. // startWithVideoMuted, and startAudioOnly were introduced for
  107. // conferences/meetings. So it makes sense for these to not be considered
  108. // outside of conferences/meetings (e.g. WelcomePage). Later on, though, we
  109. // introduced a "Video <-> Voice" toggle on the WelcomePage which utilizes
  110. // startAudioOnly outside of conferences/meetings so that particular
  111. // configuration/preference/setting employs slightly exclusive logic.
  112. const mutedSources = {
  113. // We have startWithAudioMuted and startWithVideoMuted here:
  114. config: true,
  115. settings: true,
  116. // XXX We've already overwritten base/config with urlParams. However,
  117. // settings are more important than the server-side config.
  118. // Consequently, we need to read from urlParams anyway:
  119. urlParams: true,
  120. // We don't have startWithAudioMuted and startWithVideoMuted here:
  121. jwt: false
  122. };
  123. const audioMuted
  124. = roomIsValid
  125. ? Boolean(
  126. getPropertyValue(state, 'startWithAudioMuted', mutedSources))
  127. : _AUDIO_INITIAL_MEDIA_STATE.muted;
  128. const videoMuted
  129. = roomIsValid
  130. ? Boolean(
  131. getPropertyValue(state, 'startWithVideoMuted', mutedSources))
  132. : _VIDEO_INITIAL_MEDIA_STATE.muted;
  133. sendAnalytics(
  134. createStartMutedConfigurationEvent('local', audioMuted, videoMuted));
  135. logger.log(
  136. `Start muted: ${audioMuted ? 'audio, ' : ''}${
  137. videoMuted ? 'video' : ''}`);
  138. // Unconditionally express the desires/expectations/intents of the app and
  139. // the user i.e. the state of base/media. Eventually, practice/reality i.e.
  140. // the state of base/tracks will or will not agree with the desires.
  141. dispatch(setAudioMuted(audioMuted));
  142. dispatch(setCameraFacingMode(CAMERA_FACING_MODE.USER));
  143. dispatch(setVideoMuted(videoMuted));
  144. // startAudioOnly
  145. //
  146. // FIXME Technically, the audio-only feature is owned by base/conference,
  147. // not base/media so the following should be in base/conference.
  148. // Practically, I presume it was easier to write the source code here
  149. // because it looks like startWithAudioMuted and startWithVideoMuted.
  150. //
  151. // XXX After the introduction of the "Video <-> Voice" toggle on the
  152. // WelcomePage, startAudioOnly is utilized even outside of
  153. // conferences/meetings.
  154. let audioOnly;
  155. if (JitsiMeetJS.mediaDevices.supportsVideo()) {
  156. audioOnly
  157. = Boolean(
  158. getPropertyValue(
  159. state,
  160. 'startAudioOnly',
  161. /* sources */ {
  162. // FIXME Practically, base/config is (really) correct
  163. // only if roomIsValid. At the time of this writing,
  164. // base/config is overwritten by URL params which leaves
  165. // base/config incorrect on the WelcomePage after
  166. // leaving a conference which explicitly overwrites
  167. // base/config with URL params.
  168. config: roomIsValid,
  169. // XXX We've already overwritten base/config with
  170. // urlParams if roomIsValid. However, settings are more
  171. // important than the server-side config. Consequently,
  172. // we need to read from urlParams anyway. We also
  173. // probably want to read from urlParams when
  174. // !roomIsValid.
  175. urlParams: true,
  176. // The following don't have complications around whether
  177. // they are defined or not:
  178. jwt: false,
  179. settings: true
  180. }));
  181. } else {
  182. // Default to audio-only if the (execution) environment does not
  183. // support (sending and/or receiving) video.
  184. audioOnly = true;
  185. }
  186. sendAnalytics(createStartAudioOnlyEvent(audioOnly));
  187. logger.log(`Start audio only set to ${audioOnly.toString()}`);
  188. dispatch(setAudioOnly(audioOnly, false));
  189. return next(action);
  190. }
  191. /**
  192. * Syncs muted state of local media track with muted state from media state.
  193. *
  194. * @param {Store} store - The redux store.
  195. * @param {Track} track - The local media track.
  196. * @private
  197. * @returns {void}
  198. */
  199. function _syncTrackMutedState({ getState }, track) {
  200. const state = getState()['features/base/media'];
  201. const muted = Boolean(state[track.mediaType].muted);
  202. // XXX If muted state of track when it was added is different from our media
  203. // muted state, we need to mute track and explicitly modify 'muted' property
  204. // on track. This is because though TRACK_ADDED action was dispatched it's
  205. // not yet in redux state and JitsiTrackEvents.TRACK_MUTE_CHANGED may be
  206. // fired before track gets to state.
  207. if (track.muted !== muted) {
  208. sendAnalytics(createSyncTrackStateEvent(track.mediaType, muted));
  209. logger.log(
  210. `Sync ${track.mediaType} track muted state to ${
  211. muted ? 'muted' : 'unmuted'}`);
  212. track.muted = muted;
  213. setTrackMuted(track.jitsiTrack, muted);
  214. }
  215. }