diff --git a/react/features/base/conference/middleware.web.ts b/react/features/base/conference/middleware.web.ts index cf19e87a7d..bb3572dc34 100644 --- a/react/features/base/conference/middleware.web.ts +++ b/react/features/base/conference/middleware.web.ts @@ -8,11 +8,13 @@ import { isPrejoinPageVisible } from '../../prejoin/functions'; import { iAmVisitor } from '../../visitors/functions'; import { CONNECTION_DISCONNECTED, CONNECTION_ESTABLISHED } from '../connection/actionTypes'; import { hangup } from '../connection/actions.web'; -import { JitsiConferenceErrors } from '../lib-jitsi-meet'; +import { JitsiConferenceErrors, browser } from '../lib-jitsi-meet'; import { gumPending, setInitialGUMPromise } from '../media/actions'; import { MEDIA_TYPE } from '../media/constants'; import { IGUMPendingState } from '../media/types'; import MiddlewareRegistry from '../redux/MiddlewareRegistry'; +import { replaceLocalTrack } from '../tracks/actions.any'; +import { getLocalTracks } from '../tracks/functions.any'; import { CONFERENCE_FAILED, @@ -147,10 +149,46 @@ MiddlewareRegistry.register(store => next => action => { break; } case CONNECTION_ESTABLISHED: { - const state = getState(); + if (isPrejoinPageVisible(getState())) { + let { initialGUMPromise } = getState()['features/base/media']; - if (!isPrejoinPageVisible(state)) { - const { initialGUMPromise = Promise.resolve({ tracks: [] }) } = state['features/base/media']; + initialGUMPromise = initialGUMPromise || Promise.resolve({ tracks: [] }); + + initialGUMPromise.then(() => { + const state = getState(); + let localTracks = getLocalTracks(state['features/base/tracks']); + const trackReplacePromises = []; + + // Do not signal audio/video tracks if the user joins muted. + for (const track of localTracks) { + // Always add the audio track on Safari because of a known issue where audio playout doesn't happen + // if the user joins audio and video muted. + if ((track.muted && !(browser.isWebKitBased() && track.jitsiTrack + && track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)) || iAmVisitor(state)) { + trackReplacePromises.push(dispatch(replaceLocalTrack(track.jitsiTrack, null)) + .catch((error: any) => { + logger.error(`Failed to replace local track (${track.jitsiTrack}) with null: ${error}`); + })); + } + } + + Promise.allSettled(trackReplacePromises).then(() => { + + // 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: any) => t.jitsiTrack); + + + return APP.conference.startConference(jitsiTracks); + }); + }); + } else { + let { initialGUMPromise } = getState()['features/base/media']; + + initialGUMPromise = initialGUMPromise || Promise.resolve({ tracks: [] }); initialGUMPromise.then(({ tracks }) => { let tracksToUse = tracks ?? []; diff --git a/react/features/base/media/actions.ts b/react/features/base/media/actions.ts index c0b76bbf32..f99de59f8e 100644 --- a/react/features/base/media/actions.ts +++ b/react/features/base/media/actions.ts @@ -103,7 +103,7 @@ export function setCameraFacingMode(cameraFacingMode: string) { * promise: Promise * }} */ -export function setInitialGUMPromise(promise?: Promise<{ errors: any; tracks: Array; }>) { +export function setInitialGUMPromise(promise: Promise<{ errors: any; tracks: Array; }> | null = null) { return { type: SET_INITIAL_GUM_PROMISE, promise diff --git a/react/features/base/media/reducer.ts b/react/features/base/media/reducer.ts index 73cbbe6c52..d8a149a2f5 100644 --- a/react/features/base/media/reducer.ts +++ b/react/features/base/media/reducer.ts @@ -96,9 +96,9 @@ function _audio(state: IAudioState = _AUDIO_INITIAL_MEDIA_STATE, action: AnyActi * @param {string} action.type - Type of action. * @returns {ICommonState} */ -function _initialGUMPromise(state: initialGUMPromise | undefined, action: AnyAction) { +function _initialGUMPromise(state: initialGUMPromise | null = null, action: AnyAction) { if (action.type === SET_INITIAL_GUM_PROMISE) { - return action.promise; + return action.promise ?? null; } return state; @@ -265,9 +265,9 @@ interface IAudioState { } type initialGUMPromise = Promise<{ - errors: any; + errors?: any; tracks: Array; - }> | undefined; + }> | null; interface IScreenshareState { available: boolean; diff --git a/react/features/prejoin/middleware.web.ts b/react/features/prejoin/middleware.web.ts index 622b58be32..8f7cfb948b 100644 --- a/react/features/prejoin/middleware.web.ts +++ b/react/features/prejoin/middleware.web.ts @@ -2,19 +2,14 @@ import { AnyAction } from 'redux'; import { IStore } from '../app/types'; import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference/actionTypes'; -import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes'; -import { browser } from '../base/lib-jitsi-meet'; +import { CONNECTION_FAILED } from '../base/connection/actionTypes'; import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../base/media/actionTypes'; -import { MEDIA_TYPE } from '../base/media/constants'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; import { updateSettings } from '../base/settings/actions'; import { TRACK_ADDED, TRACK_NO_DATA_FROM_SOURCE } from '../base/tracks/actionTypes'; -import { replaceLocalTrack } from '../base/tracks/actions.any'; -import { getLocalTracks } from '../base/tracks/functions.any'; -import { iAmVisitor } from '../visitors/functions'; import { setDeviceStatusOk, @@ -22,7 +17,6 @@ import { setJoiningInProgress } from './actions'; import { isPrejoinPageVisible } from './functions.any'; -import logger from './logger'; /** * The redux middleware for {@link PrejoinPage}. @@ -32,48 +26,6 @@ import logger from './logger'; */ MiddlewareRegistry.register(store => next => action => { switch (action.type) { - case CONNECTION_ESTABLISHED: { - const { dispatch, getState } = store; - const result = next(action); - - if (isPrejoinPageVisible(getState())) { - const { initialGUMPromise = Promise.resolve() } = getState()['features/base/media']; - - initialGUMPromise.then(() => { - const state = getState(); - let localTracks = getLocalTracks(state['features/base/tracks']); - const trackReplacePromises = []; - - // Do not signal audio/video tracks if the user joins muted. - for (const track of localTracks) { - // Always add the audio track on Safari because of a known issue where audio playout doesn't happen - // if the user joins audio and video muted. - if ((track.muted && !(browser.isWebKitBased() && track.jitsiTrack - && track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)) || iAmVisitor(state)) { - trackReplacePromises.push(dispatch(replaceLocalTrack(track.jitsiTrack, null)) - .catch((error: any) => { - logger.error(`Failed to replace local track (${track.jitsiTrack}) with null: ${error}`); - })); - } - } - - Promise.allSettled(trackReplacePromises).then(() => { - - // 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: any) => t.jitsiTrack); - - - return APP.conference.startConference(jitsiTracks); - }); - }); - } - - return result; - } case SET_AUDIO_MUTED: { if (isPrejoinPageVisible(store.getState())) { store.dispatch(updateSettings({