perf: Make getCameraPermission and getMicrophonePermission synchronous (#2302)

This commit is contained in:
Marc Rousavy 2023-12-19 14:22:04 +01:00 committed by GitHub
parent 3d2feb6f6c
commit 591cf30a06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 28 additions and 66 deletions

View File

@ -164,8 +164,8 @@ There could be three states to this:
Simply use the **get** functions to find out if a user has granted or denied permission before: Simply use the **get** functions to find out if a user has granted or denied permission before:
```ts ```ts
const cameraPermission = await Camera.getCameraPermissionStatus() const cameraPermission = Camera.getCameraPermissionStatus()
const microphonePermission = await Camera.getMicrophonePermissionStatus() const microphonePermission = Camera.getMicrophonePermissionStatus()
``` ```
A permission status can have the following values: A permission status can have the following values:

View File

@ -149,24 +149,24 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
return activity?.shouldShowRequestPermissionRationale(permission) ?: false return activity?.shouldShowRequestPermissionRationale(permission) ?: false
} }
@ReactMethod @ReactMethod(isBlockingSynchronousMethod = true)
fun getCameraPermissionStatus(promise: Promise) { fun getCameraPermissionStatus(): String {
val status = ContextCompat.checkSelfPermission(reactApplicationContext, Manifest.permission.CAMERA) val status = ContextCompat.checkSelfPermission(reactApplicationContext, Manifest.permission.CAMERA)
var parsed = PermissionStatus.fromPermissionStatus(status) var parsed = PermissionStatus.fromPermissionStatus(status)
if (parsed == PermissionStatus.DENIED && canRequestPermission(Manifest.permission.CAMERA)) { if (parsed == PermissionStatus.DENIED && canRequestPermission(Manifest.permission.CAMERA)) {
parsed = PermissionStatus.NOT_DETERMINED parsed = PermissionStatus.NOT_DETERMINED
} }
promise.resolve(parsed.unionValue) return parsed.unionValue
} }
@ReactMethod @ReactMethod(isBlockingSynchronousMethod = true)
fun getMicrophonePermissionStatus(promise: Promise) { fun getMicrophonePermissionStatus(): String {
val status = ContextCompat.checkSelfPermission(reactApplicationContext, Manifest.permission.RECORD_AUDIO) val status = ContextCompat.checkSelfPermission(reactApplicationContext, Manifest.permission.RECORD_AUDIO)
var parsed = PermissionStatus.fromPermissionStatus(status) var parsed = PermissionStatus.fromPermissionStatus(status)
if (parsed == PermissionStatus.DENIED && canRequestPermission(Manifest.permission.RECORD_AUDIO)) { if (parsed == PermissionStatus.DENIED && canRequestPermission(Manifest.permission.RECORD_AUDIO)) {
parsed = PermissionStatus.NOT_DETERMINED parsed = PermissionStatus.NOT_DETERMINED
} }
promise.resolve(parsed.unionValue) return parsed.unionValue
} }
@ReactMethod @ReactMethod

View File

