fix: leaking listeners while waiting on auth dialog (#11288)

* fix: Drop duplicate call of wait for owner.

* fix: Fixes leaking listeners while waiting for host to join.

While waiting for the host to join on the dialog we attempt to join over and over again till we are admitted to enter the meeting. While doing that authRequired flag is on, and we were adding listeners on and on.

* feat: Introduces conference join in progress action.

This event is coming from lib-jitsi-meet and is fired when we receive the first presence of series when joining. It is always fired before joined event.

* fix: Moves testing middleware to use CONFERENCE_JOIN_IN_PROGRESS.

* fix: Moves follow-me middleware to use CONFERENCE_JOIN_IN_PROGRESS.

* fix: Moves some polls logic to middleware and use CONFERENCE_JOIN_IN_PROGRESS.

* fix: Moves reactions middleware to use CONFERENCE_JOIN_IN_PROGRESS.

* fix: Moves recordings middleware to use CONFERENCE_JOIN_IN_PROGRESS.

* fix: Moves shared-video middleware to use CONFERENCE_JOIN_IN_PROGRESS.

* fix: Moves videosipgw middleware to use CONFERENCE_JOIN_IN_PROGRESS.

* squash: Fix comments.

* fix: Fixes join in progress on web.

* fix: Moves variable extraction inside handlers.

* fix: Moves variable extraction inside handlers again.

* fix: Moves etherpad middleware to use CONFERENCE_JOIN_IN_PROGRESS.
This commit is contained in:
Дамян Минков 2022-04-05 21:13:39 -05:00 committed by GitHub
parent 33db511d93
commit a99532b0d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 311 additions and 250 deletions

View File

@ -37,6 +37,7 @@ import {
commonUserLeftHandling, commonUserLeftHandling,
conferenceFailed, conferenceFailed,
conferenceJoined, conferenceJoined,
conferenceJoinInProgress,
conferenceLeft, conferenceLeft,
conferenceSubjectChanged, conferenceSubjectChanged,
conferenceTimestampChanged, conferenceTimestampChanged,
@ -142,8 +143,7 @@ import {
initPrejoin, initPrejoin,
isPrejoinPageVisible, isPrejoinPageVisible,
makePrecallTest, makePrecallTest,
setJoiningInProgress, setJoiningInProgress
setPrejoinPageVisibility
} from './react/features/prejoin'; } from './react/features/prejoin';
import { disableReceiver, stopReceiver } from './react/features/remote-control'; import { disableReceiver, stopReceiver } from './react/features/remote-control';
import { setScreenAudioShareState, isScreenAudioShared } from './react/features/screen-share/'; import { setScreenAudioShareState, isScreenAudioShared } from './react/features/screen-share/';
@ -2068,9 +2068,9 @@ export default {
room.on(JitsiConferenceEvents.CONFERENCE_JOINED, () => { room.on(JitsiConferenceEvents.CONFERENCE_JOINED, () => {
this._onConferenceJoined(); this._onConferenceJoined();
}); });
room.on(JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS, () => { room.on(
APP.store.dispatch(setPrejoinPageVisibility(false)); JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS,
}); () => APP.store.dispatch(conferenceJoinInProgress(room)));
room.on( room.on(
JitsiConferenceEvents.CONFERENCE_LEFT, JitsiConferenceEvents.CONFERENCE_LEFT,

View File

@ -6,7 +6,10 @@ import { openConnection } from '../../../connection';
import { import {
openAuthDialog, openAuthDialog,
openLoginDialog } from '../../../react/features/authentication/actions.web'; openLoginDialog } from '../../../react/features/authentication/actions.web';
import { WaitForOwnerDialog } from '../../../react/features/authentication/components'; import {
LoginDialog,
WaitForOwnerDialog
} from '../../../react/features/authentication/components';
import { import {
isTokenAuthEnabled, isTokenAuthEnabled,
getTokenAuthUrl getTokenAuthUrl
@ -16,7 +19,7 @@ import { isDialogOpen } from '../../../react/features/base/dialog';
import { setJWT } from '../../../react/features/base/jwt'; import { setJWT } from '../../../react/features/base/jwt';
import UIUtil from '../util/UIUtil'; import UIUtil from '../util/UIUtil';
import LoginDialog from './LoginDialog'; import ExternalLoginDialog from './LoginDialog';
let externalAuthWindow; let externalAuthWindow;
@ -51,7 +54,7 @@ function doExternalAuth(room, lockPassword) {
getUrl = room.getExternalAuthUrl(true); getUrl = room.getExternalAuthUrl(true);
} }
getUrl.then(url => { getUrl.then(url => {
externalAuthWindow = LoginDialog.showExternalAuthDialog( externalAuthWindow = ExternalLoginDialog.showExternalAuthDialog(
url, url,
() => { () => {
externalAuthWindow = null; externalAuthWindow = null;
@ -187,7 +190,7 @@ function authenticate(room: Object, lockPassword: string) {
* @param {string} [lockPassword] password to use if the conference is locked * @param {string} [lockPassword] password to use if the conference is locked
*/ */
function requireAuth(room: Object, lockPassword: string) { function requireAuth(room: Object, lockPassword: string) {
if (!isDialogOpen(APP.store, WaitForOwnerDialog)) { if (isDialogOpen(APP.store, WaitForOwnerDialog) || isDialogOpen(APP.store, LoginDialog)) {
return; return;
} }

View File

@ -37,7 +37,6 @@ import '../lobby/middleware';
import '../notifications/middleware'; import '../notifications/middleware';
import '../overlay/middleware'; import '../overlay/middleware';
import '../polls/middleware'; import '../polls/middleware';
import '../polls/subscriber';
import '../reactions/middleware'; import '../reactions/middleware';
import '../recent-list/middleware'; import '../recent-list/middleware';
import '../recording/middleware'; import '../recording/middleware';

View File

@ -22,8 +22,7 @@ import {
import { import {
hideLoginDialog, hideLoginDialog,
openWaitForOwnerDialog, openWaitForOwnerDialog,
stopWaitForOwner, stopWaitForOwner
waitForOwner
} from './actions.web'; } from './actions.web';
import { LoginDialog, WaitForOwnerDialog } from './components'; import { LoginDialog, WaitForOwnerDialog } from './components';
@ -72,7 +71,11 @@ MiddlewareRegistry.register(store => next => action => {
recoverable = error.recoverable; recoverable = error.recoverable;
} }
if (recoverable) { if (recoverable) {
store.dispatch(waitForOwner()); // we haven't migrated all the code from AuthHandler, and we need for now conference.js to trigger
// the dialog to pass all required parameters to WaitForOwnerDialog
// keep it commented, so we do not trigger sending iqs to jicofo twice
// and showing the broken dialog with no handler
// store.dispatch(waitForOwner());
} else { } else {
store.dispatch(stopWaitForOwner()); store.dispatch(stopWaitForOwner());
} }

View File

@ -32,6 +32,17 @@ export const CONFERENCE_FAILED = 'CONFERENCE_FAILED';
*/ */
export const CONFERENCE_JOINED = 'CONFERENCE_JOINED'; export const CONFERENCE_JOINED = 'CONFERENCE_JOINED';
/**
* The type of (redux) action which signals that a specific conference joining is in progress.
* A CONFERENCE_JOINED is guaranteed to follow.
*
* {
* type: CONFERENCE_JOIN_IN_PROGRESS,
* conference: JitsiConference
* }
*/
export const CONFERENCE_JOIN_IN_PROGRESS = 'CONFERENCE_JOIN_IN_PROGRESS';
/** /**
* The type of (redux) action which signals that a specific conference was left. * The type of (redux) action which signals that a specific conference was left.
* *

View File

@ -39,6 +39,7 @@ import { getBackendSafeRoomName } from '../util';
import { import {
AUTH_STATUS_CHANGED, AUTH_STATUS_CHANGED,
CONFERENCE_FAILED, CONFERENCE_FAILED,
CONFERENCE_JOIN_IN_PROGRESS,
CONFERENCE_JOINED, CONFERENCE_JOINED,
CONFERENCE_LEFT, CONFERENCE_LEFT,
CONFERENCE_LOCAL_SUBJECT_CHANGED, CONFERENCE_LOCAL_SUBJECT_CHANGED,
@ -105,6 +106,9 @@ function _addConferenceListeners(conference, dispatch, state) {
conference.on( conference.on(
JitsiConferenceEvents.CONFERENCE_JOINED, JitsiConferenceEvents.CONFERENCE_JOINED,
(...args) => dispatch(conferenceJoined(conference, ...args))); (...args) => dispatch(conferenceJoined(conference, ...args)));
conference.on(
JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS,
(...args) => dispatch(conferenceJoinInProgress(conference, ...args)));
conference.on( conference.on(
JitsiConferenceEvents.CONFERENCE_LEFT, JitsiConferenceEvents.CONFERENCE_LEFT,
(...args) => { (...args) => {
@ -350,6 +354,23 @@ export function conferenceJoined(conference: Object) {
}; };
} }
/**
* Signals that a specific conference join is in progress.
*
* @param {JitsiConference} conference - The JitsiConference instance for which join by the local participant
* is in progress.
* @returns {{
* type: CONFERENCE_JOIN_IN_PROGRESS,
* conference: JitsiConference
* }}
*/
export function conferenceJoinInProgress(conference: Object) {
return {
type: CONFERENCE_JOIN_IN_PROGRESS,
conference
};
}
/** /**
* Signals that a specific conference has been left. * Signals that a specific conference has been left.
* *

View File

@ -2,7 +2,10 @@
import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from '../../../../modules/UI/UIErrors'; import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from '../../../../modules/UI/UIErrors';
import { showNotification, NOTIFICATION_TIMEOUT_TYPE } from '../../notifications'; import { showNotification, NOTIFICATION_TIMEOUT_TYPE } from '../../notifications';
import { setSkipPrejoinOnReload } from '../../prejoin'; import {
setPrejoinPageVisibility,
setSkipPrejoinOnReload
} from '../../prejoin';
import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share'; import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share';
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect'; import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
import { setAudioOnly } from '../audio-only'; import { setAudioOnly } from '../audio-only';
@ -19,7 +22,7 @@ import {
TOGGLE_SCREENSHARING TOGGLE_SCREENSHARING
} from '../tracks'; } from '../tracks';
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from './actionTypes'; import { CONFERENCE_FAILED, CONFERENCE_JOIN_IN_PROGRESS, CONFERENCE_JOINED } from './actionTypes';
import { getCurrentConference } from './functions'; import { getCurrentConference } from './functions';
import './middleware.any'; import './middleware.any';
@ -28,6 +31,11 @@ MiddlewareRegistry.register(store => next => action => {
const { enableForcedReload } = getState()['features/base/config']; const { enableForcedReload } = getState()['features/base/config'];
switch (action.type) { switch (action.type) {
case CONFERENCE_JOIN_IN_PROGRESS: {
dispatch(setPrejoinPageVisibility(false));
break;
}
case CONFERENCE_JOINED: { case CONFERENCE_JOINED: {
if (enableForcedReload) { if (enableForcedReload) {
dispatch(setSkipPrejoinOnReload(false)); dispatch(setSkipPrejoinOnReload(false));

View File

@ -1,6 +1,6 @@
// @flow // @flow
import { CONFERENCE_WILL_JOIN } from '../conference'; import { CONFERENCE_JOIN_IN_PROGRESS } from '../conference/actionTypes';
import { SET_CONFIG } from '../config'; import { SET_CONFIG } from '../config';
import { JitsiConferenceEvents } from '../lib-jitsi-meet'; import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { MiddlewareRegistry } from '../redux'; import { MiddlewareRegistry } from '../redux';
@ -24,7 +24,7 @@ import logger from './logger';
*/ */
MiddlewareRegistry.register(store => next => action => { MiddlewareRegistry.register(store => next => action => {
switch (action.type) { switch (action.type) {
case CONFERENCE_WILL_JOIN: case CONFERENCE_JOIN_IN_PROGRESS:
_bindConferenceConnectionListener(action.conference, store); _bindConferenceConnectionListener(action.conference, store);
break; break;
case SET_CONFIG: { case SET_CONFIG: {

View File

@ -2,6 +2,7 @@
import UIEvents from '../../../service/UI/UIEvents'; import UIEvents from '../../../service/UI/UIEvents';
import { getCurrentConference } from '../base/conference'; import { getCurrentConference } from '../base/conference';
import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux'; import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { TOGGLE_DOCUMENT_EDITING } from './actionTypes'; import { TOGGLE_DOCUMENT_EDITING } from './actionTypes';
@ -21,6 +22,23 @@ const ETHERPAD_COMMAND = 'etherpad';
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
switch (action.type) { switch (action.type) {
case CONFERENCE_JOIN_IN_PROGRESS: {
const { conference } = action;
conference.addCommandListener(ETHERPAD_COMMAND,
({ value }) => {
let url;
const { etherpad_base: etherpadBase } = getState()['features/base/config'];
if (etherpadBase) {
url = new URL(value, etherpadBase).toString();
}
dispatch(setDocumentUrl(url));
}
);
break;
}
case TOGGLE_DOCUMENT_EDITING: { case TOGGLE_DOCUMENT_EDITING: {
if (typeof APP !== 'undefined') { if (typeof APP !== 'undefined') {
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED); APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
@ -39,24 +57,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
*/ */
StateListenerRegistry.register( StateListenerRegistry.register(
state => getCurrentConference(state), state => getCurrentConference(state),
(conference, { dispatch, getState }, previousConference) => { (conference, { dispatch }, previousConference) => {
if (conference) {
conference.addCommandListener(ETHERPAD_COMMAND,
({ value }) => {
let url;
const { etherpad_base: etherpadBase } = getState()['features/base/config'];
if (etherpadBase) {
const u = new URL(value, etherpadBase);
url = u.toString();
}
dispatch(setDocumentUrl(url));
}
);
}
if (previousConference) { if (previousConference) {
dispatch(setDocumentUrl(undefined)); dispatch(setDocumentUrl(undefined));
} }

View File

@ -2,7 +2,7 @@
import _ from 'lodash'; import _ from 'lodash';
import { CONFERENCE_WILL_JOIN } from '../base/conference/actionTypes'; import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
import { import {
getParticipantById, getParticipantById,
getPinnedParticipant, getPinnedParticipant,
@ -61,7 +61,7 @@ let nextOnStageTimer = 0;
*/ */
MiddlewareRegistry.register(store => next => action => { MiddlewareRegistry.register(store => next => action => {
switch (action.type) { switch (action.type) {
case CONFERENCE_WILL_JOIN: { case CONFERENCE_JOIN_IN_PROGRESS: {
const { conference } = action; const { conference } = action;
conference.addCommandListener( conference.addCommandListener(

View File

@ -1,16 +1,95 @@
// @flow // @flow
import { MiddlewareRegistry } from '../base/redux'; import { getCurrentConference } from '../base/conference';
import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { playSound } from '../base/sounds'; import { playSound } from '../base/sounds';
import { INCOMING_MSG_SOUND_ID } from '../chat/constants'; import { INCOMING_MSG_SOUND_ID } from '../chat/constants';
import {
NOTIFICATION_TIMEOUT_TYPE,
NOTIFICATION_TYPE,
showNotification
} from '../notifications';
import { RECEIVE_POLL } from './actionTypes'; import { RECEIVE_POLL } from './actionTypes';
import { clearPolls, receiveAnswer, receivePoll } from './actions';
import {
COMMAND_ANSWER_POLL,
COMMAND_NEW_POLL,
COMMAND_OLD_POLLS
} from './constants';
import type { Answer, Poll } from './types';
/**
* Set up state change listener to perform maintenance tasks when the conference
* is left or failed, e.g. Clear messages or close the chat modal if it's left
* open.
*/
StateListenerRegistry.register(
state => getCurrentConference(state),
(conference, { dispatch }, previousConference) => {
if (conference !== previousConference) {
// conference changed, left or failed...
// clean old polls
dispatch(clearPolls());
}
});
const parsePollData = (pollData): Poll | null => {
if (typeof pollData !== 'object' || pollData === null) {
return null;
}
const { id, senderId, senderName, question, answers } = pollData;
if (typeof id !== 'string' || typeof senderId !== 'string' || typeof senderName !== 'string'
|| typeof question !== 'string' || !(answers instanceof Array)) {
return null;
}
const answersParsed = [];
for (const answer of answers) {
const voters = new Map();
for (const [ voterId, voter ] of Object.entries(answer.voters)) {
if (typeof voter !== 'string') {
return null;
}
voters.set(voterId, voter);
}
answersParsed.push({
name: answer.name,
voters
});
}
return {
changingVote: false,
senderId,
senderName,
question,
showResults: true,
lastVote: null,
answers: answersParsed
};
};
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const result = next(action); const result = next(action);
switch (action.type) { switch (action.type) {
case CONFERENCE_JOIN_IN_PROGRESS: {
const { conference } = action;
conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(_, data) => _handleReceivePollsMessage(data, dispatch));
conference.on(JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(_, data) => _handleReceivePollsMessage(data, dispatch));
break;
}
// Middleware triggered when a poll is received // Middleware triggered when a poll is received
case RECEIVE_POLL: { case RECEIVE_POLL: {
@ -30,3 +109,73 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
return result; return result;
}); });
/**
* Handles receiving of polls message command.
*
* @param {Object} data - The json data carried by the polls message.
* @param {Function} dispatch - The dispatch function.
*
* @returns {void}
*/
function _handleReceivePollsMessage(data, dispatch) {
switch (data.type) {
case COMMAND_NEW_POLL: {
const { question, answers, pollId, senderId, senderName } = data;
const poll = {
changingVote: false,
senderId,
senderName,
showResults: false,
lastVote: null,
question,
answers: answers.map(answer => {
return {
name: answer,
voters: new Map()
};
})
};
dispatch(receivePoll(pollId, poll, true));
dispatch(showNotification({
appearance: NOTIFICATION_TYPE.NORMAL,
titleKey: 'polls.notification.title',
descriptionKey: 'polls.notification.description'
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
break;
}
case COMMAND_ANSWER_POLL: {
const { pollId, answers, voterId, voterName } = data;
const receivedAnswer: Answer = {
voterId,
voterName,
pollId,
answers
};
dispatch(receiveAnswer(pollId, receivedAnswer));
break;
}
case COMMAND_OLD_POLLS: {
const { polls } = data;
for (const pollData of polls) {
const poll = parsePollData(pollData);
if (poll === null) {
console.warn('[features/polls] Invalid old poll data');
} else {
dispatch(receivePoll(pollData.id, poll, false));
}
}
break;
}
}
}

View File

@ -1,130 +0,0 @@
// @flow
import { getCurrentConference } from '../base/conference';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { StateListenerRegistry } from '../base/redux';
import {
NOTIFICATION_TIMEOUT_TYPE,
NOTIFICATION_TYPE,
showNotification
} from '../notifications';
import { clearPolls, receiveAnswer, receivePoll } from './actions';
import { COMMAND_NEW_POLL, COMMAND_ANSWER_POLL, COMMAND_OLD_POLLS } from './constants';
import type { Answer, Poll } from './types';
const parsePollData = (pollData): Poll | null => {
if (typeof pollData !== 'object' || pollData === null) {
return null;
}
const { id, senderId, senderName, question, answers } = pollData;
if (typeof id !== 'string' || typeof senderId !== 'string' || typeof senderName !== 'string'
|| typeof question !== 'string' || !(answers instanceof Array)) {
return null;
}
const answersParsed = [];
for (const answer of answers) {
const voters = new Map();
for (const [ voterId, voter ] of Object.entries(answer.voters)) {
if (typeof voter !== 'string') {
return null;
}
voters.set(voterId, voter);
}
answersParsed.push({
name: answer.name,
voters
});
}
return {
changingVote: false,
senderId,
senderName,
question,
showResults: true,
lastVote: null,
answers: answersParsed
};
};
StateListenerRegistry.register(
state => getCurrentConference(state),
(conference, store, previousConference) => {
if (conference && conference !== previousConference) {
const receiveMessage = (_, data) => {
switch (data.type) {
case COMMAND_NEW_POLL: {
const { question, answers, pollId, senderId, senderName } = data;
const poll = {
changingVote: false,
senderId,
senderName,
showResults: false,
lastVote: null,
question,
answers: answers.map(answer => {
return {
name: answer,
voters: new Map()
};
})
};
store.dispatch(receivePoll(pollId, poll, true));
store.dispatch(showNotification({
appearance: NOTIFICATION_TYPE.NORMAL,
titleKey: 'polls.notification.title',
descriptionKey: 'polls.notification.description'
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
break;
}
case COMMAND_ANSWER_POLL: {
const { pollId, answers, voterId, voterName } = data;
const receivedAnswer: Answer = {
voterId,
voterName,
pollId,
answers
};
store.dispatch(receiveAnswer(pollId, receivedAnswer));
break;
}
case COMMAND_OLD_POLLS: {
const { polls } = data;
for (const pollData of polls) {
const poll = parsePollData(pollData);
if (poll === null) {
console.warn('[features/polls] Invalid old poll data');
} else {
store.dispatch(receivePoll(pollData.id, poll, false));
}
}
break;
}
}
};
conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, receiveMessage);
conference.on(JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED, receiveMessage);
// clean old polls
store.dispatch(clearPolls());
}
}
);

View File

@ -5,7 +5,7 @@ import { batch } from 'react-redux';
import { createReactionSoundsDisabledEvent, sendAnalytics } from '../analytics'; import { createReactionSoundsDisabledEvent, sendAnalytics } from '../analytics';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
import { import {
CONFERENCE_WILL_JOIN, CONFERENCE_JOIN_IN_PROGRESS,
SET_START_REACTIONS_MUTED, SET_START_REACTIONS_MUTED,
setStartReactionsMuted setStartReactionsMuted
} from '../base/conference'; } from '../base/conference';
@ -104,7 +104,7 @@ MiddlewareRegistry.register(store => next => action => {
break; break;
} }
case CONFERENCE_WILL_JOIN: { case CONFERENCE_JOIN_IN_PROGRESS: {
const { conference } = action; const { conference } = action;
conference.addCommandListener( conference.addCommandListener(

View File

@ -6,7 +6,7 @@ import {
sendAnalytics sendAnalytics
} from '../analytics'; } from '../analytics';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
import { CONFERENCE_WILL_JOIN, getCurrentConference } from '../base/conference'; import { CONFERENCE_JOIN_IN_PROGRESS, getCurrentConference } from '../base/conference';
import JitsiMeetJS, { import JitsiMeetJS, {
JitsiConferenceEvents, JitsiConferenceEvents,
JitsiRecordingConstants JitsiRecordingConstants
@ -106,21 +106,15 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
break; break;
case CONFERENCE_WILL_JOIN: { case CONFERENCE_JOIN_IN_PROGRESS: {
const { conference } = action; const { conference } = action;
conference.on( conference.on(
JitsiConferenceEvents.RECORDER_STATE_CHANGED, JitsiConferenceEvents.RECORDER_STATE_CHANGED,
recorderSession => { recorderSession => {
if (recorderSession) { if (recorderSession) {
recorderSession.getID() recorderSession.getID() && dispatch(updateRecordingSessionData(recorderSession));
&& dispatch( recorderSession.getError() && _showRecordingErrorNotification(recorderSession, dispatch);
updateRecordingSessionData(recorderSession));
recorderSession.getError()
&& _showRecordingErrorNotification(
recorderSession, dispatch);
} }
return; return;

View File

@ -2,7 +2,8 @@
import { batch } from 'react-redux'; import { batch } from 'react-redux';
import { CONFERENCE_LEFT, getCurrentConference } from '../base/conference'; import { CONFERENCE_JOIN_IN_PROGRESS, CONFERENCE_LEFT } from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import { import {
PARTICIPANT_LEFT, PARTICIPANT_LEFT,
getLocalParticipant, getLocalParticipant,
@ -10,7 +11,7 @@ import {
participantLeft, participantLeft,
pinParticipant pinParticipant
} from '../base/participants'; } from '../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import { SET_SHARED_VIDEO_STATUS, RESET_SHARED_VIDEO_STATUS } from './actionTypes'; import { SET_SHARED_VIDEO_STATUS, RESET_SHARED_VIDEO_STATUS } from './actionTypes';
import { import {
@ -30,16 +31,37 @@ import { isSharingStatus } from './functions';
MiddlewareRegistry.register(store => next => action => { MiddlewareRegistry.register(store => next => action => {
const { dispatch, getState } = store; const { dispatch, getState } = store;
const state = getState(); const state = getState();
const conference = getCurrentConference(state);
const localParticipantId = getLocalParticipant(state)?.id;
const { videoUrl, status, ownerId, time, muted, volume } = action;
const { ownerId: stateOwnerId, videoUrl: statevideoUrl } = state['features/shared-video'];
switch (action.type) { switch (action.type) {
case CONFERENCE_JOIN_IN_PROGRESS: {
const { conference } = action;
const localParticipantId = getLocalParticipant(state)?.id;
conference.addCommandListener(SHARED_VIDEO,
({ value, attributes }) => {
const { from } = attributes;
const sharedVideoStatus = attributes.state;
if (isSharingStatus(sharedVideoStatus)) {
handleSharingVideoStatus(store, value, attributes, conference);
} else if (sharedVideoStatus === 'stop') {
dispatch(participantLeft(value, conference));
if (localParticipantId !== from) {
dispatch(resetSharedVideoStatus());
}
}
}
);
break;
}
case CONFERENCE_LEFT: case CONFERENCE_LEFT:
dispatch(resetSharedVideoStatus()); dispatch(resetSharedVideoStatus());
break; break;
case PARTICIPANT_LEFT: case PARTICIPANT_LEFT: {
const conference = getCurrentConference(state);
const { ownerId: stateOwnerId, videoUrl: statevideoUrl } = state['features/shared-video'];
if (action.participant.id === stateOwnerId) { if (action.participant.id === stateOwnerId) {
batch(() => { batch(() => {
dispatch(resetSharedVideoStatus()); dispatch(resetSharedVideoStatus());
@ -47,7 +69,12 @@ MiddlewareRegistry.register(store => next => action => {
}); });
} }
break; break;
case SET_SHARED_VIDEO_STATUS: }
case SET_SHARED_VIDEO_STATUS: {
const conference = getCurrentConference(state);
const localParticipantId = getLocalParticipant(state)?.id;
const { videoUrl, status, ownerId, time, muted, volume } = action;
if (localParticipantId === ownerId) { if (localParticipantId === ownerId) {
sendShareVideoCommand({ sendShareVideoCommand({
conference, conference,
@ -60,8 +87,14 @@ MiddlewareRegistry.register(store => next => action => {
}); });
} }
break; break;
case RESET_SHARED_VIDEO_STATUS: }
case RESET_SHARED_VIDEO_STATUS: {
const localParticipantId = getLocalParticipant(state)?.id;
const { ownerId: stateOwnerId, videoUrl: statevideoUrl } = state['features/shared-video'];
if (localParticipantId === stateOwnerId) { if (localParticipantId === stateOwnerId) {
const conference = getCurrentConference(state);
sendShareVideoCommand({ sendShareVideoCommand({
conference, conference,
id: statevideoUrl, id: statevideoUrl,
@ -74,41 +107,11 @@ MiddlewareRegistry.register(store => next => action => {
} }
break; break;
} }
}
return next(action); return next(action);
}); });
/**
* Set up state change listener to perform maintenance tasks when the conference
* is left or failed, e.g. Clear messages or close the chat modal if it's left
* open.
*/
StateListenerRegistry.register(
state => getCurrentConference(state),
(conference, store, previousConference) => {
if (conference && conference !== previousConference) {
conference.addCommandListener(SHARED_VIDEO,
({ value, attributes }) => {
const { dispatch, getState } = store;
const { from } = attributes;
const localParticipantId = getLocalParticipant(getState()).id;
const status = attributes.state;
if (isSharingStatus(status)) {
handleSharingVideoStatus(store, value, attributes, conference);
} else if (status === 'stop') {
dispatch(participantLeft(value, conference));
if (localParticipantId !== from) {
dispatch(resetSharedVideoStatus());
}
}
}
);
}
}
);
/** /**
* Handles the playing, pause and start statuses for the shared video. * Handles the playing, pause and start statuses for the shared video.
* Dispatches participantJoined event and, if necessary, pins it. * Dispatches participantJoined event and, if necessary, pins it.

View File

@ -1,38 +1,37 @@
// @flow // @flow
import { getCurrentConference } from '../base/conference'; import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
import { getLocalParticipant } from '../base/participants'; import { getLocalParticipant } from '../base/participants';
import { StateListenerRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import { setDisableButton } from './actions.web'; import { setDisableButton } from './actions.web';
import { SHARED_VIDEO } from './constants'; import { SHARED_VIDEO } from './constants';
import './middleware.any'; import './middleware.any';
/** MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
* Set up state change listener to disable or enable the share video button in const state = getState();
* the toolbar menu. const localParticipantId = getLocalParticipant(state)?.id;
*/
StateListenerRegistry.register(
state => getCurrentConference(state),
(conference, store, previousConference) => {
if (conference && conference !== previousConference) {
conference.addCommandListener(SHARED_VIDEO,
({ attributes }) => {
const { dispatch, getState } = store;
const { from } = attributes;
const localParticipantId = getLocalParticipant(getState()).id;
const status = attributes.state;
if (status === 'playing') { switch (action.type) {
if (localParticipantId !== from) { case CONFERENCE_JOIN_IN_PROGRESS: {
dispatch(setDisableButton(true)); const { conference } = action;
}
} else if (status === 'stop') { conference.addCommandListener(SHARED_VIDEO, ({ attributes }) => {
dispatch(setDisableButton(false)); const { from } = attributes;
} const status = attributes.state;
if (status === 'playing') {
if (localParticipantId !== from) {
dispatch(setDisableButton(true));
} }
); } else if (status === 'stop') {
} dispatch(setDisableButton(false));
}
});
break;
} }
); }
return next(action);
});

View File

@ -1,6 +1,6 @@
// @flow // @flow
import { CONFERENCE_WILL_JOIN } from '../base/conference'; import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
import { import {
JitsiConferenceEvents, JitsiConferenceEvents,
JitsiSIPVideoGWStatus JitsiSIPVideoGWStatus
@ -29,12 +29,12 @@ import logger from './logger';
* @param {Store} store - The redux store. * @param {Store} store - The redux store.
* @returns {Function} * @returns {Function}
*/ */
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { MiddlewareRegistry.register(({ dispatch }) => next => action => {
const result = next(action); const result = next(action);
switch (action.type) { switch (action.type) {
case CONFERENCE_WILL_JOIN: { case CONFERENCE_JOIN_IN_PROGRESS: {
const conference = getState()['features/base/conference'].joining; const { conference } = action;
conference.on( conference.on(
JitsiConferenceEvents.VIDEO_SIP_GW_AVAILABILITY_CHANGED, JitsiConferenceEvents.VIDEO_SIP_GW_AVAILABILITY_CHANGED,