瀏覽代碼

feat(external_api) Exposed AV Moderation to the iFrame API

Renamed event property
master
robertpin 3 年之前
父節點
當前提交
09835a672b

+ 116
- 1
modules/API/API.js 查看文件

6
     createApiEvent,
6
     createApiEvent,
7
     sendAnalytics
7
     sendAnalytics
8
 } from '../../react/features/analytics';
8
 } from '../../react/features/analytics';
9
+import {
10
+    approveParticipantAudio,
11
+    approveParticipantVideo,
12
+    rejectParticipantAudio,
13
+    rejectParticipantVideo,
14
+    requestDisableAudioModeration,
15
+    requestDisableVideoModeration,
16
+    requestEnableAudioModeration,
17
+    requestEnableVideoModeration
18
+} from '../../react/features/av-moderation/actions';
19
+import { isEnabledFromState } from '../../react/features/av-moderation/functions';
9
 import {
20
 import {
10
     getCurrentConference,
21
     getCurrentConference,
11
     sendTones,
22
     sendTones,
25
     pinParticipant,
36
     pinParticipant,
26
     kickParticipant,
37
     kickParticipant,
27
     raiseHand,
38
     raiseHand,
28
-    isParticipantModerator
39
+    isParticipantModerator,
40
+    isLocalParticipantModerator
29
 } from '../../react/features/base/participants';
41
 } from '../../react/features/base/participants';
30
 import { updateSettings } from '../../react/features/base/settings';
42
 import { updateSettings } from '../../react/features/base/settings';
31
 import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
43
 import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
50
     resizeLargeVideo
62
     resizeLargeVideo
51
 } from '../../react/features/large-video/actions.web';
63
 } from '../../react/features/large-video/actions.web';
52
 import { toggleLobbyMode } from '../../react/features/lobby/actions';
64
 import { toggleLobbyMode } from '../../react/features/lobby/actions';
65
+import { isForceMuted } from '../../react/features/participants-pane/functions';
53
 import { RECORDING_TYPES } from '../../react/features/recording/constants';
66
 import { RECORDING_TYPES } from '../../react/features/recording/constants';
54
 import { getActiveSession } from '../../react/features/recording/functions';
67
 import { getActiveSession } from '../../react/features/recording/functions';
55
 import { isScreenAudioSupported } from '../../react/features/screen-share';
68
 import { isScreenAudioSupported } from '../../react/features/screen-share';
100
  */
113
  */
