123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- // @flow
-
- import { ReducerRegistry, set } from '../redux';
-
- import {
- DOMINANT_SPEAKER_CHANGED,
- PARTICIPANT_ID_CHANGED,
- PARTICIPANT_JOINED,
- PARTICIPANT_LEFT,
- PARTICIPANT_UPDATED,
- PIN_PARTICIPANT,
- SET_LOADABLE_AVATAR_URL
- } from './actionTypes';
- import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
- import { isParticipantModerator } from './functions';
-
- /**
- * 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.
- */
-
- declare var APP: Object;
-
- /**
- * 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[]}
- */
- 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'
- ];
-
- const DEFAULT_STATE = {
- haveParticipantWithScreenSharingFeature: false,
- dominantSpeaker: undefined,
- everyoneIsModerator: false,
- pinnedParticipant: undefined,
- local: undefined,
- remote: new Map(),
- fakeParticipants: new Map()
- };
-
- /**
- * 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 = DEFAULT_STATE, action) => {
- switch (action.type) {
- case PARTICIPANT_ID_CHANGED: {
- const { local } = state;
-
- if (local) {
- state.local = {
- ...local,
- id: action.newValue
- };
-
- return {
- ...state
- };
- }
-
- return state;
- }
- case DOMINANT_SPEAKER_CHANGED: {
- const { participant } = action;
- const { id } = participant;
- const { dominantSpeaker } = state;
-
- // Only one dominant speaker is allowed.
- if (dominantSpeaker) {
- _updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
- }
-
- if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
- return {
- ...state,
- dominantSpeaker: id
- };
- }
-
- 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);
- }
-
- if (_updateParticipantProperty(state, id, 'pinned', true)) {
- return {
- ...state,
- pinnedParticipant: id
- };
- }
-
- delete state.pinnedParticipant;
-
- return {
- ...state
- };
- }
- case SET_LOADABLE_AVATAR_URL:
- 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
- };
- }
- case PARTICIPANT_JOINED: {
- const participant = _participantJoined(action);
- const { pinnedParticipant, dominantSpeaker } = state;
-
- if (participant.pinned) {
- if (pinnedParticipant) {
- _updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
- }
-
- state.pinnedParticipant = participant.id;
- }
-
- if (participant.dominantSpeaker) {
- if (dominantSpeaker) {
- _updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
- }
- state.dominantSpeaker = participant.id;
- }
-
- 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
- };
- }
-
- state.remote.set(participant.id, participant);
-
- if (participant.isFakeParticipant) {
- state.fakeParticipants.set(participant.id, participant);
- }
-
- return { ...state };
-
- }
- 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;
- const { fakeParticipants, remote, local, dominantSpeaker, pinnedParticipant } = state;
- let oldParticipant = remote.get(id);
-
- if (oldParticipant && oldParticipant.conference === conference) {
- remote.delete(id);
- } else if (local?.id === id) {
- oldParticipant = state.local;
- delete state.local;
- } else {
- // no participant found
- return state;
- }
-
- if (!state.everyoneIsModerator && !isParticipantModerator(oldParticipant)) {
- state.everyoneIsModerator = _isEveryoneModerator(state);
- }
-
- const { features = {} } = oldParticipant || {};
-
- 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;
- }
-
- if (pinnedParticipant === id) {
- state.pinnedParticipant = undefined;
- }
-
- if (fakeParticipants.has(id)) {
- fakeParticipants.delete(id);
- }
-
- return { ...state };
- }
- }
-
- return state;
- });
-
- /**
- * 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;
- }
-
-
- /**
- * 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.
- */
- function _updateParticipantProperty(state, id, property, value) {
- const { remote, local } = state;
-
- if (remote.has(id)) {
- remote.set(id, set(remote.get(id), property, value));
-
- return true;
- } else if (local?.id === id) {
- state.local = set(local, property, value);
-
- return true;
- }
-
- return false;
- }
-
- /**
- * 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
- * @returns {Participant}
- */
- function _participant(state: Object = {}, action) {
- switch (action.type) {
- case SET_LOADABLE_AVATAR_URL:
- case PARTICIPANT_UPDATED: {
- const { participant } = action; // eslint-disable-line no-shadow
-
- const newState = { ...state };
-
- for (const key in participant) {
- if (participant.hasOwnProperty(key)
- && PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE.indexOf(key)
- === -1) {
- newState[key] = participant[key];
- }
- }
-
- return newState;
- }
- }
-
- 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,
- botType,
- connectionStatus,
- dominantSpeaker,
- email,
- isFakeParticipant,
- isReplacing,
- isJigasi,
- loadableAvatarUrl,
- local,
- name,
- pinned,
- presence,
- role
- } = participant;
- let { conference, id } = participant;
-
- 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;
-
- // id
- id || (id = LOCAL_PARTICIPANT_DEFAULT_ID);
- }
-
- return {
- avatarURL,
- botType,
- conference,
- connectionStatus,
- dominantSpeaker: dominantSpeaker || false,
- email,
- id,
- isFakeParticipant,
- isReplacing,
- isJigasi,
- loadableAvatarUrl,
- local: local || false,
- name,
- pinned: pinned || false,
- presence,
- role: role || PARTICIPANT_ROLE.NONE
- };
- }
|