"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-7555/react/features/base/participants/reducer.ts" (28 Sep 2023, 20884 Bytes) of package /linux/misc/jitsi-meet-7555.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 import { AnyAction } from 'redux';
2
3 import { MEDIA_TYPE } from '../media/constants';
4 import ReducerRegistry from '../redux/ReducerRegistry';
5 import { set } from '../redux/functions';
6
7 import {
8 DOMINANT_SPEAKER_CHANGED,
9 OVERWRITE_PARTICIPANT_NAME,
10 PARTICIPANT_ID_CHANGED,
11 PARTICIPANT_JOINED,
12 PARTICIPANT_LEFT,
13 PARTICIPANT_SOURCES_UPDATED,
14 PARTICIPANT_UPDATED,
15 PIN_PARTICIPANT,
16 RAISE_HAND_UPDATED,
17 SCREENSHARE_PARTICIPANT_NAME_CHANGED,
18 SET_LOADABLE_AVATAR_URL
19 } from './actionTypes';
20 import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
21 import {
22 isLocalScreenshareParticipant,
23 isParticipantModerator,
24 isRemoteScreenshareParticipant,
25 isScreenShareParticipant
26 } from './functions';
27 import { FakeParticipant, ILocalParticipant, IParticipant, ISourceInfo } from './types';
28
29 /**
30 * Participant object.
31 *
32 * @typedef {Object} Participant
33 * @property {string} id - Participant ID.
34 * @property {string} name - Participant name.
35 * @property {string} avatar - Path to participant avatar if any.
36 * @property {string} role - Participant role.
37 * @property {boolean} local - If true, participant is local.
38 * @property {boolean} pinned - If true, participant is currently a
39 * "PINNED_ENDPOINT".
40 * @property {boolean} dominantSpeaker - If this participant is the dominant
41 * speaker in the (associated) conference, {@code true}; otherwise,
42 * {@code false}.
43 * @property {string} email - Participant email.
44 */
45
46 /**
47 * The participant properties which cannot be updated through
48 * {@link PARTICIPANT_UPDATED}. They either identify the participant or can only
49 * be modified through property-dedicated actions.
50 *
51 * @type {string[]}
52 */
53 const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
54
55 // The following properties identify the participant:
56 'conference',
57 'id',
58 'local',
59
60 // The following properties can only be modified through property-dedicated
61 // actions:
62 'dominantSpeaker',
63 'pinned'
64 ];
65
66 const DEFAULT_STATE = {
67 dominantSpeaker: undefined,
68 fakeParticipants: new Map(),
69 local: undefined,
70 localScreenShare: undefined,
71 numberOfNonModeratorParticipants: 0,
72 numberOfParticipantsDisabledE2EE: 0,
73 numberOfParticipantsNotSupportingE2EE: 0,
74 overwrittenNameList: {},
75 pinnedParticipant: undefined,
76 raisedHandsQueue: [],
77 remote: new Map(),
78 remoteVideoSources: new Set<string>(),
79 sortedRemoteVirtualScreenshareParticipants: new Map(),
80 sortedRemoteParticipants: new Map(),
81 speakersList: new Map()
82 };
83
84 export interface IParticipantsState {
85 dominantSpeaker?: string;
86 fakeParticipants: Map<string, IParticipant>;
87 local?: ILocalParticipant;
88 localScreenShare?: IParticipant;
89 numberOfNonModeratorParticipants: number;
90 numberOfParticipantsDisabledE2EE: number;
91 numberOfParticipantsNotSupportingE2EE: number;
92 overwrittenNameList: { [id: string]: string; };
93 pinnedParticipant?: string;
94 raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
95 remote: Map<string, IParticipant>;
96 remoteVideoSources: Set<string>;
97 sortedRemoteParticipants: Map<string, string>;
98 sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
99 speakersList: Map<string, string>;
100 }
101
102 /**
103 * Listen for actions which add, remove, or update the set of participants in
104 * the conference.
105 *
106 * @param {IParticipant[]} state - List of participants to be modified.
107 * @param {Object} action - Action object.
108 * @param {string} action.type - Type of action.
109 * @param {IParticipant} action.participant - Information about participant to be
110 * added/removed/modified.
111 * @returns {IParticipant[]}
112 */
113 ReducerRegistry.register<IParticipantsState>('features/base/participants',
114 (state = DEFAULT_STATE, action): IParticipantsState => {
115 switch (action.type) {
116 case PARTICIPANT_ID_CHANGED: {
117 const { local } = state;
118
119 if (local) {
120 if (action.newValue === 'local' && state.raisedHandsQueue.find(pid => pid.id === local.id)) {
121 state.raisedHandsQueue = state.raisedHandsQueue.filter(pid => pid.id !== local.id);
122 }
123 state.local = {
124 ...local,
125 id: action.newValue
126 };
127
128 return {
129 ...state
130 };
131 }
132
133 return state;
134 }
135 case DOMINANT_SPEAKER_CHANGED: {
136 const { participant } = action;
137 const { id, previousSpeakers = [] } = participant;
138 const { dominantSpeaker, local } = state;
139 const newSpeakers = [ id, ...previousSpeakers ];
140 const sortedSpeakersList: Array<Array<string>> = [];
141
142 for (const speaker of newSpeakers) {
143 if (speaker !== local?.id) {
144 const remoteParticipant = state.remote.get(speaker);
145
146 remoteParticipant
147 && sortedSpeakersList.push(
148 [ speaker, _getDisplayName(state, remoteParticipant?.name) ]
149 );
150 }
151 }
152
153 // Keep the remote speaker list sorted alphabetically.
154 sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
155
156 // Only one dominant speaker is allowed.
157 if (dominantSpeaker) {
158 _updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
159 }
160
161 if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
162 return {
163 ...state,
164 dominantSpeaker: id, // @ts-ignore
165 speakersList: new Map(sortedSpeakersList)
166 };
167 }
168
169 delete state.dominantSpeaker;
170
171 return {
172 ...state
173 };
174 }
175 case PIN_PARTICIPANT: {
176 const { participant } = action;
177 const { id } = participant;
178 const { pinnedParticipant } = state;
179
180 // Only one pinned participant is allowed.
181 if (pinnedParticipant) {
182 _updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
183 }
184
185 if (id && _updateParticipantProperty(state, id, 'pinned', true)) {
186 return {
187 ...state,
188 pinnedParticipant: id
189 };
190 }
191
192 delete state.pinnedParticipant;
193
194 return {
195 ...state
196 };
197 }
198 case SET_LOADABLE_AVATAR_URL:
199 case PARTICIPANT_UPDATED: {
200 const { participant } = action;
201 let { id } = participant;
202 const { local } = participant;
203
204 if (!id && local) {
205 id = LOCAL_PARTICIPANT_DEFAULT_ID;
206 }
207
208 let newParticipant: IParticipant | null = null;
209 const oldParticipant = local || state.local?.id === id ? state.local : state.remote.get(id);
210
211 if (state.remote.has(id)) {
212 newParticipant = _participant(oldParticipant, action);
213 state.remote.set(id, newParticipant);
214 } else if (id === state.local?.id) {
215 newParticipant = state.local = _participant(state.local, action);
216 }
217
218 if (oldParticipant && newParticipant && !newParticipant.fakeParticipant) {
219 const isModerator = isParticipantModerator(newParticipant);
220
221 if (isParticipantModerator(oldParticipant) !== isModerator) {
222 state.numberOfNonModeratorParticipants += isModerator ? -1 : 1;
223 }
224
225 const e2eeEnabled = Boolean(newParticipant.e2eeEnabled);
226 const e2eeSupported = Boolean(newParticipant.e2eeSupported);
227
228 if (Boolean(oldParticipant.e2eeEnabled) !== e2eeEnabled) {
229 state.numberOfParticipantsDisabledE2EE += e2eeEnabled ? -1 : 1;
230 }
231 if (!local && Boolean(oldParticipant.e2eeSupported) !== e2eeSupported) {
232 state.numberOfParticipantsNotSupportingE2EE += e2eeSupported ? -1 : 1;
233 }
234 }
235
236 return {
237 ...state
238 };
239 }
240 case SCREENSHARE_PARTICIPANT_NAME_CHANGED: {
241 const { id, name } = action;
242
243 if (state.sortedRemoteVirtualScreenshareParticipants.has(id)) {
244 state.sortedRemoteVirtualScreenshareParticipants.delete(id);
245
246 const sortedRemoteVirtualScreenshareParticipants = [ ...state.sortedRemoteVirtualScreenshareParticipants ];
247
248 sortedRemoteVirtualScreenshareParticipants.push([ id, name ]);
249 sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
250
251 state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
252 }
253
254 return { ...state };
255 }
256
257 case PARTICIPANT_JOINED: {
258 const participant = _participantJoined(action);
259 const {
260 fakeParticipant,
261 id,
262 name,
263 pinned,
264 sources
265 } = participant;
266 const { pinnedParticipant, dominantSpeaker } = state;
267
268 if (pinned) {
269 if (pinnedParticipant) {
270 _updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
271 }
272
273 state.pinnedParticipant = id;
274 }
275
276 if (participant.dominantSpeaker) {
277 if (dominantSpeaker) {
278 _updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
279 }
280 state.dominantSpeaker = id;
281 }
282
283 if (!fakeParticipant) {
284 const isModerator = isParticipantModerator(participant);
285
286 if (!isModerator) {
287 state.numberOfNonModeratorParticipants += 1;
288 }
289
290 const { e2eeEnabled, e2eeSupported } = participant as IParticipant;
291
292 if (!e2eeEnabled) {
293 state.numberOfParticipantsDisabledE2EE += 1;
294 }
295
296 if (!participant.local && !e2eeSupported) {
297 state.numberOfParticipantsNotSupportingE2EE += 1;
298 }
299 }
300
301 if (participant.local) {
302 return {
303 ...state,
304 local: participant
305 };
306 }
307
308 if (isLocalScreenshareParticipant(participant)) {
309 return {
310 ...state,
311 localScreenShare: participant
312 };
313 }
314
315 state.remote.set(id, participant);
316
317 if (sources?.size) {
318 const videoSources: Map<string, ISourceInfo> | undefined = sources.get(MEDIA_TYPE.VIDEO);
319
320 if (videoSources?.size) {
321 const newRemoteVideoSources = new Set(state.remoteVideoSources);
322
323 for (const source of videoSources.keys()) {
324 newRemoteVideoSources.add(source);
325 }
326 state.remoteVideoSources = newRemoteVideoSources;
327 }
328 }
329
330 // Insert the new participant.
331 const displayName = _getDisplayName(state, name);
332 const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);
333
334 sortedRemoteParticipants.push([ id, displayName ]);
335 sortedRemoteParticipants.sort((a, b) => a[1].localeCompare(b[1]));
336
337 // The sort order of participants is preserved since Map remembers the original insertion order of the keys.
338 state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);
339
340 if (isRemoteScreenshareParticipant(participant)) {
341 const sortedRemoteVirtualScreenshareParticipants = [ ...state.sortedRemoteVirtualScreenshareParticipants ];
342
343 sortedRemoteVirtualScreenshareParticipants.push([ id, name ?? '' ]);
344 sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
345
346 state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
347 }
348
349 // Exclude the screenshare participant from the fake participant count to avoid duplicates.
350 if (fakeParticipant && !isScreenShareParticipant(participant)) {
351 state.fakeParticipants.set(id, participant);
352 }
353
354 return { ...state };
355
356 }
357 case PARTICIPANT_LEFT: {
358 // XXX A remote participant is uniquely identified by their id in a
359 // specific JitsiConference instance. The local participant is uniquely
360 // identified by the very fact that there is only one local participant
361 // (and the fact that the local participant "joins" at the beginning of
362 // the app and "leaves" at the end of the app).
363 const { conference, id } = action.participant;
364 const {
365 fakeParticipants,
366 sortedRemoteVirtualScreenshareParticipants,
367 remote,
368 local,
369 localScreenShare,
370 dominantSpeaker,
371 pinnedParticipant
372 } = state;
373 let oldParticipant = remote.get(id);
374 let isLocalScreenShare = false;
375
376 if (oldParticipant?.sources?.size) {
377 const videoSources: Map<string, ISourceInfo> | undefined = oldParticipant.sources.get(MEDIA_TYPE.VIDEO);
378 const newRemoteVideoSources = new Set(state.remoteVideoSources);
379
380 if (videoSources?.size) {
381 for (const source of videoSources.keys()) {
382 newRemoteVideoSources.delete(source);
383 }
384 }
385 state.remoteVideoSources = newRemoteVideoSources;
386 } else if (oldParticipant?.fakeParticipant === FakeParticipant.RemoteScreenShare) {
387 const newRemoteVideoSources = new Set(state.remoteVideoSources);
388
389 newRemoteVideoSources.delete(id);
390 state.remoteVideoSources = newRemoteVideoSources;
391 }
392
393 if (oldParticipant && oldParticipant.conference === conference) {
394 remote.delete(id);
395 } else if (local?.id === id) {
396 oldParticipant = state.local;
397 delete state.local;
398 } else if (localScreenShare?.id === id) {
399 isLocalScreenShare = true;
400 oldParticipant = state.local;
401 delete state.localScreenShare;
402 } else {
403 // no participant found
404 return state;
405 }
406
407 state.sortedRemoteParticipants.delete(id);
408 state.raisedHandsQueue = state.raisedHandsQueue.filter(pid => pid.id !== id);
409
410 if (dominantSpeaker === id) {
411 state.dominantSpeaker = undefined;
412 }
413
414 // Remove the participant from the list of speakers.
415 state.speakersList.has(id) && state.speakersList.delete(id);
416
417 if (pinnedParticipant === id) {
418 state.pinnedParticipant = undefined;
419 }
420
421 if (fakeParticipants.has(id)) {
422 fakeParticipants.delete(id);
423 }
424
425 if (sortedRemoteVirtualScreenshareParticipants.has(id)) {
426 sortedRemoteVirtualScreenshareParticipants.delete(id);
427 state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
428 }
429
430 if (oldParticipant && !oldParticipant.fakeParticipant && !isLocalScreenShare) {
431 const { e2eeEnabled, e2eeSupported } = oldParticipant;
432
433 if (!isParticipantModerator(oldParticipant)) {
434 state.numberOfNonModeratorParticipants -= 1;
435 }
436
437 if (!e2eeEnabled) {
438 state.numberOfParticipantsDisabledE2EE -= 1;
439 }
440
441 if (!oldParticipant.local && !e2eeSupported) {
442 state.numberOfParticipantsNotSupportingE2EE -= 1;
443 }
444 }
445
446 return { ...state };
447 }
448 case PARTICIPANT_SOURCES_UPDATED: {
449 const { id, sources } = action.participant;
450 const participant = state.remote.get(id);
451
452 if (participant) {
453 participant.sources = sources;
454 const videoSources: Map<string, ISourceInfo> = sources.get(MEDIA_TYPE.VIDEO);
455
456 if (videoSources?.size) {
457 const newRemoteVideoSources = new Set(state.remoteVideoSources);
458
459 for (const source of videoSources.keys()) {
460 newRemoteVideoSources.add(source);
461 }
462 state.remoteVideoSources = newRemoteVideoSources;
463 }
464 }
465
466 return { ...state };
467 }
468 case RAISE_HAND_UPDATED: {
469 return {
470 ...state,
471 raisedHandsQueue: action.queue
472 };
473 }
474 case OVERWRITE_PARTICIPANT_NAME: {
475 const { id, name } = action;
476
477 return {
478 ...state,
479 overwrittenNameList: {
480 ...state.overwrittenNameList,
481 [id]: name
482 }
483 };
484 }
485 }
486
487 return state;
488 });
489
490 /**
491 * Returns the participant's display name, default string if display name is not set on the participant.
492 *
493 * @param {Object} state - The local participant redux state.
494 * @param {string} name - The display name of the participant.
495 * @returns {string}
496 */
497 function _getDisplayName(state: Object, name?: string): string {
498 // @ts-ignore
499 const config = state['features/base/config'];
500
501 return name ?? (config?.defaultRemoteDisplayName || 'Fellow Jitster');
502 }
503
504 /**
505 * Reducer function for a single participant.
506 *
507 * @param {IParticipant|undefined} state - Participant to be modified.
508 * @param {Object} action - Action object.
509 * @param {string} action.type - Type of action.
510 * @param {IParticipant} action.participant - Information about participant to be
511 * added/modified.
512 * @param {JitsiConference} action.conference - Conference instance.
513 * @private
514 * @returns {IParticipant}
515 */
516 function _participant(state: IParticipant | ILocalParticipant = { id: '' },
517 action: AnyAction): IParticipant | ILocalParticipant {
518 switch (action.type) {
519 case SET_LOADABLE_AVATAR_URL:
520 case PARTICIPANT_UPDATED: {
521 const { participant } = action; // eslint-disable-line no-shadow
522
523 const newState = { ...state };
524
525 for (const key in participant) {
526 if (participant.hasOwnProperty(key)
527 && PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE.indexOf(key)
528 === -1) {
529 // @ts-ignore
530 newState[key] = participant[key];
531 }
532 }
533
534 return newState;
535 }
536 }
537
538 return state;
539 }
540
541 /**
542 * Reduces a specific redux action of type {@link PARTICIPANT_JOINED} in the
543 * feature base/participants.
544 *
545 * @param {Action} action - The redux action of type {@code PARTICIPANT_JOINED}
546 * to reduce.
547 * @private
548 * @returns {Object} The new participant derived from the payload of the
549 * specified {@code action} to be added into the redux state of the feature
550 * base/participants after the reduction of the specified
551 * {@code action}.
552 */
553 function _participantJoined({ participant }: { participant: IParticipant; }) {
554 const {
555 avatarURL,
556 botType,
557 dominantSpeaker,
558 email,
559 fakeParticipant,
560 isReplacing,
561 loadableAvatarUrl,
562 local,
563 name,
564 pinned,
565 presence,
566 role,
567 sources
568 } = participant;
569 let { conference, id } = participant;
570
571 if (local) {
572 // conference
573 //
574 // XXX The local participant is not identified in association with a
575 // JitsiConference because it is identified by the very fact that it is
576 // the local participant.
577 conference = undefined;
578
579 // id
580 id || (id = LOCAL_PARTICIPANT_DEFAULT_ID);
581 }
582
583 return {
584 avatarURL,
585 botType,
586 conference,
587 dominantSpeaker: dominantSpeaker || false,
588 email,
589 fakeParticipant,
590 id,
591 isReplacing,
592 loadableAvatarUrl,
593 local: local || false,
594 name,
595 pinned: pinned || false,
596 presence,
597 role: role || PARTICIPANT_ROLE.NONE,
598 sources
599 };
600 }
601
602 /**
603 * Updates a specific property for a participant.
604 *
605 * @param {State} state - The redux state.
606 * @param {string} id - The ID of the participant.
607 * @param {string} property - The property to update.
608 * @param {*} value - The new value.
609 * @returns {boolean} - True if a participant was updated and false otherwise.
610 */
611 function _updateParticipantProperty(state: IParticipantsState, id: string, property: string, value: boolean) {
612 const { remote, local, localScreenShare } = state;
613
614 if (remote.has(id)) {
615 remote.set(id, set(remote.get(id) ?? {
616 id: '',
617 name: ''
618 }, property as keyof IParticipant, value));
619
620 return true;
621 } else if (local?.id === id || local?.id === 'local') {
622 // The local participant's ID can chance from something to "local" when
623 // not in a conference.
624 state.local = set(local, property as keyof ILocalParticipant, value);
625
626 return true;
627
628 } else if (localScreenShare?.id === id) {
629 state.localScreenShare = set(localScreenShare, property as keyof IParticipant, value);
630
631 return true;
632 }
633
634 return false;
635 }