2021-06-23 11:23:44 +00:00
|
|
|
// @flow
|
|
|
|
import { batch } from 'react-redux';
|
|
|
|
|
|
|
|
import { getConferenceState } from '../base/conference';
|
|
|
|
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
|
|
|
import { MEDIA_TYPE } from '../base/media';
|
|
|
|
import {
|
|
|
|
getParticipantDisplayName,
|
|
|
|
isLocalParticipantModerator,
|
|
|
|
PARTICIPANT_UPDATED,
|
|
|
|
raiseHand
|
|
|
|
} from '../base/participants';
|
|
|
|
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
|
|
|
import {
|
|
|
|
hideNotification,
|
|
|
|
NOTIFICATION_TIMEOUT,
|
|
|
|
showNotification
|
|
|
|
} from '../notifications';
|
|
|
|
|
|
|
|
import {
|
|
|
|
DISABLE_MODERATION,
|
|
|
|
ENABLE_MODERATION,
|
|
|
|
LOCAL_PARTICIPANT_MODERATION_NOTIFICATION,
|
|
|
|
REQUEST_DISABLE_MODERATION,
|
|
|
|
REQUEST_ENABLE_MODERATION
|
|
|
|
} from './actionTypes';
|
|
|
|
import {
|
|
|
|
disableModeration,
|
|
|
|
dismissPendingParticipant,
|
|
|
|
dismissPendingAudioParticipant,
|
|
|
|
enableModeration,
|
|
|
|
localParticipantApproved,
|
|
|
|
participantApproved,
|
|
|
|
participantPendingAudio
|
|
|
|
} from './actions';
|
|
|
|
import {
|
|
|
|
isEnabledFromState,
|
|
|
|
isParticipantApproved,
|
|
|
|
isParticipantPending
|
|
|
|
} from './functions';
|
|
|
|
|
|
|
|
const VIDEO_MODERATION_NOTIFICATION_ID = 'video-moderation';
|
|
|
|
const AUDIO_MODERATION_NOTIFICATION_ID = 'audio-moderation';
|
|
|
|
const CS_MODERATION_NOTIFICATION_ID = 'video-moderation';
|
|
|
|
|
|
|
|
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
|
|
|
const { actor, mediaType, type } = action;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case DISABLE_MODERATION:
|
|
|
|
case ENABLE_MODERATION: {
|
|
|
|
// Audio & video moderation are both enabled at the same time.
|
|
|
|
// Avoid displaying 2 different notifications.
|
|
|
|
if (mediaType === MEDIA_TYPE.VIDEO) {
|
|
|
|
const titleKey = type === ENABLE_MODERATION
|
|
|
|
? 'notify.moderationStartedTitle'
|
|
|
|
: 'notify.moderationStoppedTitle';
|
|
|
|
|
|
|
|
dispatch(showNotification({
|
|
|
|
descriptionKey: actor ? 'notify.moderationToggleDescription' : undefined,
|
|
|
|
descriptionArguments: actor ? {
|
|
|
|
participantDisplayName: getParticipantDisplayName(getState, actor.getId())
|
|
|
|
} : undefined,
|
|
|
|
titleKey
|
|
|
|
}, NOTIFICATION_TIMEOUT));
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case LOCAL_PARTICIPANT_MODERATION_NOTIFICATION: {
|
|
|
|
let descriptionKey;
|
|
|
|
let titleKey;
|
|
|
|
let uid;
|
|
|
|
|
|
|
|
switch (action.mediaType) {
|
|
|
|
case MEDIA_TYPE.AUDIO: {
|
|
|
|
titleKey = 'notify.moderationInEffectTitle';
|
|
|
|
descriptionKey = 'notify.moderationInEffectDescription';
|
|
|
|
uid = AUDIO_MODERATION_NOTIFICATION_ID;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MEDIA_TYPE.VIDEO: {
|
|
|
|
titleKey = 'notify.moderationInEffectVideoTitle';
|
|
|
|
descriptionKey = 'notify.moderationInEffectVideoDescription';
|
|
|
|
uid = VIDEO_MODERATION_NOTIFICATION_ID;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MEDIA_TYPE.PRESENTER: {
|
|
|
|
titleKey = 'notify.moderationInEffectCSTitle';
|
|
|
|
descriptionKey = 'notify.moderationInEffectCSDescription';
|
|
|
|
uid = CS_MODERATION_NOTIFICATION_ID;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch(showNotification({
|
|
|
|
customActionNameKey: 'notify.raiseHandAction',
|
|
|
|
customActionHandler: () => batch(() => {
|
|
|
|
dispatch(raiseHand(true));
|
|
|
|
dispatch(hideNotification(uid));
|
|
|
|
}),
|
|
|
|
descriptionKey,
|
|
|
|
sticky: true,
|
|
|
|
titleKey,
|
|
|
|
uid
|
|
|
|
}));
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case REQUEST_DISABLE_MODERATION: {
|
|
|
|
const { conference } = getConferenceState(getState());
|
|
|
|
|
|
|
|
conference.disableAVModeration(MEDIA_TYPE.AUDIO);
|
|
|
|
conference.disableAVModeration(MEDIA_TYPE.VIDEO);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case REQUEST_ENABLE_MODERATION: {
|
|
|
|
const { conference } = getConferenceState(getState());
|
|
|
|
|
|
|
|
conference.enableAVModeration(MEDIA_TYPE.AUDIO);
|
|
|
|
conference.enableAVModeration(MEDIA_TYPE.VIDEO);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case PARTICIPANT_UPDATED: {
|
|
|
|
const state = getState();
|
|
|
|
const audioModerationEnabled = isEnabledFromState(MEDIA_TYPE.AUDIO, state);
|
|
|
|
|
|
|
|
// this is handled only by moderators
|
|
|
|
if (audioModerationEnabled && isLocalParticipantModerator(state)) {
|
2021-07-09 12:36:19 +00:00
|
|
|
const participant = action.participant;
|
2021-06-23 11:23:44 +00:00
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
if (participant.raisedHand) {
|
2021-06-23 11:23:44 +00:00
|
|
|
// if participant raises hand show notification
|
2021-07-09 12:36:19 +00:00
|
|
|
!isParticipantApproved(participant.id, MEDIA_TYPE.AUDIO)(state)
|
|
|
|
&& dispatch(participantPendingAudio(participant));
|
2021-06-23 11:23:44 +00:00
|
|
|
} else {
|
|
|
|
// if participant lowers hand hide notification
|
2021-07-09 12:36:19 +00:00
|
|
|
isParticipantPending(participant, MEDIA_TYPE.AUDIO)(state)
|
|
|
|
&& dispatch(dismissPendingAudioParticipant(participant));
|
2021-06-23 11:23:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return next(action);
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a change handler for state['features/base/conference'].conference to
|
|
|
|
* set the event listeners needed for the A/V moderation feature to operate.
|
|
|
|
*/
|
|
|
|
StateListenerRegistry.register(
|
|
|
|
state => state['features/base/conference'].conference,
|
|
|
|
(conference, { dispatch }, previousConference) => {
|
|
|
|
if (conference && !previousConference) {
|
|
|
|
// local participant is allowed to unmute
|
|
|
|
conference.on(JitsiConferenceEvents.AV_MODERATION_APPROVED, ({ mediaType }) => {
|
|
|
|
dispatch(localParticipantApproved(mediaType));
|
|
|
|
|
|
|
|
// Audio & video moderation are both enabled at the same time.
|
|
|
|
// Avoid displaying 2 different notifications.
|
|
|
|
if (mediaType === MEDIA_TYPE.VIDEO) {
|
|
|
|
dispatch(showNotification({
|
|
|
|
titleKey: 'notify.unmute',
|
|
|
|
descriptionKey: 'notify.hostAskedUnmute',
|
|
|
|
sticky: true
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
conference.on(JitsiConferenceEvents.AV_MODERATION_CHANGED, ({ enabled, mediaType, actor }) => {
|
|
|
|
enabled ? dispatch(enableModeration(mediaType, actor)) : dispatch(disableModeration(mediaType, actor));
|
|
|
|
});
|
|
|
|
|
|
|
|
// this is received by moderators
|
|
|
|
conference.on(
|
|
|
|
JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_APPROVED,
|
|
|
|
({ participant, mediaType }) => {
|
|
|
|
const { _id: id } = participant;
|
|
|
|
|
|
|
|
batch(() => {
|
|
|
|
// store in the whitelist
|
|
|
|
dispatch(participantApproved(id, mediaType));
|
|
|
|
|
|
|
|
// remove from pending list
|
|
|
|
dispatch(dismissPendingParticipant(id, mediaType));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|