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

228 lines
6.8 KiB
JavaScript
Raw Normal View History

2017-10-13 19:31:05 +00:00
// @flow
import { ReducerRegistry, set } from '../redux';
import { randomHexString } from '../util';
import {
DOMINANT_SPEAKER_CHANGED,
PARTICIPANT_ID_CHANGED,
PARTICIPANT_JOINED,
PARTICIPANT_LEFT,
PARTICIPANT_UPDATED,
PIN_PARTICIPANT
} from './actionTypes';
import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
/**
* Participant object.
* @typedef {Object} Participant
* @property {string} id - Participant ID.
* @property {string} name - Participant name.
* @property {string} avatar - Path to participant avatar if any.
* @property {string} role - Participant role.
* @property {boolean} local - If true, participant is local.
* @property {boolean} pinned - If true, participant is currently a
* "PINNED_ENDPOINT".
* @property {boolean} dominantSpeaker - If this participant is the dominant
* speaker in the (associated) conference, {@code true}; otherwise,
* {@code false}.
* @property {string} email - Participant email.
*/
2017-10-13 19:31:05 +00:00
declare var APP: Object;
/**
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
* The participant properties which cannot be updated through
* {@link PARTICIPANT_UPDATED}. They either identify the participant or can only
* be modified through property-dedicated actions.
*
* @type {string[]}
*/
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 PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
// The following properties identify the participant:
'conference',
'id',
'local',
// The following properties can only be modified through property-dedicated
// actions:
'dominantSpeaker',
'pinned'
];
/**
* Listen for actions which add, remove, or update the set of participants in
* the conference.
*
* @param {Participant[]} state - List of participants to be modified.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @param {Participant} action.participant - Information about participant to be
* added/removed/modified.
* @returns {Participant[]}
*/
ReducerRegistry.register('features/base/participants', (state = [], action) => {
switch (action.type) {
case DOMINANT_SPEAKER_CHANGED:
case PARTICIPANT_ID_CHANGED:
case PARTICIPANT_UPDATED:
case PIN_PARTICIPANT:
return state.map(p => _participant(p, action));
case PARTICIPANT_JOINED:
return [ ...state, _participantJoined(action) ];
case PARTICIPANT_LEFT: {
// XXX A remote participant is uniquely identified by their id in a
// specific JitsiConference instance. The local participant is uniquely
// identified by the very fact that there is only one local participant
// (and the fact that the local participant "joins" at the beginning of
// the app and "leaves" at the end of the app).
const { conference, id } = action.participant;
return state.filter(p =>
!(
p.id === id
// XXX Do not allow collisions in the IDs of the local
// participant and a remote participant cause the removal of
// the local participant when the remote participant's
// removal is requested.
&& p.conference === conference
&& (conference || p.local)));
}
}
return state;
});
/**
* Reducer function for a single participant.
*
* @param {Participant|undefined} state - Participant to be modified.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @param {Participant} action.participant - Information about participant to be
* added/modified.
* @param {JitsiConference} action.conference - Conference instance.
* @private
2017-10-13 19:31:05 +00:00
* @returns {Participant}
*/
2017-10-13 19:31:05 +00:00
function _participant(state: Object = {}, action) {
switch (action.type) {
case DOMINANT_SPEAKER_CHANGED:
// Only one dominant speaker is allowed.
return (
set(state, 'dominantSpeaker', state.id === action.participant.id));
case PARTICIPANT_ID_CHANGED: {
// A participant is identified by an id-conference pair. Only the local
// participant is with an undefined conference.
const { conference } = action;
if (state.id === action.oldValue
&& state.conference === conference
&& (conference || state.local)) {
return {
...state,
id: action.newValue
};
}
break;
}
2017-03-07 16:50:17 +00:00
case PARTICIPANT_UPDATED: {
2017-10-18 18:21:46 +00:00
const { participant } = action; // eslint-disable-line no-shadow
let { id } = participant;
2017-10-18 18:21:46 +00:00
const { local } = participant;
if (!id && local) {
id = LOCAL_PARTICIPANT_DEFAULT_ID;
}
2017-03-07 16:50:17 +00:00
if (state.id === id) {
const newState = { ...state };
2017-03-07 16:50:17 +00:00
for (const key in participant) {
if (participant.hasOwnProperty(key)
&& PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE.indexOf(key)
=== -1) {
2017-03-07 16:50:17 +00:00
newState[key] = participant[key];
}
}
return newState;
}
break;
2017-03-07 16:50:17 +00:00
}
case PIN_PARTICIPANT:
// Currently, only one pinned participant is allowed.
return set(state, 'pinned', state.id === action.participant.id);
}
return state;
}
/**
* Reduces a specific redux action of type {@link PARTICIPANT_JOINED} in the
* feature base/participants.
*
* @param {Action} action - The redux action of type {@code PARTICIPANT_JOINED}
* to reduce.
* @private
* @returns {Object} The new participant derived from the payload of the
* specified {@code action} to be added into the redux state of the feature
* base/participants after the reduction of the specified
* {@code action}.
*/
function _participantJoined({ participant }) {
const {
avatarURL,
connectionStatus,
dominantSpeaker,
email,
isBot,
local,
name,
pinned,
presence,
role
} = participant;
let { avatarID, conference, id } = participant;
if (local) {
// avatarID
//
// TODO Get the avatarID of the local participant from localStorage.
avatarID || (avatarID = randomHexString(32));
2017-10-13 19:31:05 +00:00
// conference
//
// XXX The local participant is not identified in association with a
// JitsiConference because it is identified by the very fact that it is
// the local participant.
conference = undefined;
2017-10-13 19:31:05 +00:00
// id
id || (id = LOCAL_PARTICIPANT_DEFAULT_ID);
}
return {
avatarID,
avatarURL,
conference,
connectionStatus,
dominantSpeaker: dominantSpeaker || false,
email,
id,
isBot,
local: local || false,
name,
pinned: pinned || false,
presence,
role: role || PARTICIPANT_ROLE.NONE
};
}