101
 function initCommands() {
114
 function initCommands() {
102
     commands = {
115
     commands = {
116
+        'approve-video': participantId => {
117
+            if (!isLocalParticipantModerator(APP.store.getState())) {
118
+                return;
119
+            }
120
+
121
+            APP.store.dispatch(approveParticipantVideo(participantId));
122
+        },
123
+        'ask-to-unmute': participantId => {
124
+            if (!isLocalParticipantModerator(APP.store.getState())) {
125
+                return;
126
+            }
127
+
128
+            APP.store.dispatch(approveParticipantAudio(participantId));
129
+        },
103
         'display-name': displayName => {
130
         'display-name': displayName => {
104
             sendAnalytics(createApiEvent('display.name.changed'));
131
             sendAnalytics(createApiEvent('display.name.changed'));
105
             APP.conference.changeLocalDisplayName(displayName);
132
             APP.conference.changeLocalDisplayName(displayName);
150
         'proxy-connection-event': event => {
177
         'proxy-connection-event': event => {
151
             APP.conference.onProxyConnectionEvent(event);
178
             APP.conference.onProxyConnectionEvent(event);
152
         },
179
         },
180
+        'reject-participant': (participantId, mediaType) => {
181
+            if (!isLocalParticipantModerator(APP.store.getState())) {
182
+                return;
183
+            }
184
+
185
+            const reject = mediaType === MEDIA_TYPE.VIDEO ? rejectParticipantVideo : rejectParticipantAudio;
186
+
187
+            APP.store.dispatch(reject(participantId));
188
+        },
153
         'resize-large-video': (width, height) => {
189
         'resize-large-video': (width, height) => {
154
             logger.debug('Resize large video command received');
190
             logger.debug('Resize large video command received');
155
             sendAnalytics(createApiEvent('largevideo.resized'));
191
             sendAnalytics(createApiEvent('largevideo.resized'));
218
             sendAnalytics(createApiEvent('chat.toggled'));
254
             sendAnalytics(createApiEvent('chat.toggled'));
219
             APP.store.dispatch(toggleChat());
255
             APP.store.dispatch(toggleChat());
220
         },
256
         },
257
+        'toggle-moderation': (enabled, mediaType) => {
258
+            const state = APP.store.getState();
259
+
260
+            if (!isLocalParticipantModerator(state)) {
261
+                return;
262
+            }
263
+
264
+            const enable = mediaType === MEDIA_TYPE.VIDEO
265
+                ? requestEnableVideoModeration : requestEnableAudioModeration;
266
+            const disable = mediaType === MEDIA_TYPE.VIDEO
267
+                ? requestDisableVideoModeration : requestDisableAudioModeration;
268
+
269
+            if (enabled) {
270
+                APP.store.dispatch(enable());
271
+            } else {
272
+                APP.store.dispatch(disable());
273
+            }
274
+        },
221
         'toggle-raise-hand': () => {
275
         'toggle-raise-hand': () => {
222
             const localParticipant = getLocalParticipant(APP.store.getState());
276
             const localParticipant = getLocalParticipant(APP.store.getState());
223
 
277
 
541
         case 'is-audio-muted':
595
         case 'is-audio-muted':
542
             callback(APP.conference.isLocalAudioMuted());
596
             callback(APP.conference.isLocalAudioMuted());
543
             break;
597
             break;
598
+        case 'is-moderation-on': {
599
+            const { mediaType } = request;
600
+            const type = mediaType || MEDIA_TYPE.AUDIO;
601
+
602
+            callback(isEnabledFromState(type, APP.store.getState()));
603
+            break;
604
+        }
605
+        case 'is-participant-force-muted': {
606
+            const state = APP.store.getState();
607
+            const { participantId, mediaType } = request;
608
+            const type = mediaType || MEDIA_TYPE.AUDIO;
609
+            const participant = getParticipantById(state, participantId);
610
+
611
+            callback(isForceMuted(participant, type, state));
612
+            break;
613
+        }
544
         case 'is-video-muted':
614
         case 'is-video-muted':
545
             callback(APP.conference.isLocalVideoMuted());
615
             callback(APP.conference.isLocalVideoMuted());
546
             break;
616
             break;
806
         });
876
         });
807
     }
877
     }
808
 
878
 
879
+    /**
880
+     * Notify the external application that the moderation status has changed.
881
+     *
882
+     * @param {string} mediaType - Media type for which the moderation changed.
883
+     * @param {boolean} enabled - Whether or not the new moderation status is enabled.
884
+     * @returns {void}
885
+     */
886
+    notifyModerationChanged(mediaType: string, enabled: boolean) {
887
+        this._sendEvent({
888
+            name: 'moderation-status-changed',
889
+            mediaType,
890
+            enabled
891
+        });
892
+    }
893
+
894
+    /**
895
+     * Notify the external application that a participant was approved on moderation.
896
+     *
897
+     * @param {string} participantId - The ID of the participant that got approved.
898
+     * @param {string} mediaType - Media type for which the participant was approved.
899
+     * @returns {void}
900
+     */
901
+    notifyParticipantApproved(participantId: string, mediaType: string) {
902
+        this._sendEvent({
903
+            name: 'moderation-participant-approved',
904
+            id: participantId,
905
+            mediaType
906
+        });
907
+    }
908
+
909
+    /**
910
+     * Notify the external application that a participant was rejected on moderation.
911
+     *
912
+     * @param {string} participantId - The ID of the participant that got rejected.
913
+     * @param {string} mediaType - Media type for which the participant was rejected.
914
+     * @returns {void}
915
+     */
916
+    notifyParticipantRejected(participantId: string, mediaType: string) {
917
+        this._sendEvent({
918
+            name: 'moderation-participant-rejected',
919
+            id: participantId,
920
+            mediaType
921
+        });
922
+    }
923
+
809
     /**
924
     /**
810
      * Notify external application that the video quality setting has changed.
925
      * Notify external application that the video quality setting has changed.
811
      *
926
      *

+ 37
- 0
modules/API/external/external_api.js 查看文件

27
  * commands expected by jitsi-meet
27
  * commands expected by jitsi-meet
28
  */
28
  */
29
 const commands = {
29
 const commands = {
30
+    approveVideo: 'approve-video',
31
+    askToUnmute: 'ask-to-unmute',
30
     avatarUrl: 'avatar-url',
32
     avatarUrl: 'avatar-url',
31
     cancelPrivateChat: 'cancel-private-chat',
33
     cancelPrivateChat: 'cancel-private-chat',
32
     displayName: 'display-name',
34
     displayName: 'display-name',
40
     overwriteConfig: 'overwrite-config',
42
     overwriteConfig: 'overwrite-config',
41
     password: 'password',
43
     password: 'password',
42
     pinParticipant: 'pin-participant',
44
     pinParticipant: 'pin-participant',
45
+    rejectParticipant: 'reject-participant',
43
     resizeLargeVideo: 'resize-large-video',
46
     resizeLargeVideo: 'resize-large-video',
44
     sendChatMessage: 'send-chat-message',
47
     sendChatMessage: 'send-chat-message',
45
     sendEndpointTextMessage: 'send-endpoint-text-message',
48
     sendEndpointTextMessage: 'send-endpoint-text-message',
60
     toggleCameraMirror: 'toggle-camera-mirror',
63
     toggleCameraMirror: 'toggle-camera-mirror',
61
     toggleChat: 'toggle-chat',
64
     toggleChat: 'toggle-chat',
62
     toggleFilmStrip: 'toggle-film-strip',
65
     toggleFilmStrip: 'toggle-film-strip',
66
+    toggleModeration: 'toggle-moderation',
63
     toggleRaiseHand: 'toggle-raise-hand',
67
     toggleRaiseHand: 'toggle-raise-hand',
64
     toggleShareAudio: 'toggle-share-audio',
68
     toggleShareAudio: 'toggle-share-audio',
65
     toggleShareScreen: 'toggle-share-screen',
69
     toggleShareScreen: 'toggle-share-screen',
92
     'incoming-message': 'incomingMessage',
96
     'incoming-message': 'incomingMessage',
93
     'log': 'log',
97
     'log': 'log',
94
     'mic-error': 'micError',
98
     'mic-error': 'micError',
99
+    'moderation-participant-approved': 'moderationParticipantApproved',
100
+    'moderation-participant-rejected': 'moderationParticipantRejected',
101
+    'moderation-status-changed': 'moderationStatusChanged',
95
     'mouse-enter': 'mouseEnter',
102
     'mouse-enter': 'mouseEnter',
96
     'mouse-leave': 'mouseLeave',
103
     'mouse-leave': 'mouseLeave',
97
     'mouse-move': 'mouseMove',
104
     'mouse-move': 'mouseMove',
896
         });
903
         });
