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

295 lines
9.1 KiB
JavaScript
Raw Normal View History

// @flow
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
2018-05-22 03:42:22 +00:00
import { CONFERENCE_LEFT, CONFERENCE_WILL_JOIN } from '../conference';
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
2018-05-22 03:42:22 +00:00
import UIEvents from '../../../../service/UI/UIEvents';
import { playSound, registerSound, unregisterSound } from '../sounds';
import {
localParticipantIdChanged,
localParticipantJoined,
participantLeft,
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_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
declare var APP: Object;
/**
* Middleware that captures CONFERENCE_JOINED and CONFERENCE_LEFT actions and
* updates respectively ID of local participant.
*
2018-05-22 03:42:22 +00:00
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case APP_WILL_MOUNT:
_registerSounds(store);
return _localParticipantJoined(store, next, action);
case APP_WILL_UNMOUNT:
_unregisterSounds(store);
break;
case CONFERENCE_WILL_JOIN:
2016-12-12 00:29:13 +00:00
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.
Associate remote participant w/ JitsiConference (_UPDATED) The commit message of "Associate remote participant w/ JitsiConference (_JOINED)" explains the motivation for this commit. Practically, _JOINED and _LEFT combined with "Remove remote participants who are no longer of interest" should alleviate the problem with multiplying remote participants to an acceptable level of annoyance. Technically though, a remote participant cannot be identified by an ID only. The ID is (somewhat) "unique" in the context of a single JitsiConference instance. So in order to not have to scratch our heads over an obscure corner, racing case, it's better to always identify remote participants by the pair id-conference. Unfortunately, that's a bit of a high order given the existing source code. So I've implemented the cases which are the easiest so that new source code written with participantUpdated is more likely to identify a remote participant with the pair id-conference. Additionally, the commit "Reduce direct read access to the features/base/participants redux state" brings more control back to the functions of the feature base/participants so that one day we can (if we choose to) do something like, for example: If getParticipants is called with a conference, it returns the participants from features/base/participants who are associated with the specified conference. If no conference is specified in the function call, then default to the conference which is the primary focus of the app at the time of the function call. Added to the above, this should allow us to further reduce the cases in which we're identifying remote participants by id only and get us even closer to a more "predictable" behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
const { conference, id } = action.participant;
const participant = getLocalParticipant(store.getState());
Associate remote participant w/ JitsiConference (_UPDATED) The commit message of "Associate remote participant w/ JitsiConference (_JOINED)" explains the motivation for this commit. Practically, _JOINED and _LEFT combined with "Remove remote participants who are no longer of interest" should alleviate the problem with multiplying remote participants to an acceptable level of annoyance. Technically though, a remote participant cannot be identified by an ID only. The ID is (somewhat) "unique" in the context of a single JitsiConference instance. So in order to not have to scratch our heads over an obscure corner, racing case, it's better to always identify remote participants by the pair id-conference. Unfortunately, that's a bit of a high order given the existing source code. So I've implemented the cases which are the easiest so that new source code written with participantUpdated is more likely to identify a remote participant with the pair id-conference. Additionally, the commit "Reduce direct read access to the features/base/participants redux state" brings more control back to the functions of the feature base/participants so that one day we can (if we choose to) do something like, for example: If getParticipants is called with a conference, it returns the participants from features/base/participants who are associated with the specified conference. If no conference is specified in the function call, then default to the conference which is the primary focus of the app at the time of the function call. Added to the above, this should allow us to further reduce the cases in which we're identifying remote participants by id only and get us even closer to a more "predictable" behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
participant
&& store.dispatch(participantUpdated({
conference,
2018-05-22 03:42:22 +00:00
id,
local: participant.id === id,
raisedHand: false
}));
Associate remote participant w/ JitsiConference (_UPDATED) The commit message of "Associate remote participant w/ JitsiConference (_JOINED)" explains the motivation for this commit. Practically, _JOINED and _LEFT combined with "Remove remote participants who are no longer of interest" should alleviate the problem with multiplying remote participants to an acceptable level of annoyance. Technically though, a remote participant cannot be identified by an ID only. The ID is (somewhat) "unique" in the context of a single JitsiConference instance. So in order to not have to scratch our heads over an obscure corner, racing case, it's better to always identify remote participants by the pair id-conference. Unfortunately, that's a bit of a high order given the existing source code. So I've implemented the cases which are the easiest so that new source code written with participantUpdated is more likely to identify a remote participant with the pair id-conference. Additionally, the commit "Reduce direct read access to the features/base/participants redux state" brings more control back to the functions of the feature base/participants so that one day we can (if we choose to) do something like, for example: If getParticipants is called with a conference, it returns the participants from features/base/participants who are associated with the specified conference. If no conference is specified in the function call, then default to the conference which is the primary focus of the app at the time of the function call. Added to the above, this should allow us to further reduce the cases in which we're identifying remote participants by id only and get us even closer to a more "predictable" behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
typeof APP === 'object' && APP.UI.markDominantSpeaker(id);
break;
}
2018-05-22 03:42:22 +00:00
case KICK_PARTICIPANT: {
const { conference } = store.getState()['features/base/conference'];
conference.kickParticipant(action.id);
break;
2018-05-22 03:42:22 +00:00
}
case MUTE_REMOTE_PARTICIPANT: {
const { conference } = store.getState()['features/base/conference'];
conference.muteParticipant(action.id);
break;
2018-05-22 03:42:22 +00:00
}
// 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:
2018-05-22 03:42:22 +00:00
_maybePlaySounds(store, action);
2018-05-22 03:42:22 +00:00
return _participantJoinedOrUpdated(store, next, action);
2018-05-22 03:42:22 +00:00
case PARTICIPANT_LEFT:
_maybePlaySounds(store, action);
break;
2018-05-22 03:42:22 +00:00
case PARTICIPANT_UPDATED:
return _participantJoinedOrUpdated(store, next, action);
}
return next(action);
});
/**
* Syncs the redux state features/base/participants up with the redux state
* features/base/conference by ensuring that the former does not contain remote
* participants no longer relevant to the latter. Introduced to address an issue
* with multiplying thumbnails in the filmstrip.
*/
StateListenerRegistry.register(
/* selector */ state => {
const { conference, joining } = state['features/base/conference'];
return conference || joining;
},
/* listener */ (conference, { dispatch, getState }) => {
for (const p of getState()['features/base/participants']) {
!p.local
&& (!conference || p.conference !== conference)
&& dispatch(participantLeft(p.id, p.conference));
}
});
/**
* Initializes the local participant and signals that it joined.
*
* @private
2018-05-22 03:42:22 +00:00
* @param {Store} store - The redux store.
* @param {Dispatch} next - The redux dispatch function to dispatch the
* specified action to the specified store.
* @param {Action} action - The redux action which is being dispatched
* in the specified store.
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _localParticipantJoined({ getState, dispatch }, next, action) {
const result = next(action);
const settings = getState()['features/base/settings'];
dispatch(localParticipantJoined({
avatarID: settings.avatarID,
avatarURL: settings.avatarURL,
email: settings.email,
name: settings.displayName
}));
return result;
}
/**
* Plays sounds when participants join/leave conference.
*
2018-05-22 03:42:22 +00:00
* @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
2018-05-22 03:42:22 +00:00
&& (!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));
}
}
}
2018-05-22 03:42:22 +00:00
/**
* Notifies the feature base/participants that the action
* {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} is being dispatched
* within a specific redux store.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} in the specified {@code store}.
* @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or
* {@code PARTICIPANT_UPDATED} which is being dispatched in the specified
* {@code store}.
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _participantJoinedOrUpdated({ getState }, next, action) {
const { participant: { id, local, raisedHand } } = action;
// 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) {
const { conference } = getState()['features/base/conference'];
conference
&& conference.setLocalParticipantProperty(
'raisedHand',
raisedHand);
2018-05-22 03:42:22 +00:00
}
if (typeof APP === 'object') {
if (local) {
APP.UI.onLocalRaiseHandChanged(raisedHand);
APP.UI.setLocalRaisedHandStatus(raisedHand);
} else {
const remoteParticipant = getParticipantById(getState(), id);
remoteParticipant
&& APP.UI.setRaisedHandStatus(
remoteParticipant.id,
remoteParticipant.name,
raisedHand);
}
}
}
// Notify external listeners of potential avatarURL changes.
if (typeof APP === 'object') {
const oldAvatarURL = getAvatarURLByParticipantId(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 newAvatarURL = getAvatarURLByParticipantId(getState(), id);
if (oldAvatarURL !== newAvatarURL) {
const currentKnownId = local ? APP.conference.getMyUserId() : id;
APP.UI.refreshAvatarDisplay(currentKnownId, newAvatarURL);
APP.API.notifyAvatarChanged(currentKnownId, newAvatarURL);
}
return result;
}
return next(action);
}
/**
* Registers sounds related with the participants feature.
*
2018-05-22 03:42:22 +00:00
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _registerSounds({ dispatch }) {
dispatch(
registerSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_FILE));
2018-05-22 03:42:22 +00:00
dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
}
/**
* Unregisters sounds related with the participants feature.
*
2018-05-22 03:42:22 +00:00
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _unregisterSounds({ dispatch }) {
2018-05-22 03:42:22 +00:00
dispatch(unregisterSound(PARTICIPANT_JOINED_SOUND_ID));
dispatch(unregisterSound(PARTICIPANT_LEFT_SOUND_ID));
}