From 2a725d2165ac1618ac3946003ae3f09df5e6ddaa Mon Sep 17 00:00:00 2001 From: Hristo Terezov Date: Tue, 28 Sep 2021 17:50:57 -0500 Subject: [PATCH] fix(prejoin): Prevent double joining conference. --- conference.js | 13 +++- react/features/prejoin/actionTypes.js | 4 +- react/features/prejoin/actions.js | 103 ++++++++++++++++++++++---- react/features/prejoin/middleware.js | 51 ++----------- react/features/prejoin/reducer.js | 7 +- 5 files changed, 114 insertions(+), 64 deletions(-) diff --git a/conference.js b/conference.js index dba91c38d..951dfe556 100644 --- a/conference.js +++ b/conference.js @@ -129,7 +129,8 @@ import { initPrejoin, isPrejoinPageEnabled, isPrejoinPageVisible, - makePrecallTest + makePrecallTest, + setJoiningInProgress } from './react/features/prejoin'; import { disableReceiver, stopReceiver } from './react/features/remote-control'; import { setScreenAudioShareState, isScreenAudioShared } from './react/features/screen-share/'; @@ -861,9 +862,15 @@ export default { _onConnectionPromiseCreated = undefined; } - const con = await _connectionPromise; + let con; - this.startConference(con, tracks); + try { + con = await _connectionPromise; + this.startConference(con, tracks); + } catch (error) { + logger.error(`An error occurred while trying to join a meeting from the prejoin screen: ${error}`); + APP.store.dispatch(setJoiningInProgress(false)); + } }, /** diff --git a/react/features/prejoin/actionTypes.js b/react/features/prejoin/actionTypes.js index 000fd19ae..aa529358c 100644 --- a/react/features/prejoin/actionTypes.js +++ b/react/features/prejoin/actionTypes.js @@ -1,8 +1,8 @@ /** - * Action type to signal the start of the conference. + * Action type to signal that joining is in progress. */ -export const PREJOIN_START_CONFERENCE = 'PREJOIN_START_CONFERENCE'; +export const PREJOIN_JOINING_IN_PROGRESS = 'PREJOIN_JOINING_IN_PROGRESS'; /** * Action type to signal that prejoin page was initialized. diff --git a/react/features/prejoin/actions.js b/react/features/prejoin/actions.js index f470ce22c..e354da71b 100644 --- a/react/features/prejoin/actions.js +++ b/react/features/prejoin/actions.js @@ -1,27 +1,30 @@ // @flow declare var JitsiMeetJS: Object; +declare var APP: Object; import uuid from 'uuid'; -import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions'; +import { getDialOutStatusUrl, getDialOutUrl, updateConfig } from '../base/config'; +import { isIosMobileBrowser } from '../base/environment/utils'; import { createLocalTrack } from '../base/lib-jitsi-meet'; -import { isVideoMutedByUser } from '../base/media'; +import { isVideoMutedByUser, MEDIA_TYPE } from '../base/media'; +import { updateSettings } from '../base/settings'; import { + createLocalTracksF, getLocalAudioTrack, + getLocalTracks, getLocalVideoTrack, trackAdded, replaceLocalTrack } from '../base/tracks'; -import { createLocalTracksF } from '../base/tracks/functions'; import { openURLInBrowser } from '../base/util'; import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions'; import { showErrorNotification } from '../notifications'; import { + PREJOIN_JOINING_IN_PROGRESS, PREJOIN_INITIALIZED, - PREJOIN_START_CONFERENCE, - SET_DEVICE_STATUS, SET_DIALOUT_COUNTRY, SET_DIALOUT_NUMBER, SET_DIALOUT_STATUS, @@ -31,9 +34,10 @@ import { SET_JOIN_BY_PHONE_DIALOG_VISIBLITY, SET_PRECALL_TEST_RESULTS, SET_PREJOIN_DEVICE_ERRORS, - SET_PREJOIN_PAGE_VISIBILITY + SET_PREJOIN_PAGE_VISIBILITY, + SET_DEVICE_STATUS } from './actionTypes'; -import { type PREJOIN_SCREEN_STATE } from './constants'; +import { type PREJOIN_SCREEN_STATE, PREJOIN_SCREEN_STATES } from './constants'; import { getFullDialOutNumber, getDialOutConferenceUrl, @@ -209,15 +213,74 @@ export function initPrejoin(tracks: Object[], errors: Object) { * Action used to start the conference. * * @param {Object} options - The config options that override the default ones (if any). + * @param {boolean} ignoreJoiningInProgress - If true we won't check the joiningInProgress flag. * @returns {Function} */ -export function joinConference(options?: Object) { - return { - type: PREJOIN_START_CONFERENCE, - options +export function joinConference(options?: Object, ignoreJoiningInProgress: boolean = false) { + return async function(dispatch: Function, getState: Function) { + if (!ignoreJoiningInProgress) { + const state = getState(); + const { joiningInProgress } = state['features/prejoin']; + + if (joiningInProgress) { + return; + } + + dispatch(setJoiningInProgress(true)); + } + + const state = getState(); + const { userSelectedSkipPrejoin } = state['features/prejoin']; + let localTracks = getLocalTracks(state['features/base/tracks']); + + options && dispatch(updateConfig(options)); + + userSelectedSkipPrejoin && dispatch(updateSettings({ + userSelectedSkipPrejoin + })); + + // Do not signal audio/video tracks if the user joins muted. + for (const track of localTracks) { + // Always add the audio track on mobile Safari because of a known issue where audio playout doesn't happen + // if the user joins audio and video muted. + if (track.muted + && !(isIosMobileBrowser() && track.jitsiTrack && track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)) { + try { + await dispatch(replaceLocalTrack(track.jitsiTrack, null)); + } catch (error) { + logger.error(`Failed to replace local track (${track.jitsiTrack}) with null: ${error}`); + } + } + } + + // Re-fetch the local tracks after muted tracks have been removed above. + // This is needed, because the tracks are effectively disposed by the replaceLocalTrack and should not be used + // anymore. + localTracks = getLocalTracks(getState()['features/base/tracks']); + + const jitsiTracks = localTracks.map(t => t.jitsiTrack); + + dispatch(setPrejoinPageVisibility(PREJOIN_SCREEN_STATES.LOADING)); + + APP.conference.prejoinStart(jitsiTracks); }; } + +/** + * Action used to set the flag for joining operation in progress. + * + * @param {boolean} value - The config options that override the default ones (if any). + * @returns {Function} + */ +export function setJoiningInProgress(value: boolean) { + return { + type: PREJOIN_JOINING_IN_PROGRESS, + value + }; +} + + /** * Joins the conference without audio. * @@ -225,16 +288,28 @@ export function joinConference(options?: Object) { */ export function joinConferenceWithoutAudio() { return async function(dispatch: Function, getState: Function) { - const tracks = getState()['features/base/tracks']; + const state = getState(); + const { joiningInProgress } = state['features/prejoin']; + + if (joiningInProgress) { + return; + } + + dispatch(setJoiningInProgress(true)); + const tracks = state['features/base/tracks']; const audioTrack = getLocalAudioTrack(tracks)?.jitsiTrack; if (audioTrack) { - await dispatch(replaceLocalTrack(audioTrack, null)); + try { + await dispatch(replaceLocalTrack(audioTrack, null)); + } catch (error) { + logger.error(`Failed to replace local audio with null: ${error}`); + } } dispatch(joinConference({ startSilent: true - })); + }, true)); }; } diff --git a/react/features/prejoin/middleware.js b/react/features/prejoin/middleware.js index edce76b33..90bbefc21 100644 --- a/react/features/prejoin/middleware.js +++ b/react/features/prejoin/middleware.js @@ -1,22 +1,18 @@ // @flow -import { CONFERENCE_JOINED } from '../base/conference'; -import { updateConfig } from '../base/config'; -import { isIosMobileBrowser } from '../base/environment/utils'; -import { MEDIA_TYPE, SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media'; +import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference'; +import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media'; import { MiddlewareRegistry } from '../base/redux'; import { updateSettings } from '../base/settings'; import { - getLocalTracks, - replaceLocalTrack, TRACK_ADDED, TRACK_NO_DATA_FROM_SOURCE } from '../base/tracks'; -import { PREJOIN_START_CONFERENCE } from './actionTypes'; import { setDeviceStatusOk, setDeviceStatusWarning, + setJoiningInProgress, setPrejoinPageVisibility } from './actions'; import { PREJOIN_SCREEN_STATES } from './constants'; @@ -32,43 +28,6 @@ declare var APP: Object; */ MiddlewareRegistry.register(store => next => async action => { switch (action.type) { - case PREJOIN_START_CONFERENCE: { - const { getState, dispatch } = store; - const state = getState(); - const { userSelectedSkipPrejoin } = state['features/prejoin']; - let localTracks = getLocalTracks(state['features/base/tracks']); - const { options } = action; - - options && store.dispatch(updateConfig(options)); - - userSelectedSkipPrejoin && dispatch(updateSettings({ - userSelectedSkipPrejoin - })); - - // Do not signal audio/video tracks if the user joins muted. - for (const track of localTracks) { - // Always add the audio track on mobile Safari because of a known issue where audio playout doesn't happen - // if the user joins audio and video muted. - if (track.muted - && !(isIosMobileBrowser() && track.jitsiTrack && track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)) { - await dispatch(replaceLocalTrack(track.jitsiTrack, null)); - } - } - - // Re-fetch the local tracks after muted tracks have been removed above. - // This is needed, because the tracks are effectively disposed by the replaceLocalTrack and should not be used - // anymore. - localTracks = getLocalTracks(getState()['features/base/tracks']); - - const jitsiTracks = localTracks.map(t => t.jitsiTrack); - - dispatch(setPrejoinPageVisibility(PREJOIN_SCREEN_STATES.LOADING)); - - APP.conference.prejoinStart(jitsiTracks); - - break; - } - case SET_AUDIO_MUTED: { if (isPrejoinPageVisible(store.getState())) { store.dispatch(updateSettings({ @@ -110,6 +69,9 @@ MiddlewareRegistry.register(store => next => async action => { } break; } + case CONFERENCE_FAILED: + store.dispatch(setJoiningInProgress(false)); + break; case CONFERENCE_JOINED: return _conferenceJoined(store, next, action); } @@ -127,6 +89,7 @@ MiddlewareRegistry.register(store => next => async action => { */ function _conferenceJoined({ dispatch }, next, action) { dispatch(setPrejoinPageVisibility(PREJOIN_SCREEN_STATES.HIDDEN)); + dispatch(setJoiningInProgress(false)); return next(action); } diff --git a/react/features/prejoin/reducer.js b/react/features/prejoin/reducer.js index 35c235b85..73af3f5a4 100644 --- a/react/features/prejoin/reducer.js +++ b/react/features/prejoin/reducer.js @@ -1,6 +1,7 @@ import { PersistenceRegistry, ReducerRegistry } from '../base/redux'; import { + PREJOIN_JOINING_IN_PROGRESS, SET_DEVICE_STATUS, SET_DIALOUT_COUNTRY, SET_DIALOUT_NUMBER, @@ -53,7 +54,11 @@ PersistenceRegistry.register(STORE_NAME, { ReducerRegistry.register( 'features/prejoin', (state = DEFAULT_STATE, action) => { switch (action.type) { - + case PREJOIN_JOINING_IN_PROGRESS: + return { + ...state, + joiningInProgress: action.value + }; case SET_SKIP_PREJOIN: { return { ...state,