897
     }
904
     }
898
 
905
 
906
+    /**
907
+     * Returns the moderation on status on the given mediaType.
908
+     *
909
+     * @param {string} mediaType - The media type for which to check moderation.
910
+     * @returns {Promise} - Resolves with the moderation on status and rejects on
911
+     * failure.
912
+     */
913
+    isModerationOn(mediaType) {
914
+        return this._transport.sendRequest({
915
+            name: 'is-moderation-on',
916
+            mediaType
917
+        });
918
+    }
919
+
920
+    /**
921
+     * Returns force muted status of the given participant id for the given media type.
922
+     *
923
+     * @param {string} participantId - The id of the participant to check.
924
+     * @param {string} mediaType - The media type for which to check.
925
+     * @returns {Promise} - Resolves with the force muted status and rejects on
926
+     * failure.
927
+     */
928
+    isParticipantForceMuted(participantId, mediaType) {
929
+        return this._transport.sendRequest({
930
+            name: 'is-participant-force-muted',
931
+            participantId,
932
+            mediaType
933
+        });
934
+    }
935
+
899
     /**
936
     /**
900
      * Returns screen sharing status.
937
      * Returns screen sharing status.
901
      *
938
      *

+ 29
- 4
react/features/av-moderation/actions.js 查看文件

23
 import { isEnabledFromState } from './functions';
23
 import { isEnabledFromState } from './functions';
24
 
24
 
25
 /**
25
 /**
26
- * Action used by moderator to approve audio and video for a participant.
26
+ * Action used by moderator to approve audio for a participant.
27
  *
27
  *
28
  * @param {staring} id - The id of the participant to be approved.
28
  * @param {staring} id - The id of the participant to be approved.
29
  * @returns {void}
29
  * @returns {void}
30
  */
30
  */
31
-export const approveParticipant = (id: string) => (dispatch: Function, getState: Function) => {
31
+export const approveParticipantAudio = (id: string) => (dispatch: Function, getState: Function) => {
32
     const state = getState();
32
     const state = getState();
33
     const { conference } = getConferenceState(state);
33
     const { conference } = getConferenceState(state);
34
-    const participant = getParticipantById(state, id);
35
 
34
 
36
-    const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
37
     const isAudioModerationOn = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
35
     const isAudioModerationOn = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
38
     const isVideoModerationOn = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
36
     const isVideoModerationOn = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
39
 
37
 
40
     if (isAudioModerationOn || !isVideoModerationOn) {
38
     if (isAudioModerationOn || !isVideoModerationOn) {
41
         conference.avModerationApprove(MEDIA_TYPE.AUDIO, id);
39
         conference.avModerationApprove(MEDIA_TYPE.AUDIO, id);
42
     }
40
     }
41
+};
42
+
43
+/**
44
+ * Action used by moderator to approve video for a participant.
45
+ *
46
+ * @param {staring} id - The id of the participant to be approved.
47
+ * @returns {void}
48
+ */
49
+export const approveParticipantVideo = (id: string) => (dispatch: Function, getState: Function) => {
50
+    const state = getState();
51
+    const { conference } = getConferenceState(state);
52
+    const participant = getParticipantById(state, id);
53
+
54
+    const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
55
+    const isVideoModerationOn = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
56
+
43
     if (isVideoModerationOn && isVideoForceMuted) {
57
     if (isVideoModerationOn && isVideoForceMuted) {
44
         conference.avModerationApprove(MEDIA_TYPE.VIDEO, id);
58
         conference.avModerationApprove(MEDIA_TYPE.VIDEO, id);
45
     }
59
     }
46
 };
60
 };
