jiti-meet/react/features/base/participants/middleware.js

235 lines
6.9 KiB
JavaScript

// @flow
import UIEvents from '../../../../service/UI/UIEvents';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
import {
CONFERENCE_JOINED,
CONFERENCE_LEFT
} from '../conference';
import { MiddlewareRegistry } from '../redux';
import { playSound, registerSound, unregisterSound } from '../sounds';
import {
localParticipantIdChanged,
participantUpdated
} from './actions';
import {
DOMINANT_SPEAKER_CHANGED,
KICK_PARTICIPANT,
MUTE_REMOTE_PARTICIPANT,
PARTICIPANT_DISPLAY_NAME_CHANGED,
PARTICIPANT_JOINED,
PARTICIPANT_LEFT,
PARTICIPANT_UPDATED
} from './actionTypes';
import {
LOCAL_PARTICIPANT_DEFAULT_ID,
PARTICIPANT_JOINED_SOUND_ID,
PARTICIPANT_LEFT_SOUND_ID
} from './constants';
import {
getAvatarURLByParticipantId,
getLocalParticipant,
getParticipantById,
getParticipantCount
} from './functions';
import {
PARTICIPANT_JOINED_SRC,
PARTICIPANT_LEFT_SRC
} from './sounds';
declare var APP: Object;
/**
* Middleware that captures CONFERENCE_JOINED and CONFERENCE_LEFT actions and
* updates respectively ID of local participant.
*
* @param {Store} store - Redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
const { conference } = store.getState()['features/base/conference'];
if (action.type === PARTICIPANT_JOINED
|| action.type === PARTICIPANT_LEFT) {
_maybePlaySounds(store, action);
}
switch (action.type) {
case APP_WILL_MOUNT:
_registerSounds(store);
break;
case APP_WILL_UNMOUNT:
_unregisterSounds(store);
break;
case CONFERENCE_JOINED:
store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
break;
case CONFERENCE_LEFT:
store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
break;
case DOMINANT_SPEAKER_CHANGED: {
// Ensure the raised hand state is cleared for the dominant speaker.
const participant = getLocalParticipant(store.getState());
if (participant) {
const local = participant.id === action.participant.id;
store.dispatch(participantUpdated({
id: action.participant.id,
local,
raisedHand: false
}));
}
if (typeof APP === 'object') {
APP.UI.markDominantSpeaker(action.participant.id);
}
break;
}
case KICK_PARTICIPANT:
conference.kickParticipant(action.id);
break;
case MUTE_REMOTE_PARTICIPANT:
conference.muteParticipant(action.id);
break;
// TODO Remove this middleware when the local display name update flow is
// fully brought into redux.
case PARTICIPANT_DISPLAY_NAME_CHANGED: {
if (typeof APP !== 'undefined') {
const participant = getLocalParticipant(store.getState());
if (participant && participant.id === action.id) {
APP.UI.emitEvent(UIEvents.NICKNAME_CHANGED, action.name);
}
}
break;
}
case PARTICIPANT_JOINED:
case PARTICIPANT_UPDATED: {
const { participant } = action;
const { id, local, raisedHand } = participant;
// Send an external update of the local participant's raised hand state
// if a new raised hand state is defined in the action.
if (typeof raisedHand !== 'undefined') {
if (local) {
conference.setLocalParticipantProperty(
'raisedHand',
raisedHand);
}
if (typeof APP === 'object') {
if (local) {
APP.UI.onLocalRaiseHandChanged(raisedHand);
APP.UI.setLocalRaisedHandStatus(raisedHand);
} else {
const remoteParticipant
= getParticipantById(store.getState(), id);
remoteParticipant
&& APP.UI.setRaisedHandStatus(
remoteParticipant.id,
remoteParticipant.name,
raisedHand);
}
}
}
// Notify external listeners of potential avatarURL changes.
if (typeof APP === 'object') {
const preUpdateAvatarURL
= getAvatarURLByParticipantId(store.getState(), id);
// Allow the redux update to go through and compare the old avatar
// to the new avatar and emit out change events if necessary.
const result = next(action);
const postUpdateAvatarURL
= getAvatarURLByParticipantId(store.getState(), id);
if (preUpdateAvatarURL !== postUpdateAvatarURL) {
const currentKnownId = local
? APP.conference.getMyUserId() : id;
APP.UI.refreshAvatarDisplay(
currentKnownId, postUpdateAvatarURL);
APP.API.notifyAvatarChanged(
currentKnownId, postUpdateAvatarURL);
}
return result;
}
break;
}
}
return next(action);
});
/**
* Plays sounds when participants join/leave conference.
*
* @param {Store} store - The Redux store.
* @param {Action} action - The Redux action. Should be either
* {@link PARTICIPANT_JOINED} or {@link PARTICIPANT_LEFT}.
* @private
* @returns {void}
*/
function _maybePlaySounds({ getState, dispatch }, action) {
const state = getState();
const { startAudioMuted } = state['features/base/config'];
// We're not playing sounds for local participant
// nor when the user is joining past the "startAudioMuted" limit.
// The intention there was to not play user joined notification in big
// conferences where 100th person is joining.
if (!action.participant.local
&& (!startAudioMuted
|| getParticipantCount(state) < startAudioMuted)) {
if (action.type === PARTICIPANT_JOINED) {
dispatch(playSound(PARTICIPANT_JOINED_SOUND_ID));
} else if (action.type === PARTICIPANT_LEFT) {
dispatch(playSound(PARTICIPANT_LEFT_SOUND_ID));
}
}
}
/**
* Registers sounds related with the participants feature.
*
* @param {Store} store - The Redux store.
* @private
* @returns {void}
*/
function _registerSounds({ dispatch }) {
dispatch(
registerSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_SRC));
dispatch(
registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_SRC));
}
/**
* Unregisters sounds related with the participants feature.
*
* @param {Store} store - The Redux store.
* @private
* @returns {void}
*/
function _unregisterSounds({ dispatch }) {
dispatch(
unregisterSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_SRC));
dispatch(
unregisterSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_SRC));
}