@ -1,12 +1,12 @@
import { NavigationContainer } from '@react-navigation/native' import { NavigationContainer } from '@react-navigation/native'
import React, { useEffect, useState } from 'react' import React from 'react'
import { createNativeStackNavigator } from '@react-navigation/native-stack' import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { PermissionsPage } from './PermissionsPage' import { PermissionsPage } from './PermissionsPage'
import { MediaPage } from './MediaPage' import { MediaPage } from './MediaPage'
import { CameraPage } from './CameraPage' import { CameraPage } from './CameraPage'
import { CodeScannerPage } from './CodeScannerPage' import { CodeScannerPage } from './CodeScannerPage'
import type { Routes } from './Routes' import type { Routes } from './Routes'
import { Camera, CameraPermissionStatus } from 'react-native-vision-camera' import { Camera } from 'react-native-vision-camera'
import { GestureHandlerRootView } from 'react-native-gesture-handler' import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { StyleSheet } from 'react-native' import { StyleSheet } from 'react-native'
import { DevicesPage } from './DevicesPage' import { DevicesPage } from './DevicesPage'
@ -14,21 +14,11 @@ import { DevicesPage } from './DevicesPage'
const Stack = createNativeStackNavigator<Routes>() const Stack = createNativeStackNavigator<Routes>()
export function App(): React.ReactElement | null { export function App(): React.ReactElement | null {
const [cameraPermission, setCameraPermission] = useState<CameraPermissionStatus>() const cameraPermission = Camera.getCameraPermissionStatus()
const [microphonePermission, setMicrophonePermission] = useState<CameraPermissionStatus>() const microphonePermission = Camera.getMicrophonePermissionStatus()
useEffect(() => {
Camera.getCameraPermissionStatus().then(setCameraPermission)
Camera.getMicrophonePermissionStatus().then(setMicrophonePermission)
}, [])
console.log(`Re-rendering Navigator. Camera: ${cameraPermission} | Microphone: ${microphonePermission}`) console.log(`Re-rendering Navigator. Camera: ${cameraPermission} | Microphone: ${microphonePermission}`)
if (cameraPermission == null || microphonePermission == null) {
// still loading
return null
}
const showPermissionsPage = cameraPermission !== 'granted' || microphonePermission === 'not-determined' const showPermissionsPage = cameraPermission !== 'granted' || microphonePermission === 'not-determined'
return ( return (
<NavigationContainer> <NavigationContainer>

View File

@ -31,7 +31,7 @@ type Props = NativeStackScreenProps<Routes, 'CameraPage'>
export function CameraPage({ navigation }: Props): React.ReactElement { export function CameraPage({ navigation }: Props): React.ReactElement {
const camera = useRef<Camera>(null) const camera = useRef<Camera>(null)
const [isCameraInitialized, setIsCameraInitialized] = useState(false) const [isCameraInitialized, setIsCameraInitialized] = useState(false)
const [hasMicrophonePermission, setHasMicrophonePermission] = useState(false) const hasMicrophonePermission = useMemo(() => Camera.getMicrophonePermissionStatus() === 'granted', [])
const zoom = useSharedValue(0) const zoom = useSharedValue(0)
const isPressingButton = useSharedValue(false) const isPressingButton = useSharedValue(false)
@ -131,10 +131,6 @@ export function CameraPage({ navigation }: Props): React.ReactElement {
// Run everytime the neutralZoomScaled value changes. (reset zoom when device changes) // Run everytime the neutralZoomScaled value changes. (reset zoom when device changes)
zoom.value = neutralZoom zoom.value = neutralZoom
}, [neutralZoom, zoom]) }, [neutralZoom, zoom])
useEffect(() => {
Camera.getMicrophonePermissionStatus().then((status) => setHasMicrophonePermission(status === 'granted'))
}, [])
//#endregion //#endregion
//#region Pinch to Zoom Gesture //#region Pinch to Zoom Gesture

View File

@ -14,8 +14,8 @@
@interface RCT_EXTERN_REMAP_MODULE (CameraView, CameraViewManager, RCTViewManager) @interface RCT_EXTERN_REMAP_MODULE (CameraView, CameraViewManager, RCTViewManager)
// Module Functions // Module Functions
RCT_EXTERN_METHOD(getCameraPermissionStatus : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getCameraPermissionStatus);
RCT_EXTERN_METHOD(getMicrophonePermissionStatus : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(getMicrophonePermissionStatus);
RCT_EXTERN_METHOD(requestCameraPermission : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); RCT_EXTERN_METHOD(requestCameraPermission : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject);
RCT_EXTERN_METHOD(requestMicrophonePermission : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); RCT_EXTERN_METHOD(requestMicrophonePermission : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject);

View File

@ -84,19 +84,15 @@ final class CameraViewManager: RCTViewManager {
} }
@objc @objc
final func getCameraPermissionStatus(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { final func getCameraPermissionStatus() -> String {
withPromise(resolve: resolve, reject: reject) { let status = AVCaptureDevice.authorizationStatus(for: .video)
let status = AVCaptureDevice.authorizationStatus(for: .video) return status.descriptor
return status.descriptor
}
} }
@objc @objc
final func getMicrophonePermissionStatus(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { final func getMicrophonePermissionStatus() -> String {
withPromise(resolve: resolve, reject: reject) { let status = AVCaptureDevice.authorizationStatus(for: .audio)
let status = AVCaptureDevice.authorizationStatus(for: .audio) return status.descriptor
return status.descriptor
}
} }
@objc @objc

View File

@ -345,30 +345,18 @@ export class Camera extends React.PureComponent<CameraProps, CameraState> {
* the user has permitted the app to use the camera. * the user has permitted the app to use the camera.
* *
* To actually prompt the user for camera permission, use {@linkcode Camera.requestCameraPermission | requestCameraPermission()}. * To actually prompt the user for camera permission, use {@linkcode Camera.requestCameraPermission | requestCameraPermission()}.
*
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while getting the current permission status. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error
*/ */
public static async getCameraPermissionStatus(): Promise<CameraPermissionStatus> { public static getCameraPermissionStatus(): CameraPermissionStatus {
try { return CameraModule.getCameraPermissionStatus()
return await CameraModule.getCameraPermissionStatus()
} catch (e) {
throw tryParseNativeCameraError(e)
}
} }
/** /**
* Gets the current Microphone-Recording Permission Status. Check this before mounting the Camera to ensure * Gets the current Microphone-Recording Permission Status. Check this before mounting the Camera to ensure
* the user has permitted the app to use the microphone. * the user has permitted the app to use the microphone.
* *
* To actually prompt the user for microphone permission, use {@linkcode Camera.requestMicrophonePermission | requestMicrophonePermission()}. * To actually prompt the user for microphone permission, use {@linkcode Camera.requestMicrophonePermission | requestMicrophonePermission()}.
*
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while getting the current permission status. Use the {@linkcode CameraRuntimeError.code | code} property to get the actual error
*/ */
public static async getMicrophonePermissionStatus(): Promise<CameraPermissionStatus> { public static getMicrophonePermissionStatus(): CameraPermissionStatus {
try { return CameraModule.getMicrophonePermissionStatus()
return await CameraModule.getMicrophonePermissionStatus()
} catch (e) {
throw tryParseNativeCameraError(e)
}
} }
/** /**
* Shows a "request permission" alert to the user, and resolves with the new camera permission status. * Shows a "request permission" alert to the user, and resolves with the new camera permission status.

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react' import { useCallback, useState } from 'react'
import { Camera } from '../Camera' import { Camera } from '../Camera'
interface PermissionState { interface PermissionState {
@ -31,7 +31,7 @@ interface PermissionState {
* ``` * ```
*/ */
export function useCameraPermission(): PermissionState { export function useCameraPermission(): PermissionState {
const [hasPermission, setHasPermission] = useState(false) const [hasPermission, setHasPermission] = useState(() => Camera.getCameraPermissionStatus() === 'granted')
const requestPermission = useCallback(async () => { const requestPermission = useCallback(async () => {
const result = await Camera.requestCameraPermission() const result = await Camera.requestCameraPermission()
@ -40,10 +40,6 @@ export function useCameraPermission(): PermissionState {
return hasPermissionNow return hasPermissionNow
}, []) }, [])
useEffect(() => {
Camera.getCameraPermissionStatus().then((s) => setHasPermission(s === 'granted'))
}, [])
return { return {
hasPermission, hasPermission,
requestPermission, requestPermission,
@ -65,7 +61,7 @@ export function useCameraPermission(): PermissionState {
* ``` * ```
*/ */
export function useMicrophonePermission(): PermissionState { export function useMicrophonePermission(): PermissionState {
const [hasPermission, setHasPermission] = useState(false) const [hasPermission, setHasPermission] = useState(() => Camera.getMicrophonePermissionStatus() === 'granted')
const requestPermission = useCallback(async () => { const requestPermission = useCallback(async () => {
const result = await Camera.requestMicrophonePermission() const result = await Camera.requestMicrophonePermission()
@ -74,10 +70,6 @@ export function useMicrophonePermission(): PermissionState {
return hasPermissionNow return hasPermissionNow
}, []) }, [])
useEffect(() => {
Camera.getMicrophonePermissionStatus().then((s) => setHasPermission(s === 'granted'))
}, [])
return { return {
hasPermission, hasPermission,
requestPermission, requestPermission,