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.js 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // @flow
  2. import { batch } from 'react-redux';
  3. import { getConferenceState } from '../base/conference';
  4. import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
  5. import { MEDIA_TYPE } from '../base/media';
  6. import {
  7. getLocalParticipant,
  8. getParticipantDisplayName,
  9. getRemoteParticipants,
  10. isLocalParticipantModerator,
  11. isParticipantModerator,
  12. PARTICIPANT_UPDATED,
  13. raiseHand
  14. } from '../base/participants';
  15. import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
  16. import {
  17. hideNotification,
  18. NOTIFICATION_TIMEOUT,
  19. showNotification
  20. } from '../notifications';
  21. import {
  22. DISABLE_MODERATION,
  23. ENABLE_MODERATION,
  24. LOCAL_PARTICIPANT_MODERATION_NOTIFICATION,
  25. REQUEST_DISABLE_MODERATION,
  26. REQUEST_ENABLE_MODERATION
  27. } from './actionTypes';
  28. import {
  29. disableModeration,
  30. dismissPendingParticipant,
  31. dismissPendingAudioParticipant,
  32. enableModeration,
  33. localParticipantApproved,
  34. participantApproved,
  35. participantPendingAudio
  36. } from './actions';
  37. import {
  38. isEnabledFromState,
  39. isParticipantApproved,
  40. isParticipantPending
  41. } from './functions';
  42. const VIDEO_MODERATION_NOTIFICATION_ID = 'video-moderation';
  43. const AUDIO_MODERATION_NOTIFICATION_ID = 'audio-moderation';
  44. const CS_MODERATION_NOTIFICATION_ID = 'video-moderation';
  45. MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
  46. const { actor, mediaType, type } = action;
  47. switch (type) {
  48. case DISABLE_MODERATION:
  49. case ENABLE_MODERATION: {
  50. // Audio & video moderation are both enabled at the same time.
  51. // Avoid displaying 2 different notifications.
  52. if (mediaType === MEDIA_TYPE.VIDEO) {
  53. const titleKey = type === ENABLE_MODERATION
  54. ? 'notify.moderationStartedTitle'
  55. : 'notify.moderationStoppedTitle';
  56. dispatch(showNotification({
  57. descriptionKey: actor ? 'notify.moderationToggleDescription' : undefined,
  58. descriptionArguments: actor ? {
  59. participantDisplayName: getParticipantDisplayName(getState, actor.getId())
  60. } : undefined,
  61. titleKey
  62. }, NOTIFICATION_TIMEOUT));
  63. }
  64. break;
  65. }
  66. case LOCAL_PARTICIPANT_MODERATION_NOTIFICATION: {
  67. let descriptionKey;
  68. let titleKey;
  69. let uid;
  70. switch (action.mediaType) {
  71. case MEDIA_TYPE.AUDIO: {
  72. titleKey = 'notify.moderationInEffectTitle';
  73. descriptionKey = 'notify.moderationInEffectDescription';
  74. uid = AUDIO_MODERATION_NOTIFICATION_ID;
  75. break;
  76. }
  77. case MEDIA_TYPE.VIDEO: {
  78. titleKey = 'notify.moderationInEffectVideoTitle';
  79. descriptionKey = 'notify.moderationInEffectVideoDescription';
  80. uid = VIDEO_MODERATION_NOTIFICATION_ID;
  81. break;
  82. }
  83. case MEDIA_TYPE.PRESENTER: {
  84. titleKey = 'notify.moderationInEffectCSTitle';
  85. descriptionKey = 'notify.moderationInEffectCSDescription';
  86. uid = CS_MODERATION_NOTIFICATION_ID;
  87. break;
  88. }
  89. }
  90. dispatch(showNotification({
  91. customActionNameKey: 'notify.raiseHandAction',
  92. customActionHandler: () => batch(() => {
  93. dispatch(raiseHand(true));
  94. dispatch(hideNotification(uid));
  95. }),
  96. descriptionKey,
  97. sticky: true,
  98. titleKey,
  99. uid
  100. }));
  101. break;
  102. }
  103. case REQUEST_DISABLE_MODERATION: {
  104. const { conference } = getConferenceState(getState());
  105. conference.disableAVModeration(MEDIA_TYPE.AUDIO);
  106. conference.disableAVModeration(MEDIA_TYPE.VIDEO);
  107. break;
  108. }
  109. case REQUEST_ENABLE_MODERATION: {
  110. const { conference } = getConferenceState(getState());
  111. conference.enableAVModeration(MEDIA_TYPE.AUDIO);
  112. conference.enableAVModeration(MEDIA_TYPE.VIDEO);
  113. break;
  114. }
  115. case PARTICIPANT_UPDATED: {
  116. const state = getState();
  117. const audioModerationEnabled = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
  118. const participant = action.participant;
  119. if (participant && audioModerationEnabled) {
  120. if (isLocalParticipantModerator(state)) {
  121. // this is handled only by moderators
  122. if (participant.raisedHand) {
  123. // if participant raises hand show notification
  124. !isParticipantApproved(participant.id, MEDIA_TYPE.AUDIO)(state)
  125. && dispatch(participantPendingAudio(participant));
  126. } else {
  127. // if participant lowers hand hide notification
  128. isParticipantPending(participant, MEDIA_TYPE.AUDIO)(state)
  129. && dispatch(dismissPendingAudioParticipant(participant));
  130. }
  131. } else if (participant.id === getLocalParticipant(state).id
  132. && /* the new role */ isParticipantModerator(participant)) {
  133. // this is the granted moderator case
  134. getRemoteParticipants(state).forEach(p => {
  135. p.raisedHand && !isParticipantApproved(p.id, MEDIA_TYPE.AUDIO)(state)
  136. && dispatch(participantPendingAudio(p));
  137. });
  138. }
  139. }
  140. break;
  141. }
  142. }
  143. return next(action);
  144. });
  145. /**
  146. * Registers a change handler for state['features/base/conference'].conference to
  147. * set the event listeners needed for the A/V moderation feature to operate.
  148. */
  149. StateListenerRegistry.register(
  150. state => state['features/base/conference'].conference,
  151. (conference, { dispatch }, previousConference) => {
  152. if (conference && !previousConference) {
  153. // local participant is allowed to unmute
  154. conference.on(JitsiConferenceEvents.AV_MODERATION_APPROVED, ({ mediaType }) => {
  155. dispatch(localParticipantApproved(mediaType));
  156. // Audio & video moderation are both enabled at the same time.
  157. // Avoid displaying 2 different notifications.
  158. if (mediaType === MEDIA_TYPE.VIDEO) {
  159. dispatch(showNotification({
  160. titleKey: 'notify.unmute',
  161. descriptionKey: 'notify.hostAskedUnmute',
  162. sticky: true
  163. }));
  164. }
  165. });
  166. conference.on(JitsiConferenceEvents.AV_MODERATION_CHANGED, ({ enabled, mediaType, actor }) => {
  167. enabled ? dispatch(enableModeration(mediaType, actor)) : dispatch(disableModeration(mediaType, actor));
  168. });
  169. // this is received by moderators
  170. conference.on(
  171. JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_APPROVED,
  172. ({ participant, mediaType }) => {
  173. const { _id: id } = participant;
  174. batch(() => {
  175. // store in the whitelist
  176. dispatch(participantApproved(id, mediaType));
  177. // remove from pending list
  178. dispatch(dismissPendingParticipant(id, mediaType));
  179. });
  180. });
  181. }
  182. });