2017-10-13 19:31:05 +00:00
|
|
|
// @flow
|
|
|
|
|
2022-04-04 18:57:58 +00:00
|
|
|
import {
|
|
|
|
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
|
|
|
|
} from '../../video-layout/actionTypes';
|
2017-04-22 22:57:08 +00:00
|
|
|
import { ReducerRegistry, set } from '../redux';
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
import {
|
|
|
|
DOMINANT_SPEAKER_CHANGED,
|
|
|
|
PARTICIPANT_ID_CHANGED,
|
|
|
|
PARTICIPANT_JOINED,
|
|
|
|
PARTICIPANT_LEFT,
|
|
|
|
PARTICIPANT_UPDATED,
|
2019-06-26 14:08:23 +00:00
|
|
|
PIN_PARTICIPANT,
|
2021-09-10 11:05:16 +00:00
|
|
|
RAISE_HAND_UPDATED,
|
2022-04-14 17:07:17 +00:00
|
|
|
SCREENSHARE_PARTICIPANT_NAME_CHANGED,
|
2019-06-26 14:08:23 +00:00
|
|
|
SET_LOADABLE_AVATAR_URL
|
2016-10-05 14:36:59 +00:00
|
|
|
} from './actionTypes';
|
2017-10-18 18:15:49 +00:00
|
|
|
import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
|
2021-07-09 12:36:19 +00:00
|
|
|
import { isParticipantModerator } from './functions';
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Participant object.
|
2021-11-04 21:10:43 +00:00
|
|
|
*
|
2016-10-05 14:36:59 +00:00
|
|
|
* @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".
|
2016-11-04 18:28:47 +00:00
|
|
|
* @property {boolean} dominantSpeaker - If this participant is the dominant
|
2017-10-01 06:35:19 +00:00
|
|
|
* speaker in the (associated) conference, {@code true}; otherwise,
|
|
|
|
* {@code false}.
|
2016-10-05 14:36:59 +00:00
|
|
|
* @property {string} email - Participant email.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
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.
|
|
|
|
*
|
2016-10-05 14:36:59 +00:00
|
|
|
* @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'
|
|
|
|
];
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
const DEFAULT_STATE = {
|
|
|
|
dominantSpeaker: undefined,
|
|
|
|
everyoneIsModerator: false,
|
2021-08-18 22:34:01 +00:00
|
|
|
fakeParticipants: new Map(),
|
|
|
|
haveParticipantWithScreenSharingFeature: false,
|
2021-07-09 12:36:19 +00:00
|
|
|
local: undefined,
|
2022-04-04 18:57:58 +00:00
|
|
|
localScreenShare: undefined,
|
2021-08-18 22:34:01 +00:00
|
|
|
pinnedParticipant: undefined,
|
2021-09-10 17:37:05 +00:00
|
|
|
raisedHandsQueue: [],
|
2021-07-09 12:36:19 +00:00
|
|
|
remote: new Map(),
|
2022-04-29 14:32:16 +00:00
|
|
|
sortedRemoteVirtualScreenshareParticipants: new Map(),
|
2021-08-18 22:34:01 +00:00
|
|
|
sortedRemoteParticipants: new Map(),
|
2021-08-23 22:39:09 +00:00
|
|
|
sortedRemoteScreenshares: new Map(),
|
2021-09-10 17:37:05 +00:00
|
|
|
speakersList: new Map()
|
2021-07-09 12:36:19 +00:00
|
|
|
};
|
|
|
|
|
2018-05-22 19:30:51 +00:00
|
|
|
/**
|
|
|
|
* 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[]}
|
|
|
|
*/
|
2021-07-09 12:36:19 +00:00
|
|
|
ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, action) => {
|
2018-05-22 19:30:51 +00:00
|
|
|
switch (action.type) {
|
2021-07-09 12:36:19 +00:00
|
|
|
case PARTICIPANT_ID_CHANGED: {
|
|
|
|
const { local } = state;
|
|
|
|
|
|
|
|
if (local) {
|
2021-12-29 22:32:40 +00:00
|
|
|
if (action.newValue === 'local' && state.raisedHandsQueue.find(pid => pid.id === local.id)) {
|
|
|
|
state.raisedHandsQueue = state.raisedHandsQueue.filter(pid => pid.id !== local.id);
|
|
|
|
}
|
2021-07-09 12:36:19 +00:00
|
|
|
state.local = {
|
|
|
|
...local,
|
|
|
|
id: action.newValue
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
...state
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
case DOMINANT_SPEAKER_CHANGED: {
|
|
|
|
const { participant } = action;
|
2021-08-18 22:34:01 +00:00
|
|
|
const { id, previousSpeakers = [] } = participant;
|
2021-08-23 22:39:09 +00:00
|
|
|
const { dominantSpeaker, local } = state;
|
2021-08-19 21:56:45 +00:00
|
|
|
const newSpeakers = [ id, ...previousSpeakers ];
|
2021-08-23 22:39:09 +00:00
|
|
|
const sortedSpeakersList = [];
|
2021-08-18 22:34:01 +00:00
|
|
|
|
2021-08-19 21:56:45 +00:00
|
|
|
for (const speaker of newSpeakers) {
|
2021-08-23 22:39:09 +00:00
|
|
|
if (speaker !== local?.id) {
|
2021-08-19 21:56:45 +00:00
|
|
|
const remoteParticipant = state.remote.get(speaker);
|
|
|
|
|
2021-11-26 15:39:34 +00:00
|
|
|
remoteParticipant
|
|
|
|
&& sortedSpeakersList.push(
|
|
|
|
[ speaker, _getDisplayName(state, remoteParticipant.name) ]
|
|
|
|
);
|
2021-08-19 21:56:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keep the remote speaker list sorted alphabetically.
|
|
|
|
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
|
2021-07-09 12:36:19 +00:00
|
|
|
|
|
|
|
// Only one dominant speaker is allowed.
|
|
|
|
if (dominantSpeaker) {
|
|
|
|
_updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
|
|
|
|
return {
|
|
|
|
...state,
|
2021-08-18 22:34:01 +00:00
|
|
|
dominantSpeaker: id,
|
2021-08-19 21:56:45 +00:00
|
|
|
speakersList: new Map(sortedSpeakersList)
|
2021-07-09 12:36:19 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
delete state.dominantSpeaker;
|
|
|
|
|
|
|
|
return {
|
|
|
|
...state
|
|
|
|
};
|
|
|
|
}
|
|
|
|
case PIN_PARTICIPANT: {
|
|
|
|
const { participant } = action;
|
|
|
|
const { id } = participant;
|
|
|
|
const { pinnedParticipant } = state;
|
|
|
|
|
|
|
|
// Only one pinned participant is allowed.
|
|
|
|
if (pinnedParticipant) {
|
|
|
|
_updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
|
|
|
|
}
|
|
|
|
|
2021-12-01 12:47:54 +00:00
|
|
|
if (id && _updateParticipantProperty(state, id, 'pinned', true)) {
|
2021-07-09 12:36:19 +00:00
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
pinnedParticipant: id
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
delete state.pinnedParticipant;
|
|
|
|
|
|
|
|
return {
|
|
|
|
...state
|
|
|
|
};
|
|
|
|
}
|
2019-06-26 14:08:23 +00:00
|
|
|
case SET_LOADABLE_AVATAR_URL:
|
2021-07-09 12:36:19 +00:00
|
|
|
case PARTICIPANT_UPDATED: {
|
|
|
|
const { participant } = action;
|
|
|
|
let { id } = participant;
|
|
|
|
const { local } = participant;
|
|
|
|
|
|
|
|
if (!id && local) {
|
|
|
|
id = LOCAL_PARTICIPANT_DEFAULT_ID;
|
|
|
|
}
|
|
|
|
|
|
|
|
let newParticipant;
|
|
|
|
|
|
|
|
if (state.remote.has(id)) {
|
|
|
|
newParticipant = _participant(state.remote.get(id), action);
|
|
|
|
state.remote.set(id, newParticipant);
|
|
|
|
} else if (id === state.local?.id) {
|
|
|
|
newParticipant = state.local = _participant(state.local, action);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newParticipant) {
|
|
|
|
|
|
|
|
// everyoneIsModerator calculation:
|
|
|
|
const isModerator = isParticipantModerator(newParticipant);
|
|
|
|
|
|
|
|
if (state.everyoneIsModerator && !isModerator) {
|
|
|
|
state.everyoneIsModerator = false;
|
|
|
|
} else if (!state.everyoneIsModerator && isModerator) {
|
|
|
|
state.everyoneIsModerator = _isEveryoneModerator(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
// haveParticipantWithScreenSharingFeature calculation:
|
|
|
|
const { features = {} } = participant;
|
|
|
|
|
|
|
|
// Currently we use only PARTICIPANT_UPDATED to set a feature to enabled and we never disable it.
|
|
|
|
if (String(features['screen-sharing']) === 'true') {
|
|
|
|
state.haveParticipantWithScreenSharingFeature = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
...state
|
|
|
|
};
|
|
|
|
}
|
2022-04-14 17:07:17 +00:00
|
|
|
case SCREENSHARE_PARTICIPANT_NAME_CHANGED: {
|
|
|
|
const { id, name } = action;
|
|
|
|
|
2022-04-29 14:32:16 +00:00
|
|
|
if (state.sortedRemoteVirtualScreenshareParticipants.has(id)) {
|
|
|
|
state.sortedRemoteVirtualScreenshareParticipants.delete(id);
|
2022-04-14 17:07:17 +00:00
|
|
|
|
2022-04-29 14:32:16 +00:00
|
|
|
const sortedRemoteVirtualScreenshareParticipants = [ ...state.sortedRemoteVirtualScreenshareParticipants ];
|
2022-04-14 17:07:17 +00:00
|
|
|
|
2022-04-29 14:32:16 +00:00
|
|
|
sortedRemoteVirtualScreenshareParticipants.push([ id, name ]);
|
|
|
|
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
2022-04-14 17:07:17 +00:00
|
|
|
|
2022-04-29 14:32:16 +00:00
|
|
|
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
2022-04-14 17:07:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return { ...state };
|
|
|
|
}
|
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
case PARTICIPANT_JOINED: {
|
|
|
|
const participant = _participantJoined(action);
|
2022-04-29 14:32:16 +00:00
|
|
|
const {
|
|
|
|
id,
|
|
|
|
isFakeParticipant,
|
|
|
|
isLocalScreenShare,
|
|
|
|
isVirtualScreenshareParticipant,
|
|
|
|
name,
|
|
|
|
pinned
|
|
|
|
} = participant;
|
2021-07-09 12:36:19 +00:00
|
|
|
const { pinnedParticipant, dominantSpeaker } = state;
|
|
|
|
|
2021-08-18 22:34:01 +00:00
|
|
|
if (pinned) {
|
2021-07-09 12:36:19 +00:00
|
|
|
if (pinnedParticipant) {
|
|
|
|
_updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
|
|
|
|
}
|
|
|
|
|
2021-08-18 22:34:01 +00:00
|
|
|
state.pinnedParticipant = id;
|
2021-07-09 12:36:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (participant.dominantSpeaker) {
|
|
|
|
if (dominantSpeaker) {
|
|
|
|
_updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
|
|
|
|
}
|
2021-08-18 22:34:01 +00:00
|
|
|
state.dominantSpeaker = id;
|
2021-07-09 12:36:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const isModerator = isParticipantModerator(participant);
|
|
|
|
const { local, remote } = state;
|
|
|
|
|
|
|
|
if (state.everyoneIsModerator && !isModerator) {
|
|
|
|
state.everyoneIsModerator = false;
|
|
|
|
} else if (!local && remote.size === 0 && isModerator) {
|
|
|
|
state.everyoneIsModerator = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (participant.local) {
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
local: participant
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-04-04 18:57:58 +00:00
|
|
|
if (isLocalScreenShare) {
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
localScreenShare: participant
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-08-18 22:34:01 +00:00
|
|
|
state.remote.set(id, participant);
|
|
|
|
|
|
|
|
// Insert the new participant.
|
2021-11-26 15:39:34 +00:00
|
|
|
const displayName = _getDisplayName(state, name);
|
2021-08-18 22:34:01 +00:00
|
|
|
const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);
|
|
|
|
|
|
|
|
sortedRemoteParticipants.push([ id, displayName ]);
|
|
|
|
sortedRemoteParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
2018-05-22 19:30:51 +00:00
|
|
|
|
2021-08-18 22:34:01 +00:00
|
|
|
// The sort order of participants is preserved since Map remembers the original insertion order of the keys.
|
|
|
|
state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);
|
|
|
|
|
2022-04-29 14:32:16 +00:00
|
|
|
if (isVirtualScreenshareParticipant) {
|
|
|
|
const sortedRemoteVirtualScreenshareParticipants = [ ...state.sortedRemoteVirtualScreenshareParticipants ];
|
2022-04-04 18:57:58 +00:00
|
|
|
|
2022-04-29 14:32:16 +00:00
|
|
|
sortedRemoteVirtualScreenshareParticipants.push([ id, name ]);
|
|
|
|
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
2022-04-04 18:57:58 +00:00
|
|
|
|
2022-04-29 14:32:16 +00:00
|
|
|
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
2022-04-04 18:57:58 +00:00
|
|
|
}
|
2021-08-18 22:34:01 +00:00
|
|
|
if (isFakeParticipant) {
|
|
|
|
state.fakeParticipants.set(id, participant);
|
2021-07-09 12:36:19 +00:00
|
|
|
}
|
2018-05-22 19:30:51 +00:00
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
return { ...state };
|
|
|
|
|
|
|
|
}
|
2018-05-22 21:08:35 +00:00
|
|
|
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;
|
2022-04-04 18:57:58 +00:00
|
|
|
const {
|
|
|
|
fakeParticipants,
|
2022-04-29 14:32:16 +00:00
|
|
|
sortedRemoteVirtualScreenshareParticipants,
|
2022-04-04 18:57:58 +00:00
|
|
|
remote,
|
|
|
|
local,
|
|
|
|
localScreenShare,
|
|
|
|
dominantSpeaker,
|
|
|
|
pinnedParticipant
|
|
|
|
} = state;
|
2021-07-09 12:36:19 +00:00
|
|
|
let oldParticipant = remote.get(id);
|
|
|
|
|
|
|
|
if (oldParticipant && oldParticipant.conference === conference) {
|
|
|
|
remote.delete(id);
|
|
|
|
} else if (local?.id === id) {
|
|
|
|
oldParticipant = state.local;
|
|
|
|
delete state.local;
|
2022-04-04 18:57:58 +00:00
|
|
|
} else if (localScreenShare?.id === id) {
|
|
|
|
oldParticipant = state.local;
|
|
|
|
delete state.localScreenShare;
|
2021-07-09 12:36:19 +00:00
|
|
|
} else {
|
|
|
|
// no participant found
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2021-08-18 22:34:01 +00:00
|
|
|
state.sortedRemoteParticipants.delete(id);
|
2021-11-08 15:32:12 +00:00
|
|
|
state.raisedHandsQueue = state.raisedHandsQueue.filter(pid => pid.id !== id);
|
2021-08-18 22:34:01 +00:00
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
if (!state.everyoneIsModerator && !isParticipantModerator(oldParticipant)) {
|
|
|
|
state.everyoneIsModerator = _isEveryoneModerator(state);
|
|
|
|
}
|
2018-05-22 21:08:35 +00:00
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
const { features = {} } = oldParticipant || {};
|
2018-05-27 20:42:13 +00:00
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
if (state.haveParticipantWithScreenSharingFeature && String(features['screen-sharing']) === 'true') {
|
|
|
|
const { features: localFeatures = {} } = state.local || {};
|
|
|
|
|
|
|
|
if (String(localFeatures['screen-sharing']) !== 'true') {
|
|
|
|
state.haveParticipantWithScreenSharingFeature = false;
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
for (const [ key, participant ] of state.remote) {
|
|
|
|
const { features: f = {} } = participant;
|
|
|
|
|
|
|
|
if (String(f['screen-sharing']) === 'true') {
|
|
|
|
state.haveParticipantWithScreenSharingFeature = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dominantSpeaker === id) {
|
|
|
|
state.dominantSpeaker = undefined;
|
|
|
|
}
|
|
|
|
|
2021-08-18 22:34:01 +00:00
|
|
|
// Remove the participant from the list of speakers.
|
2021-08-19 21:56:45 +00:00
|
|
|
state.speakersList.has(id) && state.speakersList.delete(id);
|
2021-08-18 22:34:01 +00:00
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
if (pinnedParticipant === id) {
|
|
|
|
state.pinnedParticipant = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fakeParticipants.has(id)) {
|
|
|
|
fakeParticipants.delete(id);
|
|
|
|
}
|
|
|
|
|
2022-04-29 14:32:16 +00:00
|
|
|
if (sortedRemoteVirtualScreenshareParticipants.has(id)) {
|
|
|
|
sortedRemoteVirtualScreenshareParticipants.delete(id);
|
|
|
|
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
2022-04-04 18:57:58 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
return { ...state };
|
2018-05-22 21:08:35 +00:00
|
|
|
}
|
2021-09-10 11:05:16 +00:00
|
|
|
case RAISE_HAND_UPDATED: {
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
raisedHandsQueue: action.queue
|
|
|
|
};
|
|
|
|
}
|
2021-08-23 22:39:09 +00:00
|
|
|
case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED: {
|
|
|
|
const { participantIds } = action;
|
|
|
|
const sortedSharesList = [];
|
|
|
|
|
|
|
|
for (const participant of participantIds) {
|
|
|
|
const remoteParticipant = state.remote.get(participant);
|
|
|
|
|
|
|
|
if (remoteParticipant) {
|
2021-11-26 15:39:34 +00:00
|
|
|
const displayName
|
|
|
|
= _getDisplayName(state, remoteParticipant.name);
|
2021-08-23 22:39:09 +00:00
|
|
|
|
|
|
|
sortedSharesList.push([ participant, displayName ]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keep the remote screen share list sorted alphabetically.
|
|
|
|
sortedSharesList.length && sortedSharesList.sort((a, b) => a[1].localeCompare(b[1]));
|
|
|
|
state.sortedRemoteScreenshares = new Map(sortedSharesList);
|
|
|
|
|
|
|
|
return { ...state };
|
|
|
|
}
|
2018-05-22 19:30:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
});
|
|
|
|
|
2021-08-19 21:56:45 +00:00
|
|
|
/**
|
|
|
|
* Returns the participant's display name, default string if display name is not set on the participant.
|
|
|
|
*
|
2021-11-26 15:39:34 +00:00
|
|
|
* @param {Object} state - The local participant redux state.
|
2021-08-19 21:56:45 +00:00
|
|
|
* @param {string} name - The display name of the participant.
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
2021-11-26 15:39:34 +00:00
|
|
|
function _getDisplayName(state: Object, name: string): string {
|
|
|
|
const config = state['features/base/config'];
|
|
|
|
|
|
|
|
return name ?? (config?.defaultRemoteDisplayName || 'Fellow Jitster');
|
2021-08-19 21:56:45 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
/**
|
|
|
|
* Loops trough the participants in the state in order to check if all participants are moderators.
|
|
|
|
*
|
|
|
|
* @param {Object} state - The local participant redux state.
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
function _isEveryoneModerator(state) {
|
|
|
|
if (isParticipantModerator(state.local)) {
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
for (const [ k, p ] of state.remote) {
|
|
|
|
if (!isParticipantModerator(p)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
2017-01-28 23:28:13 +00:00
|
|
|
* @private
|
2017-10-13 19:31:05 +00:00
|
|
|
* @returns {Participant}
|
2016-10-05 14:36:59 +00:00
|
|
|
*/
|
2017-10-13 19:31:05 +00:00
|
|
|
function _participant(state: Object = {}, action) {
|
2016-10-05 14:36:59 +00:00
|
|
|
switch (action.type) {
|
2019-06-26 14:08:23 +00:00
|
|
|
case SET_LOADABLE_AVATAR_URL:
|
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
|
2017-03-23 18:01:33 +00:00
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
const newState = { ...state };
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
for (const key in participant) {
|
|
|
|
if (participant.hasOwnProperty(key)
|
|
|
|
&& PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE.indexOf(key)
|
|
|
|
=== -1) {
|
|
|
|
newState[key] = participant[key];
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-09 12:36:19 +00:00
|
|
|
return newState;
|
|
|
|
}
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
2016-11-04 18:28:47 +00:00
|
|
|
|
|
|
|
return state;
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-05-22 19:30:51 +00:00
|
|
|
* Reduces a specific redux action of type {@link PARTICIPANT_JOINED} in the
|
|
|
|
* feature base/participants.
|
2016-10-05 14:36:59 +00:00
|
|
|
*
|
2018-05-22 19:30:51 +00:00
|
|
|
* @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}.
|
2016-10-05 14:36:59 +00:00
|
|
|
*/
|
2018-05-22 19:30:51 +00:00
|
|
|
function _participantJoined({ participant }) {
|
|
|
|
const {
|
|
|
|
avatarURL,
|
2018-06-26 22:56:22 +00:00
|
|
|
botType,
|
2018-05-22 19:30:51 +00:00
|
|
|
connectionStatus,
|
|
|
|
dominantSpeaker,
|
|
|
|
email,
|
2018-06-22 18:59:54 +00:00
|
|
|
isFakeParticipant,
|
2022-04-29 14:32:16 +00:00
|
|
|
isVirtualScreenshareParticipant,
|
2022-04-04 18:57:58 +00:00
|
|
|
isLocalScreenShare,
|
2021-06-11 08:58:45 +00:00
|
|
|
isReplacing,
|
2019-07-04 08:31:23 +00:00
|
|
|
isJigasi,
|
2019-06-26 14:08:23 +00:00
|
|
|
loadableAvatarUrl,
|
2018-05-22 19:30:51 +00:00
|
|
|
local,
|
|
|
|
name,
|
|
|
|
pinned,
|
2018-05-21 21:40:15 +00:00
|
|
|
presence,
|
2018-05-22 19:30:51 +00:00
|
|
|
role
|
|
|
|
} = participant;
|
2019-07-04 08:51:35 +00:00
|
|
|
let { conference, id } = participant;
|
2018-05-22 19:30:51 +00:00
|
|
|
|
|
|
|
if (local) {
|
|
|
|
// 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
|
|
|
|
2018-05-22 19:30:51 +00:00
|
|
|
// id
|
|
|
|
id || (id = LOCAL_PARTICIPANT_DEFAULT_ID);
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
2018-05-22 19:30:51 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
avatarURL,
|
2018-06-26 22:56:22 +00:00
|
|
|
botType,
|
2018-05-22 19:30:51 +00:00
|
|
|
conference,
|
|
|
|
connectionStatus,
|
|
|
|
dominantSpeaker: dominantSpeaker || false,
|
|
|
|
email,
|
|
|
|
id,
|
2018-06-22 18:59:54 +00:00
|
|
|
isFakeParticipant,
|
2022-04-29 14:32:16 +00:00
|
|
|
isVirtualScreenshareParticipant,
|
2022-04-04 18:57:58 +00:00
|
|
|
isLocalScreenShare,
|
2021-06-11 08:58:45 +00:00
|
|
|
isReplacing,
|
2019-07-04 08:31:23 +00:00
|
|
|
isJigasi,
|
2019-06-26 14:08:23 +00:00
|
|
|
loadableAvatarUrl,
|
2018-05-22 19:30:51 +00:00
|
|
|
local: local || false,
|
|
|
|
name,
|
|
|
|
pinned: pinned || false,
|
2018-05-21 21:40:15 +00:00
|
|
|
presence,
|
2018-05-22 19:30:51 +00:00
|
|
|
role: role || PARTICIPANT_ROLE.NONE
|
|
|
|
};
|
|
|
|
}
|
2021-08-19 21:56:45 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates a specific property for a participant.
|
|
|
|
*
|
|
|
|
* @param {State} state - The redux state.
|
|
|
|
* @param {string} id - The ID of the participant.
|
|
|
|
* @param {string} property - The property to update.
|
|
|
|
* @param {*} value - The new value.
|
|
|
|
* @returns {boolean} - True if a participant was updated and false otherwise.
|
|
|
|
*/
|
2021-08-23 22:39:09 +00:00
|
|
|
function _updateParticipantProperty(state, id, property, value) {
|
2022-04-04 18:57:58 +00:00
|
|
|
const { remote, local, localScreenShare } = state;
|
2021-08-19 21:56:45 +00:00
|
|
|
|
|
|
|
if (remote.has(id)) {
|
|
|
|
remote.set(id, set(remote.get(id), property, value));
|
|
|
|
|
|
|
|
return true;
|
2021-12-01 12:47:54 +00:00
|
|
|
} else if (local?.id === id || local?.id === 'local') {
|
|
|
|
// The local participant's ID can chance from something to "local" when
|
|
|
|
// not in a conference.
|
2021-08-19 21:56:45 +00:00
|
|
|
state.local = set(local, property, value);
|
|
|
|
|
2022-04-04 18:57:58 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
} else if (localScreenShare?.id === id) {
|
|
|
|
state.localScreenShare = set(localScreenShare, property, value);
|
|
|
|
|
2021-08-19 21:56:45 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|