fix(prosody-auth): Don't loose initial tracks.

When the prejoin screen is disabled during the prosody login cycle the initial GUM tracks were lost causing the user to start the call without local media and audio/video mute buttons staying forever in pending state.
This commit is contained in:
Hristo Terezov
2024-02-12 18:35:51 -06:00
parent b4e4dd1aa9
commit 411e9a2372
7 changed files with 116 additions and 22 deletions

View File

@@ -83,6 +83,7 @@ import {
setAudioAvailable,
setAudioMuted,
setAudioUnmutePermissions,
setInitialGUMPromise,
setVideoAvailable,
setVideoMuted,
setVideoUnmutePermissions
@@ -606,6 +607,7 @@ export default {
return localTracks;
};
const { dispatch } = APP.store;
if (isPrejoinPageVisible(state)) {
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
@@ -616,9 +618,9 @@ export default {
this._initDeviceList(true);
if (isPrejoinPageVisible(state)) {
APP.store.dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
return APP.store.dispatch(initPrejoin(localTracks, errors));
return dispatch(initPrejoin(localTracks, errors));
}
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
@@ -633,23 +635,28 @@ export default {
}
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
const gumPromise = tryCreateLocalTracks.then(tr => {
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
return tr;
}).then(tr => {
this._initDeviceList(true);
const filteredTracks = handleInitialTracks(initialOptions, tr);
setGUMPendingStateOnFailedTracks(filteredTracks, APP.store.dispatch);
return filteredTracks;
});
return Promise.all([
tryCreateLocalTracks.then(tr => {
APP.store.dispatch(displayErrorsForCreateInitialLocalTracks(errors));
return tr;
}).then(tr => {
this._initDeviceList(true);
const filteredTracks = handleInitialTracks(initialOptions, tr);
setGUMPendingStateOnFailedTracks(filteredTracks, APP.store.dispatch);
return filteredTracks;
}),
APP.store.dispatch(connect())
]).then(([ tracks, _ ]) => {
gumPromise,
dispatch(connect())
]).catch(e => {
dispatch(setInitialGUMPromise(gumPromise));
throw e;
})
.then(([ tracks, _ ]) => {
this.startConference(tracks).catch(logger.error);
});
},

View File

@@ -1,10 +1,13 @@
import { maybeRedirectToWelcomePage } from '../app/actions.web';
import { IStore } from '../app/types';
import { connect } from '../base/connection/actions.web';
import { openDialog } from '../base/dialog/actions';
import { browser } from '../base/lib-jitsi-meet';
import { setInitialGUMPromise } from '../base/media/actions';
import { CANCEL_LOGIN } from './actionTypes';
import LoginQuestionDialog from './components/web/LoginQuestionDialog';
import logger from './logger';
export * from './actions.any';
@@ -76,3 +79,24 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
}
};
}
/**
* Executes connect with the passed credentials and then continues the flow to start a conference.
*
* @param {string} jid - The jid for the connection.
* @param {string} password - The password for the connection.
* @returns {Function}
*/
export function sumbitConnectionCredentials(jid?: string, password?: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const { initialGUMPromise } = getState()['features/base/media'].common;
dispatch(connect(jid, password))
.then(() => initialGUMPromise ?? [])
.then((tracks: Array<Object> = []) => {
// clear the initial GUM promise since we don't need it anymore.
dispatch(setInitialGUMPromise());
APP.conference.startConference(tracks).catch(logger.error);
});
};
}

View File

