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

middleware.web.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // @flow
  2. import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from '../../../../modules/UI/UIErrors';
  3. import { setNoiseSuppressionEnabled } from '../../noise-suppression/actions';
  4. import { showNotification, NOTIFICATION_TIMEOUT_TYPE } from '../../notifications';
  5. import {
  6. setPrejoinPageVisibility,
  7. setSkipPrejoinOnReload
  8. } from '../../prejoin';
  9. import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share';
  10. import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
  11. import { setAudioOnly } from '../audio-only';
  12. import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
  13. import { JitsiConferenceErrors, JitsiTrackErrors } from '../lib-jitsi-meet';
  14. import { MEDIA_TYPE, setScreenshareMuted, VIDEO_TYPE } from '../media';
  15. import { MiddlewareRegistry } from '../redux';
  16. import {
  17. addLocalTrack,
  18. createLocalTracksF,
  19. getLocalDesktopTrack,
  20. getLocalJitsiAudioTrack,
  21. replaceLocalTrack,
  22. TOGGLE_SCREENSHARING
  23. } from '../tracks';
  24. import { CONFERENCE_FAILED, CONFERENCE_JOIN_IN_PROGRESS, CONFERENCE_JOINED } from './actionTypes';
  25. import { getCurrentConference } from './functions';
  26. import './middleware.any';
  27. MiddlewareRegistry.register(store => next => action => {
  28. const { dispatch, getState } = store;
  29. const { enableForcedReload } = getState()['features/base/config'];
  30. switch (action.type) {
  31. case CONFERENCE_JOIN_IN_PROGRESS: {
  32. dispatch(setPrejoinPageVisibility(false));
  33. break;
  34. }
  35. case CONFERENCE_JOINED: {
  36. if (enableForcedReload) {
  37. dispatch(setSkipPrejoinOnReload(false));
  38. }
  39. break;
  40. }
  41. case CONFERENCE_FAILED: {
  42. const errorName = action.error?.name;
  43. if (enableForcedReload && errorName === JitsiConferenceErrors.CONFERENCE_RESTARTED) {
  44. dispatch(setSkipPrejoinOnReload(true));
  45. }
  46. break;
  47. }
  48. case TOGGLE_SCREENSHARING: {
  49. getMultipleVideoSendingSupportFeatureFlag(getState()) && _toggleScreenSharing(action, store);
  50. break;
  51. }
  52. }
  53. return next(action);
  54. });
  55. /**
  56. * Displays a UI notification for screensharing failure based on the error passed.
  57. *
  58. * @private
  59. * @param {Object} error - The error.
  60. * @param {Object} store - The redux store.
  61. * @returns {void}
  62. */
  63. function _handleScreensharingError(error, { dispatch }) {
  64. if (error.name === JitsiTrackErrors.SCREENSHARING_USER_CANCELED) {
  65. return;
  66. }
  67. let descriptionKey, titleKey;
  68. if (error.name === JitsiTrackErrors.PERMISSION_DENIED) {
  69. descriptionKey = 'dialog.screenSharingPermissionDeniedError';
  70. titleKey = 'dialog.screenSharingFailedTitle';
  71. } else if (error.name === JitsiTrackErrors.CONSTRAINT_FAILED) {
  72. descriptionKey = 'dialog.cameraConstraintFailedError';
  73. titleKey = 'deviceError.cameraError';
  74. } else if (error.name === JitsiTrackErrors.SCREENSHARING_GENERIC_ERROR) {
  75. descriptionKey = 'dialog.screenSharingFailed';
  76. titleKey = 'dialog.screenSharingFailedTitle';
  77. } else if (error === AUDIO_ONLY_SCREEN_SHARE_NO_TRACK) {
  78. descriptionKey = 'notify.screenShareNoAudio';
  79. titleKey = 'notify.screenShareNoAudioTitle';
  80. }
  81. dispatch(showNotification({
  82. titleKey,
  83. descriptionKey
  84. }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
  85. }
  86. /**
  87. * Applies the AudioMixer effect on the local audio track if applicable. If there is no local audio track, the desktop
  88. * audio track is added to the conference.
  89. *
  90. * @private
  91. * @param {JitsiLocalTrack} desktopAudioTrack - The audio track to be added to the conference.
  92. * @param {*} state - The redux state.
  93. * @returns {void}
  94. */
  95. async function _maybeApplyAudioMixerEffect(desktopAudioTrack, state) {
  96. const localAudio = getLocalJitsiAudioTrack(state);
  97. const conference = getCurrentConference(state);
  98. if (localAudio) {
  99. // If there is a localAudio stream, mix in the desktop audio stream captured by the screen sharing API.
  100. const mixerEffect = new AudioMixerEffect(desktopAudioTrack);
  101. await localAudio.setEffect(mixerEffect);
  102. } else {
  103. // If no local stream is present ( i.e. no input audio devices) we use the screen share audio
  104. // stream as we would use a regular stream.
  105. await conference.replaceTrack(null, desktopAudioTrack);
  106. }
  107. }
  108. /**
  109. * Toggles screen sharing.
  110. *
  111. * @private
  112. * @param {boolean} enabled - The state to toggle screen sharing to.
  113. * @param {Store} store - The redux store.
  114. * @returns {void}
  115. */
  116. async function _toggleScreenSharing({ enabled, audioOnly = false }, store) {
  117. const { dispatch, getState } = store;
  118. const state = getState();
  119. const conference = getCurrentConference(state);
  120. const localAudio = getLocalJitsiAudioTrack(state);
  121. const localScreenshare = getLocalDesktopTrack(state['features/base/tracks']);
  122. if (enabled) {
  123. let tracks;
  124. try {
  125. tracks = await createLocalTracksF({ devices: [ VIDEO_TYPE.DESKTOP ] });
  126. } catch (error) {
  127. _handleScreensharingError(error, store);
  128. return;
  129. }
  130. const desktopAudioTrack = tracks.find(track => track.getType() === MEDIA_TYPE.AUDIO);
  131. const desktopVideoTrack = tracks.find(track => track.getType() === MEDIA_TYPE.VIDEO);
  132. // Dispose the desktop track for audio-only screensharing.
  133. if (audioOnly) {
  134. desktopVideoTrack.dispose();
  135. if (!desktopAudioTrack) {
  136. _handleScreensharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, store);
  137. return;
  138. }
  139. } else if (desktopVideoTrack) {
  140. if (localScreenshare) {
  141. await dispatch(replaceLocalTrack(localScreenshare.jitsiTrack, desktopVideoTrack, conference));
  142. } else {
  143. await dispatch(addLocalTrack(desktopVideoTrack));
  144. }
  145. }
  146. // Apply the AudioMixer effect if there is a local audio track, add the desktop track to the conference
  147. // otherwise without unmuting the microphone.
  148. if (desktopAudioTrack) {
  149. // Noise suppression doesn't work with desktop audio because we can't chain
  150. // track effects yet, disable it first.
  151. // We need to to wait for the effect to clear first or it might interfere with the audio mixer.
  152. await dispatch(setNoiseSuppressionEnabled(false));
  153. _maybeApplyAudioMixerEffect(desktopAudioTrack, state);
  154. dispatch(setScreenshareAudioTrack(desktopAudioTrack));
  155. }
  156. // Disable audio-only or best performance mode if the user starts screensharing. This doesn't apply to
  157. // audio-only screensharing.
  158. const { enabled: bestPerformanceMode } = state['features/base/audio-only'];
  159. if (bestPerformanceMode && !audioOnly) {
  160. dispatch(setAudioOnly(false));
  161. }
  162. } else {
  163. const { desktopAudioTrack } = state['features/screen-share'];
  164. // Mute the desktop track instead of removing it from the conference since we don't want the client to signal
  165. // a source-remove to the remote peer for the screenshare track. Later when screenshare is enabled again, the
  166. // same sender will be re-used without the need for signaling a new ssrc through source-add.
  167. dispatch(setScreenshareMuted(true));
  168. if (desktopAudioTrack) {
  169. if (localAudio) {
  170. localAudio.setEffect(undefined);
  171. } else {
  172. await conference.replaceTrack(desktopAudioTrack, null);
  173. }
  174. desktopAudioTrack.dispose();
  175. dispatch(setScreenshareAudioTrack(null));
  176. }
  177. }
  178. if (audioOnly) {
  179. dispatch(setScreenAudioShareState(enabled));
  180. }
  181. }