Relax media checks in calling lobbies

This commit is contained in:
Fedor Indutny
2025-03-17 11:38:26 -07:00
committed by GitHub
parent 9de83541ca
commit 3758e8138a
8 changed files with 49 additions and 30 deletions
+2 -2
View File
@@ -89,7 +89,7 @@ export type PropsDataType = {
hasHideMenuBar?: boolean;
hasIncomingCallNotifications: boolean;
hasLinkPreviews: boolean;
hasMediaCameraPermissions: boolean;
hasMediaCameraPermissions: boolean | undefined;
hasMediaPermissions: boolean;
hasMessageAudio: boolean;
hasMinimizeToAndStartInSystemTray: boolean;
@@ -609,7 +609,7 @@ export function Preferences({
onChange={onMediaPermissionsChange}
/>
<Checkbox
checked={hasMediaCameraPermissions}
checked={hasMediaCameraPermissions ?? false}
label={i18n('icu:mediaCameraPermissionsDescription')}
moduleClassName="Preferences__checkbox"
name="mediaCameraPermissions"
+3 -1
View File
@@ -119,7 +119,9 @@ export class SettingsChannel extends EventEmitter {
return userConfig.get('mediaPermissions') || false;
});
ipc.handle('settings:get:mediaCameraPermissions', () => {
return userConfig.get('mediaCameraPermissions') || false;
// Intentionally returning `undefined` when unset to let app properly
// onboard the user.
return userConfig.get('mediaCameraPermissions');
});
ipc.handle('settings:set:mediaPermissions', (_event, value) => {
userConfig.set('mediaPermissions', value);
+34 -19
View File
@@ -92,6 +92,7 @@ import type { ProcessedEnvelope } from '../textsecure/Types.d';
import type { GetIceServersResultType } from '../textsecure/WebAPI';
import { missingCaseError } from '../util/missingCaseError';
import { normalizeGroupCallTimestamp } from '../util/ringrtc/normalizeGroupCallTimestamp';
import { requestCameraPermissions } from '../util/callingPermissions';
import {
AUDIO_LEVEL_INTERVAL_MS,
REQUESTED_VIDEO_WIDTH,
@@ -351,6 +352,18 @@ async function ensureSystemPermissions({
}
}
async function checkCameraPermission(): Promise<boolean> {
// If user never went through on boarding, the value is going to be
// `undefined` and we should ask for permissions. If it is explicitly `false`
// camera is intentionally not available.
const cameraPermission = await window.IPC.getMediaCameraPermissions();
if (cameraPermission === false) {
return false;
}
const status = await window.IPC.getMediaAccessStatus('camera');
return status !== 'denied';
}
function getLogId(
options:
| {
@@ -500,11 +513,11 @@ export class CallingClass {
async startCallingLobby({
conversation,
hasLocalAudio,
hasLocalVideo,
preferLocalVideo,
}: Readonly<{
conversation: Readonly<ConversationType>;
hasLocalAudio: boolean;
hasLocalVideo: boolean;
preferLocalVideo: boolean;
}>): Promise<
| undefined
| ({ hasLocalAudio: boolean; hasLocalVideo: boolean } & (
@@ -569,6 +582,8 @@ export class CallingClass {
return;
}
const hasLocalVideo = preferLocalVideo && (await checkCameraPermission());
const haveMediaPermissions = await this.#requestPermissions(hasLocalVideo);
if (!haveMediaPermissions) {
log.info(`${logId}: Permissions were denied, new call not allowed.`);
@@ -899,12 +914,12 @@ export class CallingClass {
callLinkRootKey,
adminPasskey,
hasLocalAudio,
hasLocalVideo = true,
preferLocalVideo = true,
}: Readonly<{
callLinkRootKey: CallLinkRootKey;
adminPasskey: Buffer | undefined;
hasLocalAudio: boolean;
hasLocalVideo?: boolean;
preferLocalVideo?: boolean;
}>): Promise<
| undefined
| {
@@ -918,7 +933,17 @@ export class CallingClass {
}
> {
const roomId = getRoomIdFromRootKey(callLinkRootKey);
log.info('startCallLinkLobby() for roomId', roomId);
const logId = `startCallLinkLobby(roomId=${roomId})`;
log.info(`${logId}: starting`);
const hasLocalVideo = preferLocalVideo && (await checkCameraPermission());
const haveMediaPermissions = await this.#requestPermissions(hasLocalVideo);
if (!haveMediaPermissions) {
log.info(
`${logId}: Permissions were denied, but allow joining group call`
);
}
await ensureSystemPermissions({ hasLocalAudio, hasLocalVideo });
@@ -937,7 +962,9 @@ export class CallingClass {
groupCall.setOutgoingAudioMuted(!hasLocalAudio);
groupCall.setOutgoingVideoMuted(!hasLocalVideo);
drop(this.enableLocalCamera());
if (hasLocalVideo) {
drop(this.enableLocalCamera());
}
return {
callMode: CallMode.Adhoc,
@@ -2742,23 +2769,11 @@ export class CallingClass {
}
}
async #requestCameraPermissions(): Promise<boolean> {
const cameraPermission = await window.IPC.getMediaCameraPermissions();
if (!cameraPermission) {
await window.IPC.showPermissionsPopup(true, true);
// Check the setting again (from the source of truth).
return window.IPC.getMediaCameraPermissions();
}
return true;
}
async #requestPermissions(isVideoCall: boolean): Promise<boolean> {
const microphonePermission = await requestMicrophonePermissions(true);
if (microphonePermission) {
if (isVideoCall) {
return this.#requestCameraPermissions();
return requestCameraPermissions();
}
return true;
+1 -1
View File
@@ -2536,7 +2536,7 @@ function startCallingLobby({
conversation,
hasLocalAudio:
groupCallDeviceCount < MAX_CALL_PARTICIPANTS_FOR_DEFAULT_MUTE,
hasLocalVideo: isVideoCall,
preferLocalVideo: isVideoCall,
});
if (!callLobbyData) {
throw new Error('Failed to start call lobby');
+3 -3
View File
@@ -2178,7 +2178,7 @@ describe('calling duck', () => {
);
sinon.assert.calledWithMatch(startCallingLobbyStub, {
hasLocalVideo: true,
preferLocalVideo: true,
});
});
@@ -2189,7 +2189,7 @@ describe('calling duck', () => {
})(noop, () => rootState, null);
sinon.assert.calledWithMatch(startCallingLobbyStub, {
hasLocalVideo: true,
preferLocalVideo: true,
});
});
@@ -2200,7 +2200,7 @@ describe('calling duck', () => {
})(noop, () => rootState, null);
sinon.assert.calledWithMatch(startCallingLobbyStub, {
hasLocalVideo: false,
preferLocalVideo: false,
});
});
+1 -1
View File
@@ -6,7 +6,7 @@ export async function requestCameraPermissions(): Promise<boolean> {
await window.IPC.showPermissionsPopup(true, true);
// Check the setting again (from the source of truth).
return window.IPC.getMediaCameraPermissions();
return (await window.IPC.getMediaCameraPermissions()) ?? false;
}
return true;
+4 -2
View File
@@ -91,7 +91,7 @@ export type IPCEventsValuesType = {
// Optional
mediaPermissions: boolean;
mediaCameraPermissions: boolean;
mediaCameraPermissions: boolean | undefined;
// Only getters
@@ -734,7 +734,9 @@ export function createIPCEvents(
return window.IPC.getMediaAccessStatus(mediaType);
},
getMediaPermissions: window.IPC.getMediaPermissions,
getMediaCameraPermissions: window.IPC.getMediaCameraPermissions,
getMediaCameraPermissions: async () => {
return (await window.IPC.getMediaCameraPermissions()) || false;
},
setMediaPlaybackDisabled: (playbackDisabled: boolean) => {
window.reduxActions?.lightbox.setPlaybackDisabled(playbackDisabled);
+1 -1
View File
@@ -70,7 +70,7 @@ export type IPCType = {
getMediaAccessStatus: (
mediaType: 'screen' | 'microphone' | 'camera'
) => Promise<ReturnType<SystemPreferences['getMediaAccessStatus']>>;
getMediaCameraPermissions: () => Promise<boolean>;
getMediaCameraPermissions: () => Promise<boolean | undefined>;
openSystemMediaPermissions: (
mediaType: 'microphone' | 'camera'
) => Promise<void>;