@@ -10,10 +10,10 @@ import { translate, translateToHTML } from '../../../base/i18n/functions';
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
import Dialog from '../../../base/ui/components/web/Dialog';
import Input from '../../../base/ui/components/web/Input';
import { joinConference } from '../../../prejoin/actions.web';
import {
authenticateAndUpgradeRole,
cancelLogin
cancelLogin,
sumbitConnectionCredentials
} from '../../actions.web';
/**
@@ -134,9 +134,7 @@ class LoginDialog extends Component<IProps, IState> {
if (conference) {
dispatch(authenticateAndUpgradeRole(jid, password, conference));
} else {
// dispatch(connect(jid, password));
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
dispatch(joinConference(undefined, false, jid, password));
dispatch(sumbitConnectionCredentials(jid, password));
}
}

View File

@@ -13,6 +13,7 @@ import {
JitsiConferenceErrors,
JitsiConnectionErrors
} from '../base/lib-jitsi-meet';
import { setInitialGUMPromise } from '../base/media/actions';
import { MEDIA_TYPE } from '../base/media/constants';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { isLocalTrackMuted } from '../base/tracks/functions.any';
@@ -153,6 +154,8 @@ MiddlewareRegistry.register(store => next => action => {
error.recoverable = true;
_handleLogin(store);
} else {
store.dispatch(setInitialGUMPromise());
}
break;
@@ -264,6 +267,8 @@ function _handleLogin({ dispatch, getState }: IStore) {
const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
if (!room) {
dispatch(setInitialGUMPromise());
logger.warn('Cannot handle login, room is undefined!');
return;
@@ -275,6 +280,8 @@ function _handleLogin({ dispatch, getState }: IStore) {
return;
}
dispatch(setInitialGUMPromise());
getTokenAuthUrl(
config,
locationURL,

View File

@@ -51,6 +51,16 @@ export const SET_AUDIO_UNMUTE_PERMISSIONS = 'SET_AUDIO_UNMUTE_PERMISSIONS';
*/
export const SET_CAMERA_FACING_MODE = 'SET_CAMERA_FACING_MODE';
/**
* Sets the initial GUM promise.
*
* {
* type: SET_INITIAL_GUM_PROMISE,
* promise: Promise
* }}
*/
export const SET_INITIAL_GUM_PROMISE = 'SET_INITIAL_GUM_PROMISE';
/**
* The type of (redux) action to set the muted state of the local screenshare.
*

View File

@@ -9,6 +9,7 @@ import {
SET_AUDIO_MUTED,
SET_AUDIO_UNMUTE_PERMISSIONS,
SET_CAMERA_FACING_MODE,
SET_INITIAL_GUM_PROMISE,
SET_SCREENSHARE_MUTED,
SET_VIDEO_AVAILABLE,
SET_VIDEO_MUTED,
@@ -93,6 +94,22 @@ export function setCameraFacingMode(cameraFacingMode: string) {
};
}
/**
* Sets the initial GUM promise.
*
* @param {Promise<Array<Object>> | undefined} promise - The promise.
* @returns {{
* type: SET_INITIAL_GUM_PROMISE,
* promise: Promise
* }}
*/
export function setInitialGUMPromise(promise?: Promise<Array<Object>>) {
return {
type: SET_INITIAL_GUM_PROMISE,
promise
};
}
/**
* Action to set the muted state of the local screenshare.
*

View File

@@ -10,6 +10,7 @@ import {
SET_AUDIO_MUTED,
SET_AUDIO_UNMUTE_PERMISSIONS,
SET_CAMERA_FACING_MODE,
SET_INITIAL_GUM_PROMISE,
SET_SCREENSHARE_MUTED,
SET_VIDEO_AVAILABLE,
SET_VIDEO_MUTED,
@@ -87,6 +88,30 @@ function _audio(state: IAudioState = _AUDIO_INITIAL_MEDIA_STATE, action: AnyActi
}
}
/**
* The initial common media state.
*/
const _COMMON_INITIAL_STATE = {};
/**
* Reducer fot the common properties in media state.
*
* @param {ICommonState} state - Common media state.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @returns {ICommonState}
*/
function _common(state: ICommonState = _COMMON_INITIAL_STATE, action: AnyAction) {
if (action.type === SET_INITIAL_GUM_PROMISE) {
return {
...state,
initialGUMPromise: action.promise
};
}
return state;
}
/**
* Media state object for local screenshare.
*
@@ -247,6 +272,10 @@ interface IAudioState {
unmuteBlocked: boolean;
}
interface ICommonState {
initialGUMPromise?: Promise<Array<Object>>;
}
interface IScreenshareState {
available: boolean;
muted: number;
@@ -264,6 +293,7 @@ interface IVideoState {
export interface IMediaState {
audio: IAudioState;
common: ICommonState;
screenshare: IScreenshareState;
video: IVideoState;
}
@@ -280,6 +310,7 @@ export interface IMediaState {
*/
ReducerRegistry.register<IMediaState>('features/base/media', combineReducers({
audio: _audio,
common: _common,
screenshare: _screenshare,
video: _video
}));