47
 
61
 
62
+/**
63
+ * Action used by moderator to approve audio and video for a participant.
64
+ *
65
+ * @param {staring} id - The id of the participant to be approved.
66
+ * @returns {void}
67
+ */
68
+export const approveParticipant = (id: string) => (dispatch: Function) => {
69
+    dispatch(approveParticipantAudio(id));
70
+    dispatch(approveParticipantVideo(id));
71
+};
72
+
48
 /**
73
 /**
49
  * Action used by moderator to reject audio for a participant.
74
  * Action used by moderator to reject audio for a participant.
50
  *
75
  *

+ 48
- 0
react/features/av-moderation/middleware.js 查看文件

22
 import { muteLocal } from '../video-menu/actions.any';
22
 import { muteLocal } from '../video-menu/actions.any';
23
 
23
 
24
 import {
24
 import {
25
+    DISABLE_MODERATION,
26
+    ENABLE_MODERATION,
27
+    LOCAL_PARTICIPANT_APPROVED,
25
     LOCAL_PARTICIPANT_MODERATION_NOTIFICATION,
28
     LOCAL_PARTICIPANT_MODERATION_NOTIFICATION,
29
+    LOCAL_PARTICIPANT_REJECTED,
30
+    PARTICIPANT_APPROVED,
31
+    PARTICIPANT_REJECTED,
26
     REQUEST_DISABLE_AUDIO_MODERATION,
32
     REQUEST_DISABLE_AUDIO_MODERATION,
27
     REQUEST_DISABLE_VIDEO_MODERATION,
33
     REQUEST_DISABLE_VIDEO_MODERATION,
28
     REQUEST_ENABLE_AUDIO_MODERATION,
34
     REQUEST_ENABLE_AUDIO_MODERATION,
51
 } from './functions';
57
 } from './functions';
52
 import { ASKED_TO_UNMUTE_FILE } from './sounds';
58
 import { ASKED_TO_UNMUTE_FILE } from './sounds';
53
 
59
 
60
+declare var APP: Object;
61
+
54
 MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
62
 MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
55
     const { type } = action;
63
     const { type } = action;
56
     const { conference } = getConferenceState(getState());
64
     const { conference } = getConferenceState(getState());
148
 
156
 
149
         break;
157
         break;
150
     }
158
     }
159
+    case ENABLE_MODERATION: {
160
+        if (typeof APP !== 'undefined') {
161
+            APP.API.notifyModerationChanged(action.mediaType, true);
162
+        }
163
+        break;
164
+    }
165
+    case DISABLE_MODERATION: {
166
+        if (typeof APP !== 'undefined') {
167
+            APP.API.notifyModerationChanged(action.mediaType, false);
168
+        }
169
+        break;
170
+    }
171
+    case LOCAL_PARTICIPANT_APPROVED: {
172
+        if (typeof APP !== 'undefined') {
173
+            const local = getLocalParticipant(getState());
174
+
175
+            APP.API.notifyParticipantApproved(local.id, action.mediaType);
176
+        }
177
+        break;
178
+    }
179
+    case PARTICIPANT_APPROVED: {
180
+        if (typeof APP !== 'undefined') {
181
+            APP.API.notifyParticipantApproved(action.id, action.mediaType);
182
+        }
183
+        break;
184
+    }
185
+    case LOCAL_PARTICIPANT_REJECTED: {
186
+        if (typeof APP !== 'undefined') {
187
+            const local = getLocalParticipant(getState());
188
+
189
+            APP.API.notifyParticipantRejected(local.id, action.mediaType);
190
+        }
191
+        break;
192
+    }
193
+    case PARTICIPANT_REJECTED: {
194
+        if (typeof APP !== 'undefined') {
195
+            APP.API.notifyParticipantRejected(action.id, action.mediaType);
196
+        }
197
+        break;
198
+    }
151
     }
199
     }
152
 
200
 
153
     return next(action);
201
     return next(action);

Loading…
取消
儲存