_setupListeners()

in conference.js [2014:2560]


    _setupListeners() {
        // add local streams when joined to the conference
        room.on(JitsiConferenceEvents.CONFERENCE_JOINED, () => {
            this._onConferenceJoined();
        });
        room.on(JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS, () => {
            APP.store.dispatch(setPrejoinPageVisibility(false));
        });

        room.on(
            JitsiConferenceEvents.CONFERENCE_LEFT,
            (...args) => {
                APP.store.dispatch(conferenceTimestampChanged(0));
                APP.store.dispatch(conferenceLeft(room, ...args));
            });

        room.on(
            JitsiConferenceEvents.CONFERENCE_UNIQUE_ID_SET,
            (...args) => {
                // Preserve the sessionId so that the value is accessible even after room
                // is disconnected.
                room.sessionId = room.getMeetingUniqueId();
                APP.store.dispatch(conferenceUniqueIdSet(room, ...args));
            });

        room.on(
            JitsiConferenceEvents.AUTH_STATUS_CHANGED,
            (authEnabled, authLogin) =>
                APP.store.dispatch(authStatusChanged(authEnabled, authLogin)));

        room.on(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED, user => {
            APP.store.dispatch(updateRemoteParticipantFeatures(user));
        });
        room.on(JitsiConferenceEvents.USER_JOINED, (id, user) => {
            // The logic shared between RN and web.
            commonUserJoinedHandling(APP.store, room, user);

            if (user.isHidden()) {
                return;
            }

            APP.store.dispatch(updateRemoteParticipantFeatures(user));
            logger.log(`USER ${id} connected:`, user);
            APP.UI.addUser(user);
        });

        room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
            // The logic shared between RN and web.
            commonUserLeftHandling(APP.store, room, user);

            if (user.isHidden()) {
                return;
            }

            logger.log(`USER ${id} LEFT:`, user);
        });

        room.on(JitsiConferenceEvents.USER_STATUS_CHANGED, (id, status) => {
            APP.store.dispatch(participantPresenceChanged(id, status));

            const user = room.getParticipantById(id);

            if (user) {
                APP.UI.updateUserStatus(user, status);
            }
        });

        room.on(JitsiConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
            if (this.isLocalId(id)) {
                logger.info(`My role changed, new role: ${role}`);

                APP.store.dispatch(localParticipantRoleChanged(role));
                APP.API.notifyUserRoleChanged(id, role);
            } else {
                APP.store.dispatch(participantRoleChanged(id, role));
            }
        });

        room.on(JitsiConferenceEvents.TRACK_ADDED, track => {
            if (!track || track.isLocal()) {
                return;
            }

            APP.store.dispatch(trackAdded(track));
        });

        room.on(JitsiConferenceEvents.TRACK_REMOVED, track => {
            if (!track || track.isLocal()) {
                return;
            }

            APP.store.dispatch(trackRemoved(track));
        });

        room.on(JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, (id, lvl) => {
            const localAudio = getLocalJitsiAudioTrack(APP.store.getState());
            let newLvl = lvl;

            if (this.isLocalId(id) && localAudio?.isMuted()) {
                newLvl = 0;
            }

            if (config.debug) {
                this.audioLevelsMap[id] = newLvl;
                if (config.debugAudioLevels) {
                    logger.log(`AudioLevel:${id}/${newLvl}`);
                }
            }

            APP.UI.setAudioLevel(id, newLvl);
        });

        room.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, (track, participantThatMutedUs) => {
            if (participantThatMutedUs) {
                APP.store.dispatch(participantMutedUs(participantThatMutedUs, track));
                if (this.isSharingScreen && track.isVideoTrack()) {
                    logger.debug('TRACK_MUTE_CHANGED while screen sharing');
                    this._turnScreenSharingOff(false);
                }
            }
        });

        room.on(JitsiConferenceEvents.SUBJECT_CHANGED,
            subject => APP.store.dispatch(conferenceSubjectChanged(subject)));

        room.on(
            JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
            (leavingIds, enteringIds) =>
                APP.UI.handleLastNEndpoints(leavingIds, enteringIds));

        room.on(
            JitsiConferenceEvents.P2P_STATUS,
            (jitsiConference, p2p) =>
                APP.store.dispatch(p2pStatusChanged(p2p)));

        room.on(
            JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
            (id, connectionStatus) => APP.store.dispatch(
                participantConnectionStatusChanged(id, connectionStatus)));

        room.on(
            JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
            (dominant, previous) => APP.store.dispatch(dominantSpeakerChanged(dominant, previous, room)));

        room.on(
            JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP,
            conferenceTimestamp => APP.store.dispatch(conferenceTimestampChanged(conferenceTimestamp)));

        room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
            APP.store.dispatch(localParticipantConnectionStatusChanged(
                JitsiParticipantConnectionStatus.INTERRUPTED));
        });

        room.on(JitsiConferenceEvents.CONNECTION_RESTORED, () => {
            APP.store.dispatch(localParticipantConnectionStatusChanged(
                JitsiParticipantConnectionStatus.ACTIVE));
        });

        room.on(
            JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
            (id, displayName) => {
                const formattedDisplayName
                    = getNormalizedDisplayName(displayName);
                const state = APP.store.getState();
                const {
                    defaultRemoteDisplayName
                } = state['features/base/config'];

                APP.store.dispatch(participantUpdated({
                    conference: room,
                    id,
                    name: formattedDisplayName
                }));
                APP.API.notifyDisplayNameChanged(id, {
                    displayName: formattedDisplayName,
                    formattedDisplayName:
                        appendSuffix(
                            formattedDisplayName
                                || defaultRemoteDisplayName)
                });
            }
        );
        room.on(
            JitsiConferenceEvents.BOT_TYPE_CHANGED,
            (id, botType) => {

                APP.store.dispatch(participantUpdated({
                    conference: room,
                    id,
                    botType
                }));
            }
        );

        room.on(
            JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
            (...args) => {
                APP.store.dispatch(endpointMessageReceived(...args));
                if (args && args.length >= 2) {
                    const [ sender, eventData ] = args;

                    if (eventData.name === ENDPOINT_TEXT_MESSAGE_NAME) {
                        APP.API.notifyEndpointTextMessageReceived({
                            senderInfo: {
                                jid: sender._jid,
                                id: sender._id
                            },
                            eventData
                        });
                    }
                }
            });

        room.on(
            JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
            (...args) => APP.store.dispatch(nonParticipantMessageReceived(...args)));

        room.on(
            JitsiConferenceEvents.LOCK_STATE_CHANGED,
            (...args) => APP.store.dispatch(lockStateChanged(room, ...args)));

        room.on(JitsiConferenceEvents.KICKED, (participant, reason, isReplaced) => {
            if (isReplaced) {
                // this event triggers when the local participant is kicked, `participant`
                // is the kicker. In replace participant case, kicker is undefined,
                // as the server initiated it. We mark in store the local participant
                // as being replaced based on jwt.
                const localParticipant = getLocalParticipant(APP.store.getState());

                APP.store.dispatch(participantUpdated({
                    conference: room,
                    id: localParticipant.id,
                    isReplaced
                }));

                // we send readyToClose when kicked participant is replace so that
                // embedding app can choose to dispose the iframe API on the handler.
                APP.API.notifyReadyToClose();
            }
            APP.store.dispatch(kickedOut(room, participant));
        });

        room.on(JitsiConferenceEvents.PARTICIPANT_KICKED, (kicker, kicked) => {
            APP.store.dispatch(participantKicked(kicker, kicked));
        });

        room.on(JitsiConferenceEvents.SUSPEND_DETECTED, () => {
            APP.store.dispatch(suspendDetected());
        });

        room.on(
            JitsiConferenceEvents.AUDIO_UNMUTE_PERMISSIONS_CHANGED,
            disableAudioMuteChange => {
                const muted = isAudioMuted(APP.store.getState());

                // Disable the mute button only if its muted.
                if (!disableAudioMuteChange || (disableAudioMuteChange && muted)) {
                    APP.store.dispatch(setAudioUnmutePermissions(disableAudioMuteChange));
                }
            });
        room.on(
            JitsiConferenceEvents.VIDEO_UNMUTE_PERMISSIONS_CHANGED,
            disableVideoMuteChange => {
                const muted = isVideoMuted(APP.store.getState());

                // Disable the mute button only if its muted.
                if (!disableVideoMuteChange || (disableVideoMuteChange && muted)) {
                    APP.store.dispatch(setVideoUnmutePermissions(disableVideoMuteChange));
                }
            });

        APP.UI.addListener(UIEvents.AUDIO_MUTED, muted => {
            this.muteAudio(muted);
        });
        APP.UI.addListener(UIEvents.VIDEO_MUTED, muted => {
            this.muteVideo(muted);
        });

        room.addCommandListener(this.commands.defaults.ETHERPAD,
            ({ value }) => {
                APP.UI.initEtherpad(value);
            }
        );

        APP.UI.addListener(UIEvents.EMAIL_CHANGED,
            this.changeLocalEmail.bind(this));
        room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
            APP.store.dispatch(participantUpdated({
                conference: room,
                id: from,
                email: data.value
            }));
        });

        room.addCommandListener(
            this.commands.defaults.AVATAR_URL,
            (data, from) => {
                APP.store.dispatch(
                    participantUpdated({
                        conference: room,
                        id: from,
                        avatarURL: data.value
                    }));
            });

        APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
            this.changeLocalDisplayName.bind(this));

        room.on(
            JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
            ({ audio, video }) => {
                APP.store.dispatch(
                    onStartMutedPolicyChanged(audio, video));
            }
        );
        room.on(JitsiConferenceEvents.STARTED_MUTED, () => {
            const audioMuted = room.isStartAudioMuted();
            const videoMuted = room.isStartVideoMuted();
            const localTracks = getLocalTracks(APP.store.getState()['features/base/tracks']);
            const promises = [];

            APP.store.dispatch(setAudioMuted(audioMuted));
            APP.store.dispatch(setVideoMuted(videoMuted));

            // Remove the tracks from the peerconnection.
            for (const track of localTracks) {
                // Always add the track on Safari because of a known issue where audio playout doesn't happen
                // if the user joins audio and video muted, i.e., if there is no local media capture.
                if (audioMuted && track.jitsiTrack?.getType() === MEDIA_TYPE.AUDIO && !browser.isWebKitBased()) {
                    promises.push(this.useAudioStream(null));
                }
                if (videoMuted && track.jitsiTrack?.getType() === MEDIA_TYPE.VIDEO) {
                    promises.push(this.useVideoStream(null));
                }
            }

            Promise.allSettled(promises)
                .then(() => {
                    APP.store.dispatch(showNotification({
                        titleKey: 'notify.mutedTitle',
                        descriptionKey: 'notify.muted'
                    }, NOTIFICATION_TIMEOUT_TYPE.SHORT));
                });
        });

        room.on(
            JitsiConferenceEvents.DATA_CHANNEL_OPENED, () => {
                APP.store.dispatch(dataChannelOpened());
            }
        );

        // call hangup
        APP.UI.addListener(UIEvents.HANGUP, () => {
            this.hangup(true);
        });

        // logout
        APP.UI.addListener(UIEvents.LOGOUT, () => {
            AuthHandler.logout(room).then(url => {
                if (url) {
                    UIUtil.redirect(url);
                } else {
                    this.hangup(true);
                }
            });
        });

        APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
            AuthHandler.authenticate(room);
        });

        APP.UI.addListener(
            UIEvents.VIDEO_DEVICE_CHANGED,
            cameraDeviceId => {
                const localVideo = getLocalJitsiVideoTrack(APP.store.getState());
                const videoWasMuted = this.isLocalVideoMuted();

                sendAnalytics(createDeviceChangedEvent('video', 'input'));

                // If both screenshare and video are in progress, restart the
                // presenter mode with the new camera device.
                if (this.isSharingScreen && !videoWasMuted) {
                    const { height } = localVideo.track.getSettings();

                    // dispose the existing presenter track and create a new
                    // camera track.
                    // FIXME JitsiLocalTrack.dispose is async and should be waited for
                    this.localPresenterVideo && this.localPresenterVideo.dispose();
                    this.localPresenterVideo = null;

                    return this._createPresenterStreamEffect(height, cameraDeviceId)
                        .then(effect => localVideo.setEffect(effect))
                        .then(() => {
                            this.setVideoMuteStatus();
                            logger.log('Switched local video device while screen sharing and the video is unmuted');
                            this._updateVideoDeviceId();
                        })
                        .catch(err => APP.store.dispatch(notifyCameraError(err)));

                // If screenshare is in progress but video is muted, update the default device
                // id for video, dispose the existing presenter track and create a new effect
                // that can be applied on un-mute.
                } else if (this.isSharingScreen && videoWasMuted) {
                    logger.log('Switched local video device: while screen sharing and the video is muted');
                    const { height } = localVideo.track.getSettings();

                    this._updateVideoDeviceId();

                    // FIXME JitsiLocalTrack.dispose is async and should be waited for
                    this.localPresenterVideo && this.localPresenterVideo.dispose();
                    this.localPresenterVideo = null;
                    this._createPresenterStreamEffect(height, cameraDeviceId);

                // if there is only video, switch to the new camera stream.
                } else {
                    createLocalTracksF({
                        devices: [ 'video' ],
                        cameraDeviceId,
                        micDeviceId: null
                    })
                    .then(([ stream ]) => {
                        // if we are in audio only mode or video was muted before
                        // changing device, then mute
                        if (this.isAudioOnly() || videoWasMuted) {
                            return stream.mute()
                                .then(() => stream);
                        }

                        return stream;
                    })
                    .then(stream => {
                        logger.log('Switching the local video device.');

                        return this.useVideoStream(stream);
                    })
                    .then(() => {
                        logger.log('Switched local video device.');
                        this._updateVideoDeviceId();
                    })
                    .catch(error => {
                        logger.error(`Switching the local video device failed: ${error}`);

                        return APP.store.dispatch(notifyCameraError(error));
                    });
                }
            }
        );

        APP.UI.addListener(
            UIEvents.AUDIO_DEVICE_CHANGED,
            micDeviceId => {
                const audioWasMuted = this.isLocalAudioMuted();

                // When the 'default' mic needs to be selected, we need to
                // pass the real device id to gUM instead of 'default' in order
                // to get the correct MediaStreamTrack from chrome because of the
                // following bug.
                // https://bugs.chromium.org/p/chromium/issues/detail?id=997689
                const hasDefaultMicChanged = micDeviceId === 'default';

                sendAnalytics(createDeviceChangedEvent('audio', 'input'));
                createLocalTracksF({
                    devices: [ 'audio' ],
                    cameraDeviceId: null,
                    micDeviceId: hasDefaultMicChanged
                        ? getDefaultDeviceId(APP.store.getState(), 'audioInput')
                        : micDeviceId
                })
                .then(([ stream ]) => {
                    // if audio was muted before changing the device, mute
                    // with the new device
                    if (audioWasMuted) {
                        return stream.mute()
                            .then(() => stream);
                    }

                    return stream;
                })
                .then(async stream => {
                    // In case screen sharing audio is also shared we mix it with new input stream. The old _mixerEffect
                    // will be cleaned up when the existing track is replaced.
                    if (this._mixerEffect) {
                        this._mixerEffect = new AudioMixerEffect(this._desktopAudioStream);

                        await stream.setEffect(this._mixerEffect);
                    }

                    return this.useAudioStream(stream);
                })
                .then(() => {
                    const localAudio = getLocalJitsiAudioTrack(APP.store.getState());

                    if (localAudio && hasDefaultMicChanged) {
                        // workaround for the default device to be shown as selected in the
                        // settings even when the real device id was passed to gUM because of the
                        // above mentioned chrome bug.
                        localAudio._realDeviceId = localAudio.deviceId = 'default';
                    }
                    logger.log(`switched local audio device: ${localAudio?.getDeviceId()}`);

                    this._updateAudioDeviceId();
                })
                .catch(err => {
                    APP.store.dispatch(notifyMicError(err));
                });
            }
        );

        APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly => {

            // FIXME On web video track is stored both in redux and in
            // 'localVideo' field, video is attempted to be unmuted twice when
            // turning off the audio only mode. This will crash the app with
            // 'unmute operation is already in progress'.
            // Because there's no logic in redux about creating new track in
            // case unmute when not track exists the things have to go through
            // muteVideo logic in such case.
            const tracks = APP.store.getState()['features/base/tracks'];
            const isTrackInRedux
                = Boolean(
                    tracks.find(
                        track => track.jitsiTrack
                            && track.jitsiTrack.getType() === 'video'));

            if (!isTrackInRedux) {
                this.muteVideo(audioOnly);
            }

            // Immediately update the UI by having remote videos and the large
            // video update themselves instead of waiting for some other event
            // to cause the update, usually PARTICIPANT_CONN_STATUS_CHANGED.
            // There is no guarantee another event will trigger the update
            // immediately and in all situations, for example because a remote
            // participant is having connection trouble so no status changes.
            const displayedUserId = APP.UI.getLargeVideoID();

            if (displayedUserId) {
                APP.UI.updateLargeVideo(displayedUserId, true);
            }
        });

        APP.UI.addListener(
            UIEvents.TOGGLE_SCREENSHARING, ({ enabled, audioOnly, ignoreDidHaveVideo }) => {
                this.toggleScreenSharing(enabled, { audioOnly }, ignoreDidHaveVideo);
            }
        );
    },