Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

middleware.js 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. // @flow
  2. import { batch } from 'react-redux';
  3. import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
  4. import { getConferenceState } from '../base/conference';
  5. import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
  6. import { MEDIA_TYPE } from '../base/media';
  7. import {
  8. getLocalParticipant,
  9. getRemoteParticipants,
  10. hasRaisedHand,
  11. isLocalParticipantModerator,
  12. isParticipantModerator,
  13. PARTICIPANT_UPDATED,
  14. raiseHand
  15. } from '../base/participants';
  16. import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
  17. import { playSound, registerSound, unregisterSound } from '../base/sounds';
  18. import {
  19. hideNotification,
  20. showNotification
  21. } from '../notifications';
  22. import { muteLocal } from '../video-menu/actions.any';
  23. import {
  24. DISABLE_MODERATION,
  25. ENABLE_MODERATION,
  26. LOCAL_PARTICIPANT_APPROVED,
  27. LOCAL_PARTICIPANT_MODERATION_NOTIFICATION,
  28. LOCAL_PARTICIPANT_REJECTED,
  29. PARTICIPANT_APPROVED,
  30. PARTICIPANT_REJECTED,
  31. REQUEST_DISABLE_AUDIO_MODERATION,
  32. REQUEST_DISABLE_VIDEO_MODERATION,
  33. REQUEST_ENABLE_AUDIO_MODERATION,
  34. REQUEST_ENABLE_VIDEO_MODERATION
  35. } from './actionTypes';
  36. import {
  37. disableModeration,
  38. dismissPendingParticipant,
  39. dismissPendingAudioParticipant,
  40. enableModeration,
  41. localParticipantApproved,
  42. participantApproved,
  43. participantPendingAudio,
  44. localParticipantRejected,
  45. participantRejected
  46. } from './actions';
  47. import {
  48. ASKED_TO_UNMUTE_SOUND_ID, AUDIO_MODERATION_NOTIFICATION_ID,
  49. CS_MODERATION_NOTIFICATION_ID,
  50. VIDEO_MODERATION_NOTIFICATION_ID
  51. } from './constants';
  52. import {
  53. isEnabledFromState,
  54. isParticipantApproved,
  55. isParticipantPending
  56. } from './functions';
  57. import { ASKED_TO_UNMUTE_FILE } from './sounds';
  58. declare var APP: Object;
  59. MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
  60. const { type } = action;
  61. const { conference } = getConferenceState(getState());
  62. switch (type) {
  63. case APP_WILL_MOUNT: {
  64. dispatch(registerSound(ASKED_TO_UNMUTE_SOUND_ID, ASKED_TO_UNMUTE_FILE));
  65. break;
  66. }
  67. case APP_WILL_UNMOUNT: {
  68. dispatch(unregisterSound(ASKED_TO_UNMUTE_SOUND_ID));
  69. break;
  70. }
  71. case LOCAL_PARTICIPANT_MODERATION_NOTIFICATION: {
  72. let descriptionKey;
  73. let titleKey;
  74. let uid;
  75. switch (action.mediaType) {
  76. case MEDIA_TYPE.AUDIO: {
  77. titleKey = 'notify.moderationInEffectTitle';
  78. uid = AUDIO_MODERATION_NOTIFICATION_ID;
  79. break;
  80. }
  81. case MEDIA_TYPE.VIDEO: {
  82. titleKey = 'notify.moderationInEffectVideoTitle';
  83. uid = VIDEO_MODERATION_NOTIFICATION_ID;
  84. break;
  85. }
  86. case MEDIA_TYPE.PRESENTER: {
  87. titleKey = 'notify.moderationInEffectCSTitle';
  88. uid = CS_MODERATION_NOTIFICATION_ID;
  89. break;
  90. }
  91. }
  92. dispatch(showNotification({
  93. customActionNameKey: 'notify.raiseHandAction',
  94. customActionHandler: () => batch(() => {
  95. dispatch(raiseHand(true));
  96. dispatch(hideNotification(uid));
  97. }),
  98. descriptionKey,
  99. sticky: true,
  100. titleKey,
  101. uid
  102. }));
  103. break;
  104. }
  105. case REQUEST_DISABLE_AUDIO_MODERATION: {
  106. conference.disableAVModeration(MEDIA_TYPE.AUDIO);
  107. break;
  108. }
  109. case REQUEST_DISABLE_VIDEO_MODERATION: {
  110. conference.disableAVModeration(MEDIA_TYPE.VIDEO);
  111. break;
  112. }
  113. case REQUEST_ENABLE_AUDIO_MODERATION: {
  114. conference.enableAVModeration(MEDIA_TYPE.AUDIO);
  115. break;
  116. }
  117. case REQUEST_ENABLE_VIDEO_MODERATION: {
  118. conference.enableAVModeration(MEDIA_TYPE.VIDEO);
  119. break;
  120. }
  121. case PARTICIPANT_UPDATED: {
  122. const state = getState();
  123. const audioModerationEnabled = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
  124. const participant = action.participant;
  125. if (participant && audioModerationEnabled) {
  126. if (isLocalParticipantModerator(state)) {
  127. // this is handled only by moderators
  128. if (hasRaisedHand(participant)) {
  129. // if participant raises hand show notification
  130. !isParticipantApproved(participant.id, MEDIA_TYPE.AUDIO)(state)
  131. && dispatch(participantPendingAudio(participant));
  132. } else {
  133. // if participant lowers hand hide notification
  134. isParticipantPending(participant, MEDIA_TYPE.AUDIO)(state)
  135. && dispatch(dismissPendingAudioParticipant(participant));
  136. }
  137. } else if (participant.id === getLocalParticipant(state).id
  138. && /* the new role */ isParticipantModerator(participant)) {
  139. // this is the granted moderator case
  140. getRemoteParticipants(state).forEach(p => {
  141. hasRaisedHand(p) && !isParticipantApproved(p.id, MEDIA_TYPE.AUDIO)(state)
  142. && dispatch(participantPendingAudio(p));
  143. });
  144. }
  145. }
  146. break;
  147. }
  148. case ENABLE_MODERATION: {
  149. if (typeof APP !== 'undefined') {
  150. APP.API.notifyModerationChanged(action.mediaType, true);
  151. }
  152. break;
  153. }
  154. case DISABLE_MODERATION: {
  155. if (typeof APP !== 'undefined') {
  156. APP.API.notifyModerationChanged(action.mediaType, false);
  157. }
  158. break;
  159. }
  160. case LOCAL_PARTICIPANT_APPROVED: {
  161. if (typeof APP !== 'undefined') {
  162. const local = getLocalParticipant(getState());
  163. APP.API.notifyParticipantApproved(local.id, action.mediaType);
  164. }
  165. break;
  166. }
  167. case PARTICIPANT_APPROVED: {
  168. if (typeof APP !== 'undefined') {
  169. APP.API.notifyParticipantApproved(action.id, action.mediaType);
  170. }
  171. break;
  172. }
  173. case LOCAL_PARTICIPANT_REJECTED: {
  174. if (typeof APP !== 'undefined') {
  175. const local = getLocalParticipant(getState());
  176. APP.API.notifyParticipantRejected(local.id, action.mediaType);
  177. }
  178. break;
  179. }
  180. case PARTICIPANT_REJECTED: {
  181. if (typeof APP !== 'undefined') {
  182. APP.API.notifyParticipantRejected(action.id, action.mediaType);
  183. }
  184. break;
  185. }
  186. }
  187. return next(action);
  188. });
  189. /**
  190. * Registers a change handler for state['features/base/conference'].conference to
  191. * set the event listeners needed for the A/V moderation feature to operate.
  192. */
  193. StateListenerRegistry.register(
  194. state => state['features/base/conference'].conference,
  195. (conference, { dispatch }, previousConference) => {
  196. if (conference && !previousConference) {
  197. // local participant is allowed to unmute
  198. conference.on(JitsiConferenceEvents.AV_MODERATION_APPROVED, ({ mediaType }) => {
  199. dispatch(localParticipantApproved(mediaType));
  200. // Audio & video moderation are both enabled at the same time.
  201. // Avoid displaying 2 different notifications.
  202. if (mediaType === MEDIA_TYPE.AUDIO) {
  203. dispatch(showNotification({
  204. titleKey: 'notify.hostAskedUnmute',
  205. sticky: true,
  206. customActionNameKey: 'notify.unmute',
  207. customActionHandler: () => dispatch(muteLocal(false, MEDIA_TYPE.AUDIO))
  208. }));
  209. dispatch(playSound(ASKED_TO_UNMUTE_SOUND_ID));
  210. }
  211. });
  212. conference.on(JitsiConferenceEvents.AV_MODERATION_REJECTED, ({ mediaType }) => {
  213. dispatch(localParticipantRejected(mediaType));
  214. });
  215. conference.on(JitsiConferenceEvents.AV_MODERATION_CHANGED, ({ enabled, mediaType, actor }) => {
  216. enabled ? dispatch(enableModeration(mediaType, actor)) : dispatch(disableModeration(mediaType, actor));
  217. });
  218. // this is received by moderators
  219. conference.on(
  220. JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_APPROVED,
  221. ({ participant, mediaType }) => {
  222. const { _id: id } = participant;
  223. batch(() => {
  224. // store in the whitelist
  225. dispatch(participantApproved(id, mediaType));
  226. // remove from pending list
  227. dispatch(dismissPendingParticipant(id, mediaType));
  228. });
  229. });
  230. // this is received by moderators
  231. conference.on(
  232. JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_REJECTED,
  233. ({ participant, mediaType }) => {
  234. const { _id: id } = participant;
  235. dispatch(participantRejected(id, mediaType));
  236. });
  237. }
  238. });