feat(external_api) Exposed AV Moderation to the iFrame API
Renamed event property
This commit is contained in:
parent
e4448e0a68
commit
09835a672b
|
@ -6,6 +6,17 @@ import {
|
||||||
createApiEvent,
|
createApiEvent,
|
||||||
sendAnalytics
|
sendAnalytics
|
||||||
} from '../../react/features/analytics';
|
} from '../../react/features/analytics';
|
||||||
|
import {
|
||||||
|
approveParticipantAudio,
|
||||||
|
approveParticipantVideo,
|
||||||
|
rejectParticipantAudio,
|
||||||
|
rejectParticipantVideo,
|
||||||
|
requestDisableAudioModeration,
|
||||||
|
requestDisableVideoModeration,
|
||||||
|
requestEnableAudioModeration,
|
||||||
|
requestEnableVideoModeration
|
||||||
|
} from '../../react/features/av-moderation/actions';
|
||||||
|
import { isEnabledFromState } from '../../react/features/av-moderation/functions';
|
||||||
import {
|
import {
|
||||||
getCurrentConference,
|
getCurrentConference,
|
||||||
sendTones,
|
sendTones,
|
||||||
|
@ -25,7 +36,8 @@ import {
|
||||||
pinParticipant,
|
pinParticipant,
|
||||||
kickParticipant,
|
kickParticipant,
|
||||||
raiseHand,
|
raiseHand,
|
||||||
isParticipantModerator
|
isParticipantModerator,
|
||||||
|
isLocalParticipantModerator
|
||||||
} from '../../react/features/base/participants';
|
} from '../../react/features/base/participants';
|
||||||
import { updateSettings } from '../../react/features/base/settings';
|
import { updateSettings } from '../../react/features/base/settings';
|
||||||
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
|
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
|
||||||
|
@ -50,6 +62,7 @@ import {
|
||||||
resizeLargeVideo
|
resizeLargeVideo
|
||||||
} from '../../react/features/large-video/actions.web';
|
} from '../../react/features/large-video/actions.web';
|
||||||
import { toggleLobbyMode } from '../../react/features/lobby/actions';
|
import { toggleLobbyMode } from '../../react/features/lobby/actions';
|
||||||
|
import { isForceMuted } from '../../react/features/participants-pane/functions';
|
||||||
import { RECORDING_TYPES } from '../../react/features/recording/constants';
|
import { RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||||
import { getActiveSession } from '../../react/features/recording/functions';
|
import { getActiveSession } from '../../react/features/recording/functions';
|
||||||
import { isScreenAudioSupported } from '../../react/features/screen-share';
|
import { isScreenAudioSupported } from '../../react/features/screen-share';
|
||||||
|
@ -100,6 +113,20 @@ let videoAvailable = true;
|
||||||
*/
|
*/
|
||||||
function initCommands() {
|
function initCommands() {
|
||||||
commands = {
|
commands = {
|
||||||
|
'approve-video': participantId => {
|
||||||
|
if (!isLocalParticipantModerator(APP.store.getState())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP.store.dispatch(approveParticipantVideo(participantId));
|
||||||
|
},
|
||||||
|
'ask-to-unmute': participantId => {
|
||||||
|
if (!isLocalParticipantModerator(APP.store.getState())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP.store.dispatch(approveParticipantAudio(participantId));
|
||||||
|
},
|
||||||
'display-name': displayName => {
|
'display-name': displayName => {
|
||||||
sendAnalytics(createApiEvent('display.name.changed'));
|
sendAnalytics(createApiEvent('display.name.changed'));
|
||||||
APP.conference.changeLocalDisplayName(displayName);
|
APP.conference.changeLocalDisplayName(displayName);
|
||||||
|
@ -150,6 +177,15 @@ function initCommands() {
|
||||||
'proxy-connection-event': event => {
|
'proxy-connection-event': event => {
|
||||||
APP.conference.onProxyConnectionEvent(event);
|
APP.conference.onProxyConnectionEvent(event);
|
||||||
},
|
},
|
||||||
|
'reject-participant': (participantId, mediaType) => {
|
||||||
|
if (!isLocalParticipantModerator(APP.store.getState())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reject = mediaType === MEDIA_TYPE.VIDEO ? rejectParticipantVideo : rejectParticipantAudio;
|
||||||
|
|
||||||
|
APP.store.dispatch(reject(participantId));
|
||||||
|
},
|
||||||
'resize-large-video': (width, height) => {
|
'resize-large-video': (width, height) => {
|
||||||
logger.debug('Resize large video command received');
|
logger.debug('Resize large video command received');
|
||||||
sendAnalytics(createApiEvent('largevideo.resized'));
|
sendAnalytics(createApiEvent('largevideo.resized'));
|
||||||
|
@ -218,6 +254,24 @@ function initCommands() {
|
||||||
sendAnalytics(createApiEvent('chat.toggled'));
|
sendAnalytics(createApiEvent('chat.toggled'));
|
||||||
APP.store.dispatch(toggleChat());
|
APP.store.dispatch(toggleChat());
|
||||||
},
|
},
|
||||||
|
'toggle-moderation': (enabled, mediaType) => {
|
||||||
|
const state = APP.store.getState();
|
||||||
|
|
||||||
|
if (!isLocalParticipantModerator(state)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enable = mediaType === MEDIA_TYPE.VIDEO
|
||||||
|
? requestEnableVideoModeration : requestEnableAudioModeration;
|
||||||
|
const disable = mediaType === MEDIA_TYPE.VIDEO
|
||||||
|
? requestDisableVideoModeration : requestDisableAudioModeration;
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
APP.store.dispatch(enable());
|
||||||
|
} else {
|
||||||
|
APP.store.dispatch(disable());
|
||||||
|
}
|
||||||
|
},
|
||||||
'toggle-raise-hand': () => {
|
'toggle-raise-hand': () => {
|
||||||
const localParticipant = getLocalParticipant(APP.store.getState());
|
const localParticipant = getLocalParticipant(APP.store.getState());
|
||||||
|
|
||||||
|
@ -541,6 +595,22 @@ function initCommands() {
|
||||||
case 'is-audio-muted':
|
case 'is-audio-muted':
|
||||||
callback(APP.conference.isLocalAudioMuted());
|
callback(APP.conference.isLocalAudioMuted());
|
||||||
break;
|
break;
|
||||||
|
case 'is-moderation-on': {
|
||||||
|
const { mediaType } = request;
|
||||||
|
const type = mediaType || MEDIA_TYPE.AUDIO;
|
||||||
|
|
||||||
|
callback(isEnabledFromState(type, APP.store.getState()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'is-participant-force-muted': {
|
||||||
|
const state = APP.store.getState();
|
||||||
|
const { participantId, mediaType } = request;
|
||||||
|
const type = mediaType || MEDIA_TYPE.AUDIO;
|
||||||
|
const participant = getParticipantById(state, participantId);
|
||||||
|
|
||||||
|
callback(isForceMuted(participant, type, state));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'is-video-muted':
|
case 'is-video-muted':
|
||||||
callback(APP.conference.isLocalVideoMuted());
|
callback(APP.conference.isLocalVideoMuted());
|
||||||
break;
|
break;
|
||||||
|
@ -806,6 +876,51 @@ class API {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the external application that the moderation status has changed.
|
||||||
|
*
|
||||||
|
* @param {string} mediaType - Media type for which the moderation changed.
|
||||||
|
* @param {boolean} enabled - Whether or not the new moderation status is enabled.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
notifyModerationChanged(mediaType: string, enabled: boolean) {
|
||||||
|
this._sendEvent({
|
||||||
|
name: 'moderation-status-changed',
|
||||||
|
mediaType,
|
||||||
|
enabled
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the external application that a participant was approved on moderation.
|
||||||
|
*
|
||||||
|
* @param {string} participantId - The ID of the participant that got approved.
|
||||||
|
* @param {string} mediaType - Media type for which the participant was approved.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
notifyParticipantApproved(participantId: string, mediaType: string) {
|
||||||
|
this._sendEvent({
|
||||||
|
name: 'moderation-participant-approved',
|
||||||
|
id: participantId,
|
||||||
|
mediaType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the external application that a participant was rejected on moderation.
|
||||||
|
*
|
||||||
|
* @param {string} participantId - The ID of the participant that got rejected.
|
||||||
|
* @param {string} mediaType - Media type for which the participant was rejected.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
notifyParticipantRejected(participantId: string, mediaType: string) {
|
||||||
|
this._sendEvent({
|
||||||
|
name: 'moderation-participant-rejected',
|
||||||
|
id: participantId,
|
||||||
|
mediaType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify external application that the video quality setting has changed.
|
* Notify external application that the video quality setting has changed.
|
||||||
*
|
*
|
||||||
|
|
|
@ -27,6 +27,8 @@ const ALWAYS_ON_TOP_FILENAMES = [
|
||||||
* commands expected by jitsi-meet
|
* commands expected by jitsi-meet
|
||||||
*/
|
*/
|
||||||
const commands = {
|
const commands = {
|
||||||
|
approveVideo: 'approve-video',
|
||||||
|
askToUnmute: 'ask-to-unmute',
|
||||||
avatarUrl: 'avatar-url',
|
avatarUrl: 'avatar-url',
|
||||||
cancelPrivateChat: 'cancel-private-chat',
|
cancelPrivateChat: 'cancel-private-chat',
|
||||||
displayName: 'display-name',
|
displayName: 'display-name',
|
||||||
|
@ -40,6 +42,7 @@ const commands = {
|
||||||
overwriteConfig: 'overwrite-config',
|
overwriteConfig: 'overwrite-config',
|
||||||
password: 'password',
|
password: 'password',
|
||||||
pinParticipant: 'pin-participant',
|
pinParticipant: 'pin-participant',
|
||||||
|
rejectParticipant: 'reject-participant',
|
||||||
resizeLargeVideo: 'resize-large-video',
|
resizeLargeVideo: 'resize-large-video',
|
||||||
sendChatMessage: 'send-chat-message',
|
sendChatMessage: 'send-chat-message',
|
||||||
sendEndpointTextMessage: 'send-endpoint-text-message',
|
sendEndpointTextMessage: 'send-endpoint-text-message',
|
||||||
|
@ -60,6 +63,7 @@ const commands = {
|
||||||
toggleCameraMirror: 'toggle-camera-mirror',
|
toggleCameraMirror: 'toggle-camera-mirror',
|
||||||
toggleChat: 'toggle-chat',
|
toggleChat: 'toggle-chat',
|
||||||
toggleFilmStrip: 'toggle-film-strip',
|
toggleFilmStrip: 'toggle-film-strip',
|
||||||
|
toggleModeration: 'toggle-moderation',
|
||||||
toggleRaiseHand: 'toggle-raise-hand',
|
toggleRaiseHand: 'toggle-raise-hand',
|
||||||
toggleShareAudio: 'toggle-share-audio',
|
toggleShareAudio: 'toggle-share-audio',
|
||||||
toggleShareScreen: 'toggle-share-screen',
|
toggleShareScreen: 'toggle-share-screen',
|
||||||
|
@ -92,6 +96,9 @@ const events = {
|
||||||
'incoming-message': 'incomingMessage',
|
'incoming-message': 'incomingMessage',
|
||||||
'log': 'log',
|
'log': 'log',
|
||||||
'mic-error': 'micError',
|
'mic-error': 'micError',
|
||||||
|
'moderation-participant-approved': 'moderationParticipantApproved',
|
||||||
|
'moderation-participant-rejected': 'moderationParticipantRejected',
|
||||||
|
'moderation-status-changed': 'moderationStatusChanged',
|
||||||
'mouse-enter': 'mouseEnter',
|
'mouse-enter': 'mouseEnter',
|
||||||
'mouse-leave': 'mouseLeave',
|
'mouse-leave': 'mouseLeave',
|
||||||
'mouse-move': 'mouseMove',
|
'mouse-move': 'mouseMove',
|
||||||
|
@ -896,6 +903,36 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the moderation on status on the given mediaType.
|
||||||
|
*
|
||||||
|
* @param {string} mediaType - The media type for which to check moderation.
|
||||||
|
* @returns {Promise} - Resolves with the moderation on status and rejects on
|
||||||
|
* failure.
|
||||||
|
*/
|
||||||
|
isModerationOn(mediaType) {
|
||||||
|
return this._transport.sendRequest({
|
||||||
|
name: 'is-moderation-on',
|
||||||
|
mediaType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns force muted status of the given participant id for the given media type.
|
||||||
|
*
|
||||||
|
* @param {string} participantId - The id of the participant to check.
|
||||||
|
* @param {string} mediaType - The media type for which to check.
|
||||||
|
* @returns {Promise} - Resolves with the force muted status and rejects on
|
||||||
|
* failure.
|
||||||
|
*/
|
||||||
|
isParticipantForceMuted(participantId, mediaType) {
|
||||||
|
return this._transport.sendRequest({
|
||||||
|
name: 'is-participant-force-muted',
|
||||||
|
participantId,
|
||||||
|
mediaType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns screen sharing status.
|
* Returns screen sharing status.
|
||||||
*
|
*
|
||||||
|
|
|
@ -23,28 +23,53 @@ import {
|
||||||
import { isEnabledFromState } from './functions';
|
import { isEnabledFromState } from './functions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action used by moderator to approve audio and video for a participant.
|
* Action used by moderator to approve audio for a participant.
|
||||||
*
|
*
|
||||||
* @param {staring} id - The id of the participant to be approved.
|
* @param {staring} id - The id of the participant to be approved.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
export const approveParticipant = (id: string) => (dispatch: Function, getState: Function) => {
|
export const approveParticipantAudio = (id: string) => (dispatch: Function, getState: Function) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { conference } = getConferenceState(state);
|
const { conference } = getConferenceState(state);
|
||||||
const participant = getParticipantById(state, id);
|
|
||||||
|
|
||||||
const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
|
|
||||||
const isAudioModerationOn = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
|
const isAudioModerationOn = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
|
||||||
const isVideoModerationOn = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
|
const isVideoModerationOn = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
|
||||||
|
|
||||||
if (isAudioModerationOn || !isVideoModerationOn) {
|
if (isAudioModerationOn || !isVideoModerationOn) {
|
||||||
conference.avModerationApprove(MEDIA_TYPE.AUDIO, id);
|
conference.avModerationApprove(MEDIA_TYPE.AUDIO, id);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action used by moderator to approve video for a participant.
|
||||||
|
*
|
||||||
|
* @param {staring} id - The id of the participant to be approved.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export const approveParticipantVideo = (id: string) => (dispatch: Function, getState: Function) => {
|
||||||
|
const state = getState();
|
||||||
|
const { conference } = getConferenceState(state);
|
||||||
|
const participant = getParticipantById(state, id);
|
||||||
|
|
||||||
|
const isVideoForceMuted = isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
|
||||||
|
const isVideoModerationOn = isEnabledFromState(MEDIA_TYPE.VIDEO, state);
|
||||||
|
|
||||||
if (isVideoModerationOn && isVideoForceMuted) {
|
if (isVideoModerationOn && isVideoForceMuted) {
|
||||||
conference.avModerationApprove(MEDIA_TYPE.VIDEO, id);
|
conference.avModerationApprove(MEDIA_TYPE.VIDEO, id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action used by moderator to approve audio and video for a participant.
|
||||||
|
*
|
||||||
|
* @param {staring} id - The id of the participant to be approved.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
export const approveParticipant = (id: string) => (dispatch: Function) => {
|
||||||
|
dispatch(approveParticipantAudio(id));
|
||||||
|
dispatch(approveParticipantVideo(id));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action used by moderator to reject audio for a participant.
|
* Action used by moderator to reject audio for a participant.
|
||||||
*
|
*
|
||||||
|
|
|
@ -22,7 +22,13 @@ import {
|
||||||
import { muteLocal } from '../video-menu/actions.any';
|
import { muteLocal } from '../video-menu/actions.any';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DISABLE_MODERATION,
|
||||||
|
ENABLE_MODERATION,
|
||||||
|
LOCAL_PARTICIPANT_APPROVED,
|
||||||
LOCAL_PARTICIPANT_MODERATION_NOTIFICATION,
|
LOCAL_PARTICIPANT_MODERATION_NOTIFICATION,
|
||||||
|
LOCAL_PARTICIPANT_REJECTED,
|
||||||
|
PARTICIPANT_APPROVED,
|
||||||
|
PARTICIPANT_REJECTED,
|
||||||
REQUEST_DISABLE_AUDIO_MODERATION,
|
REQUEST_DISABLE_AUDIO_MODERATION,
|
||||||
REQUEST_DISABLE_VIDEO_MODERATION,
|
REQUEST_DISABLE_VIDEO_MODERATION,
|
||||||
REQUEST_ENABLE_AUDIO_MODERATION,
|
REQUEST_ENABLE_AUDIO_MODERATION,
|
||||||
|
@ -51,6 +57,8 @@ import {
|
||||||
} from './functions';
|
} from './functions';
|
||||||
import { ASKED_TO_UNMUTE_FILE } from './sounds';
|
import { ASKED_TO_UNMUTE_FILE } from './sounds';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||||
const { type } = action;
|
const { type } = action;
|
||||||
const { conference } = getConferenceState(getState());
|
const { conference } = getConferenceState(getState());
|
||||||
|
@ -148,6 +156,46 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ENABLE_MODERATION: {
|
||||||
|
if (typeof APP !== 'undefined') {
|
||||||
|
APP.API.notifyModerationChanged(action.mediaType, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DISABLE_MODERATION: {
|
||||||
|
if (typeof APP !== 'undefined') {
|
||||||
|
APP.API.notifyModerationChanged(action.mediaType, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LOCAL_PARTICIPANT_APPROVED: {
|
||||||
|
if (typeof APP !== 'undefined') {
|
||||||
|
const local = getLocalParticipant(getState());
|
||||||
|
|
||||||
|
APP.API.notifyParticipantApproved(local.id, action.mediaType);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PARTICIPANT_APPROVED: {
|
||||||
|
if (typeof APP !== 'undefined') {
|
||||||
|
APP.API.notifyParticipantApproved(action.id, action.mediaType);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LOCAL_PARTICIPANT_REJECTED: {
|
||||||
|
if (typeof APP !== 'undefined') {
|
||||||
|
const local = getLocalParticipant(getState());
|
||||||
|
|
||||||
|
APP.API.notifyParticipantRejected(local.id, action.mediaType);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PARTICIPANT_REJECTED: {
|
||||||
|
if (typeof APP !== 'undefined') {
|
||||||
|
APP.API.notifyParticipantRejected(action.id, action.mediaType);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(action);
|
return next(action);
|
||||||
|
|
Loading…
Reference in New Issue