// @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)); }