2018-03-07 00:28:19 +00:00
|
|
|
// @flow
|
2017-10-02 23:08:07 +00:00
|
|
|
|
2018-02-26 19:37:12 +00:00
|
|
|
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';
|
2016-10-05 14:36:59 +00:00
|
|
|
import { MiddlewareRegistry } from '../redux';
|
2018-05-22 03:42:22 +00:00
|
|
|
import UIEvents from '../../../../service/UI/UIEvents';
|
2018-02-26 19:37:12 +00:00
|
|
|
import { playSound, registerSound, unregisterSound } from '../sounds';
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2017-08-14 15:02:58 +00:00
|
|
|
import {
|
2018-03-07 00:28:19 +00:00
|
|
|
localParticipantIdChanged,
|
2018-05-03 13:30:07 +00:00
|
|
|
localParticipantJoined,
|
2018-03-07 00:28:19 +00:00
|
|
|
participantUpdated
|
|
|
|
} from './actions';
|
|
|
|
import {
|
|
|
|
DOMINANT_SPEAKER_CHANGED,
|
2017-08-14 15:02:58 +00:00
|
|
|
KICK_PARTICIPANT,
|
|
|
|
MUTE_REMOTE_PARTICIPANT,
|
2017-12-19 23:11:54 +00:00
|
|
|
PARTICIPANT_DISPLAY_NAME_CHANGED,
|
|
|
|
PARTICIPANT_JOINED,
|
2018-02-26 19:37:12 +00:00
|
|
|
PARTICIPANT_LEFT,
|
2017-12-19 23:11:54 +00:00
|
|
|
PARTICIPANT_UPDATED
|
2017-08-14 15:02:58 +00:00
|
|
|
} from './actionTypes';
|
2018-02-26 19:37:12 +00:00
|
|
|
import {
|
|
|
|
LOCAL_PARTICIPANT_DEFAULT_ID,
|
|
|
|
PARTICIPANT_JOINED_SOUND_ID,
|
|
|
|
PARTICIPANT_LEFT_SOUND_ID
|
|
|
|
} from './constants';
|
2017-12-19 23:11:54 +00:00
|
|
|
import {
|
|
|
|
getAvatarURLByParticipantId,
|
2018-02-26 19:37:12 +00:00
|
|
|
getLocalParticipant,
|
2018-03-07 00:28:19 +00:00
|
|
|
getParticipantById,
|
2018-02-26 19:37:12 +00:00
|
|
|
getParticipantCount
|
2017-12-19 23:11:54 +00:00
|
|
|
} from './functions';
|
2018-04-05 19:12:24 +00:00
|
|
|
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
|
2017-06-29 03:35:43 +00:00
|
|
|
|
|
|
|
declare var APP: Object;
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2016-10-05 14:36:59 +00:00
|
|
|
* @returns {Function}
|
|
|
|
*/
|
|
|
|
MiddlewareRegistry.register(store => next => action => {
|
|
|
|
switch (action.type) {
|
2018-02-26 19:37:12 +00:00
|
|
|
case APP_WILL_MOUNT:
|
|
|
|
_registerSounds(store);
|
2018-05-03 13:30:07 +00:00
|
|
|
|
|
|
|
return _localParticipantJoined(store, next, action);
|
2018-05-18 10:27:36 +00:00
|
|
|
|
2018-02-26 19:37:12 +00:00
|
|
|
case APP_WILL_UNMOUNT:
|
|
|
|
_unregisterSounds(store);
|
|
|
|
break;
|
2018-05-18 10:27:36 +00:00
|
|
|
|
|
|
|
case CONFERENCE_WILL_JOIN:
|
2016-12-12 00:29:13 +00:00
|
|
|
store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
|
2016-10-05 14:36:59 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CONFERENCE_LEFT:
|
|
|
|
store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
|
|
|
|
break;
|
2017-06-29 03:35:43 +00:00
|
|
|
|
2018-03-07 00:28:19 +00:00
|
|
|
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());
|
2018-03-07 00:28:19 +00:00
|
|
|
|
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,
|
2018-03-07 00:28:19 +00:00
|
|
|
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);
|
2018-03-07 00:28:19 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-05-22 03:42:22 +00:00
|
|
|
case KICK_PARTICIPANT: {
|
|
|
|
const { conference } = store.getState()['features/base/conference'];
|
|
|
|
|
2017-11-09 17:23:17 +00:00
|
|
|
conference.kickParticipant(action.id);
|
2017-08-14 15:02:58 +00:00
|
|
|
break;
|
2018-05-22 03:42:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case MUTE_REMOTE_PARTICIPANT: {
|
|
|
|
const { conference } = store.getState()['features/base/conference'];
|
2017-08-14 15:02:58 +00:00
|
|
|
|
2017-11-09 17:23:17 +00:00
|
|
|
conference.muteParticipant(action.id);
|
2017-08-14 15:02:58 +00:00
|
|
|
break;
|
2018-05-22 03:42:22 +00:00
|
|
|
}
|
2017-08-14 15:02:58 +00:00
|
|
|
|
2017-06-29 03:35:43 +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;
|
|
|
|
}
|
2017-12-19 23:11:54 +00:00
|
|
|
|
|
|
|
case PARTICIPANT_JOINED:
|
2018-05-22 03:42:22 +00:00
|
|
|
_maybePlaySounds(store, action);
|
2017-12-19 23:11:54 +00:00
|
|
|
|
2018-05-22 03:42:22 +00:00
|
|
|
return _participantJoinedOrUpdated(store, next, action);
|
2017-12-19 23:11:54 +00:00
|
|
|
|
2018-05-22 03:42:22 +00:00
|
|
|
case PARTICIPANT_LEFT:
|
|
|
|
_maybePlaySounds(store, action);
|
2017-12-19 23:11:54 +00:00
|
|
|
break;
|
2018-05-22 03:42:22 +00:00
|
|
|
|
|
|
|
case PARTICIPANT_UPDATED:
|
|
|
|
return _participantJoinedOrUpdated(store, next, action);
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return next(action);
|
|
|
|
});
|
2018-02-26 19:37:12 +00:00
|
|
|
|
2018-05-03 13:30:07 +00:00
|
|
|
/**
|
|
|
|
* Initializes the local participant and signals that it joined.
|
|
|
|
*
|
|
|
|
* @private
|
2018-05-22 03:42:22 +00:00
|
|
|
* @param {Store} store - The redux store.
|
2018-05-03 13:30:07 +00:00
|
|
|
* @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);
|
2018-05-22 19:30:51 +00:00
|
|
|
|
2018-05-03 13:30:07 +00:00
|
|
|
const settings = getState()['features/base/settings'];
|
2018-05-22 19:30:51 +00:00
|
|
|
|
|
|
|
dispatch(localParticipantJoined({
|
2018-05-03 13:30:07 +00:00
|
|
|
avatarID: settings.avatarID,
|
|
|
|
avatarURL: settings.avatarURL,
|
|
|
|
email: settings.email,
|
|
|
|
name: settings.displayName
|
2018-05-22 19:30:51 +00:00
|
|
|
}));
|
2018-05-03 13:30:07 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-02-26 19:37:12 +00:00
|
|
|
/**
|
|
|
|
* 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
|
2018-02-26 19:37:12 +00:00
|
|
|
* {@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)) {
|
2018-02-26 19:37:12 +00:00
|
|
|
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'];
|
|
|
|
|
2018-05-22 19:30:51 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-02-26 19:37:12 +00:00
|
|
|
/**
|
|
|
|
* Registers sounds related with the participants feature.
|
|
|
|
*
|
2018-05-22 03:42:22 +00:00
|
|
|
* @param {Store} store - The redux store.
|
2018-02-26 19:37:12 +00:00
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function _registerSounds({ dispatch }) {
|
|
|
|
dispatch(
|
2018-04-05 19:12:24 +00:00
|
|
|
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));
|
2018-02-26 19:37:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregisters sounds related with the participants feature.
|
|
|
|
*
|
2018-05-22 03:42:22 +00:00
|
|
|
* @param {Store} store - The redux store.
|
2018-02-26 19:37:12 +00:00
|
|
|
* @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));
|
2018-02-26 19:37:12 +00:00
|
|
|
}
|