Skip to content

Commit

Permalink
fix(prosody-auth): Don't loose initial tracks.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hristoterezov committed Feb 13, 2024
1 parent 895afba commit 07e6513
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 19 deletions.
35 changes: 21 additions & 14 deletions conference.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import {
setAudioAvailable,
setAudioMuted,
setAudioUnmutePermissions,
setInitialGUMPromise,
setVideoAvailable,
setVideoMuted,
setVideoUnmutePermissions
Expand Down Expand Up @@ -716,6 +717,7 @@ export default {

return localTracks;
};
const { dispatch } = APP.store;

if (isPrejoinPageVisible(state)) {
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
Expand All @@ -726,9 +728,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');
Expand All @@ -743,23 +745,28 @@ export default {
}

const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
const gumPromise = tryCreateLocalTracks.then(tr => {
this._displayErrorsForCreateInitialLocalTracks(errors);

return Promise.all([
tryCreateLocalTracks.then(tr => {
this._displayErrorsForCreateInitialLocalTracks(errors);
return tr;
}).then(tr => {
this._initDeviceList(true);

return tr;
}).then(tr => {
this._initDeviceList(true);
const filteredTracks = handleInitialTracks(initialOptions, tr);

const filteredTracks = handleInitialTracks(initialOptions, tr);
setGUMPendingStateOnFailedTracks(filteredTracks);

setGUMPendingStateOnFailedTracks(filteredTracks);
return filteredTracks;
});

return filteredTracks;
}),
APP.store.dispatch(connect())
]).then(([ tracks, _ ]) => {
return Promise.all([
gumPromise,
dispatch(connect())
]).catch(e => {
dispatch(setInitialGUMPromise(gumPromise));
throw e;
})
.then(([ tracks, _ ]) => {
this.startConference(tracks).catch(logger.error);
});
},
Expand Down
24 changes: 24 additions & 0 deletions react/features/authentication/actions.web.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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);
});
};
}
8 changes: 3 additions & 5 deletions react/features/authentication/components/web/LoginDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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));
}
}

Expand Down
7 changes: 7 additions & 0 deletions react/features/authentication/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -153,6 +154,8 @@ MiddlewareRegistry.register(store => next => action => {
error.recoverable = true;

_handleLogin(store);
} else {
store.dispatch(setInitialGUMPromise());
}

break;
Expand Down Expand Up @@ -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;
Expand All @@ -275,6 +280,8 @@ function _handleLogin({ dispatch, getState }: IStore) {
return;
}

dispatch(setInitialGUMPromise());

getTokenAuthUrl(
config,
locationURL,
Expand Down
10 changes: 10 additions & 0 deletions react/features/base/media/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
17 changes: 17 additions & 0 deletions react/features/base/media/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
*
Expand Down
31 changes: 31 additions & 0 deletions react/features/base/media/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -247,6 +272,10 @@ interface IAudioState {
unmuteBlocked: boolean;
}

interface ICommonState {
initialGUMPromise?: Promise<Array<Object>>;
}

interface IScreenshareState {
available: boolean;
muted: number;
Expand All @@ -264,6 +293,7 @@ interface IVideoState {

export interface IMediaState {
audio: IAudioState;
common: ICommonState;
screenshare: IScreenshareState;
video: IVideoState;
}
Expand All @@ -280,6 +310,7 @@ export interface IMediaState {
*/
ReducerRegistry.register<IMediaState>('features/base/media', combineReducers({
audio: _audio,
common: _common,
screenshare: _screenshare,
video: _video
}));
Expand Down

0 comments on commit 07e6513

Please sign in to comment.