snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
261
react/features/base/conference/middleware.web.ts
Normal file
261
react/features/base/conference/middleware.web.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import { SET_KNOCKING_STATE } from '../../lobby/actionTypes';
|
||||
import {
|
||||
setPrejoinPageVisibility,
|
||||
setSkipPrejoinOnReload
|
||||
} from '../../prejoin/actions.web';
|
||||
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, JitsiConnectionErrors, 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,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_JOIN_IN_PROGRESS,
|
||||
CONFERENCE_LEFT,
|
||||
KICKED_OUT
|
||||
} from './actionTypes';
|
||||
import { TRIGGER_READY_TO_CLOSE_REASONS } from './constants';
|
||||
import { processDestroyConferenceEvent } from './functions';
|
||||
import logger from './logger';
|
||||
import './middleware.any';
|
||||
|
||||
let screenLock: WakeLockSentinel | undefined;
|
||||
|
||||
/**
|
||||
* Releases the screen lock.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function releaseScreenLock() {
|
||||
if (screenLock) {
|
||||
if (!screenLock.released) {
|
||||
logger.debug('Releasing wake lock.');
|
||||
|
||||
try {
|
||||
await screenLock.release();
|
||||
} catch (e) {
|
||||
logger.error(`Error while releasing the screen wake lock: ${e}.`);
|
||||
}
|
||||
}
|
||||
screenLock.removeEventListener('release', onWakeLockReleased);
|
||||
screenLock = undefined;
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a new screen wake lock.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function requestWakeLock() {
|
||||
// A wake lock is already held (e.g. requested while knocking in the lobby), no need to request another one.
|
||||
if (screenLock && !screenLock.released) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (navigator.wakeLock?.request) {
|
||||
navigator.wakeLock.request('screen')
|
||||
.then(lock => {
|
||||
screenLock = lock;
|
||||
screenLock.addEventListener('release', onWakeLockReleased);
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
logger.debug('Wake lock created.');
|
||||
})
|
||||
.catch(e => {
|
||||
logger.error(`Error while requesting wake lock for screen: ${e}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Page visibility change handler that re-requests the wake lock if it has been released by the OS.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
async function handleVisibilityChange() {
|
||||
if (screenLock?.released && document.visibilityState === 'visible') {
|
||||
// The screen lock have been released by the OS because of document visibility change. Lets try to request the
|
||||
// wake lock again.
|
||||
await releaseScreenLock();
|
||||
requestWakeLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wake lock released handler.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function onWakeLockReleased() {
|
||||
logger.debug('Wake lock released');
|
||||
}
|
||||
|
||||
function onVisibilityChange() {
|
||||
logger.debug(`Page visibility changed to: ${document.visibilityState}`);
|
||||
}
|
||||
|
||||
function onPageHide(e: PageTransitionEvent) {
|
||||
logger.debug(`Page hidden, persisted (BFCache freeze): ${e.persisted}`);
|
||||
}
|
||||
|
||||
function onPageShow(e: PageTransitionEvent) {
|
||||
logger.debug(`Page shown, persisted (BFCache restore): ${e.persisted}`);
|
||||
}
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const { dispatch, getState } = store;
|
||||
const { enableForcedReload } = getState()['features/base/config'];
|
||||
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOIN_IN_PROGRESS: {
|
||||
dispatch(setPrejoinPageVisibility(false));
|
||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||
window.addEventListener('pagehide', onPageHide);
|
||||
window.addEventListener('pageshow', onPageShow);
|
||||
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_JOINED: {
|
||||
if (enableForcedReload) {
|
||||
dispatch(setSkipPrejoinOnReload(false));
|
||||
}
|
||||
|
||||
requestWakeLock();
|
||||
|
||||
break;
|
||||
}
|
||||
case SET_KNOCKING_STATE: {
|
||||
// Keep the screen awake while waiting in the lobby. The lock is kept on join (see CONFERENCE_JOINED, which
|
||||
// no-ops if it is already held) and released by the CONFERENCE_FAILED/CONFERENCE_LEFT/KICKED_OUT handlers if
|
||||
// knocking is rejected or the user leaves. We intentionally do not release here on knocking === false to avoid
|
||||
// dropping the lock mid-meeting once a participant has been admitted.
|
||||
if (action.knocking) {
|
||||
requestWakeLock();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_FAILED: {
|
||||
const errorName = action.error?.name;
|
||||
|
||||
if (enableForcedReload
|
||||
&& (errorName === JitsiConferenceErrors.CONFERENCE_RESTARTED
|
||||
|| errorName === JitsiConnectionErrors.SHARD_CHANGED_ERROR)) {
|
||||
dispatch(setSkipPrejoinOnReload(true));
|
||||
}
|
||||
|
||||
if (errorName === JitsiConferenceErrors.CONFERENCE_DESTROYED) {
|
||||
const state = getState();
|
||||
const { notifyOnConferenceDestruction = true } = state['features/base/config'];
|
||||
const [ reason ] = action.error.params;
|
||||
|
||||
if (processDestroyConferenceEvent(state, dispatch, action.error.params)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const titlekey = Object.keys(TRIGGER_READY_TO_CLOSE_REASONS)[
|
||||
Object.values(TRIGGER_READY_TO_CLOSE_REASONS).indexOf(reason)
|
||||
];
|
||||
|
||||
dispatch(hangup(true, i18next.t(titlekey) || reason, notifyOnConferenceDestruction));
|
||||
}
|
||||
|
||||
releaseScreenLock();
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||
window.removeEventListener('pagehide', onPageHide);
|
||||
window.removeEventListener('pageshow', onPageShow);
|
||||
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_LEFT:
|
||||
case KICKED_OUT:
|
||||
releaseScreenLock();
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||
window.removeEventListener('pagehide', onPageHide);
|
||||
window.removeEventListener('pageshow', onPageShow);
|
||||
|
||||
break;
|
||||
case CONNECTION_DISCONNECTED: {
|
||||
const { initialGUMPromise } = getState()['features/base/media'];
|
||||
|
||||
if (initialGUMPromise) {
|
||||
store.dispatch(setInitialGUMPromise());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case CONNECTION_ESTABLISHED: {
|
||||
const { initialGUMPromise } = getState()['features/base/media'];
|
||||
const promise = initialGUMPromise ? initialGUMPromise.promise : Promise.resolve({ tracks: [] });
|
||||
const prejoinVisible = isPrejoinPageVisible(getState());
|
||||
|
||||
logger.debug(`On connection established: prejoinVisible: ${prejoinVisible}, initialGUMPromiseExists=${
|
||||
Boolean(initialGUMPromise)}, promiseExists=${Boolean(promise)}`);
|
||||
|
||||
if (prejoinVisible) {
|
||||
promise.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);
|
||||
})
|
||||
.catch(logger.error);
|
||||
});
|
||||
} else {
|
||||
promise.then(({ tracks }) => {
|
||||
let tracksToUse = tracks ?? [];
|
||||
|
||||
if (iAmVisitor(getState())) {
|
||||
tracksToUse = [];
|
||||
tracks.forEach(track => track.dispose().catch(logger.error));
|
||||
dispatch(gumPending([ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ], IGUMPendingState.NONE));
|
||||
}
|
||||
|
||||
dispatch(setInitialGUMPromise());
|
||||
|
||||
return APP.conference.startConference(tracksToUse);
|
||||
})
|
||||
.catch(logger.error);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
Reference in New Issue
Block a user