본문 바로가기

리액트네이티브(ReactNative)

[카메라어플](1) : 카메라,마이크 접근 권한 받기,카메라 연결

728x90

기존에 리액트 네이티브에서 카메라 어플을 만들 때 많이 사용하던 react-native-camer가 deprecated되어 react-native-vision-camera로 테스트를 진행하기로 하였다.

Due to the lack of maintainers and increased code complexity, react-native-camera is now deprecated in favor of react-native-vision-camera.

VisionCamera offers new APIs, better performance, improved stability and more features. It is actively maintained by @mrousavy and used in many production apps

대략 react-native-camera보다 사람들이 react-native-vision-camera를 더 선호하고 vision-camera가 기능도 더 많고 안정적이고 퍼포먼스도 더 좋은 짱짱맨이니 이거 쓰라는 말...

 

먼저 module을 install해준다.

 

npm i react-native-vision-camera

언제나 모듈을 설치할 때가 가장 즐겁다 ><

 

VisionCamera requires iOS 11 or higher, and  Android-SDK version 21 or higher

VisionCamera는 iOS 11 이상의 버전과 Android-SDK 21이상의 버전을 요구한다.

자신의 iOS의 버전을 확인하기 위해서는 좌측 최상단 사과 모양의 로고 > 시스템 설정 > 일반 > macOS을 살펴보자.

android - sdk는 본인이 에뮬레이터를 실행하는 환경을 설정할 때 21이상의 버전으로 체크하면 된다.(나는 30,29 두개를 install 해서 사용중이니 상관없음!!)

 

그리고 ios의 Info.plist에 아래의 코드를 추가해준다.

<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to your Camera.</string>

<!-- optionally, if you want to record audio: -->
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to your Microphone.</string>

$(PRODUCT_NAME)에 현재 진행하고 있는 프로젝트의 이름을 적어주면 된다.

소리 녹음도 하고 싶다면 아래 마이크 접근 권한까지 넣어준다. 

 

다음은 android > app > src > main > AndroidManifest.xml에 들어가서 permission이 있는 곳에 아래의 코드를 추가해준다.

<uses-permission android:name="android.permission.CAMERA" />
<!-- optionally, if you want to record audio: -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />

 

위의 코드를 추가해주면 어플을 이용하는 사람이 카메라와 오디오에 대한 접근 권한에 대한 동의/미동의를 선택하는 다이얼로그가 뜰 것이다.

 

테스트는 src의 main.tsx에서 진행한다.

import { Camera, CameraPermissionStatus } from 'react-native-vision-camera';

creact-native-camer에서 Camera와 CameraPermissionStatus를 가져온다.

 

import React, { useEffect, useState } from 'react';
import { FC } from 'react';
import { appStore } from './store';
import { Camera, CameraPermissionStatus } from 'react-native-vision-camera';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export const Main: FC = () => {
  const { barStyle, backgroundColor } = appStore.useInitTheme();

  const [cameraPermission, setCameraPermission] = useState<CameraPermissionStatus>();
  const [microphonePermission, setMicrophonePermission] = useState<CameraPermissionStatus>();

  const checkCameraPermissionState = async () => {
    const cameraPermission = await Camera.getCameraPermissionStatus();
    console.log(cameraPermission, 'camera');
  };

  const checkAudioPermissionState = async () => {
    const micPermission = await Camera.getMicrophonePermissionStatus();
    console.log(micPermission, 'microphone');
  };

  useEffect(() => {
    checkCameraPermissionState();
    checkAudioPermissionState();
  }, []);

  return <GestureHandlerRootView>
    
  </GestureHandlerRootView>;
};

 

먼저 cameraPermission상태와 audioPermission상태를 관리하기 위해 각각의 state를 useState로 만들어준다.

vision-camera에서 가져온 카메라에서 getCameraPermissionStatus()과 getMicrophonePermissionStatus()함수를 실행시키면 비동기로 특정 값을 실행 반환하는데 이 값들을 각각 cameraPermission, micPermission이라 칭하고 useEffect를 통해 앱이 처음 실행 될 때 위 값들을 console.log로 찍어보았다.

 LOG  denied camera
 LOG  denied microphone
 LOG  denied camera
 LOG  denied microphone

 

 

denied라는 값들이 찍히고 있었다.

 

react-native-vision-camera의 문서에 따르면, Camera의 getMicrophonePermissionStatus()와 getCameraPermissionStatus()의 함수는 총 4가지의 값들을 반환한다.

 

1.authorized : 사용자가 접근 권한을 허락한 상태

2.not-determined : 앱이 아직 사용자에게 권한 요청을 보내지 않은 상황

3.denied : 앱이 사용자에게 권한 요청을 보냈으나 사용자가 거부한 상황. 이 때 요청을 다시 보내는 것은 불가능하지만, Linking Api를 통해 사용자가 직접 해당 앱에 권한을 주도록 리다이렉트 시키는 것은 가능하다.

4.restricted : iOS에서만 가능, parental controls등의 이유로 카메라와 마이크가 제한되어 있는 특수한 상황

 

분명히 나는 앱 요청 권한을 거부 한 적이 없는데 emulator에서는 denied로 찍히고 있었다. 이 문제를 해결하기 위해 한시간 넘게 컴퓨터를 재실행하고, emulator를 바꿔보는 등 별 삽질을 다하다가 아래의 글을 읽어보게 되었다.

 

https://stackoverflow.com/questions/11844590/android-emulator-permission

 

Android emulator permission

Android emulator is not asking for any permission while running an application which uses protective functions like dialing a number etc. Is the user consent asked only while running on a mobile an...

stackoverflow.com

 

꽤 예전의 질문이라서 신빙성이 좀 낮았지만 대충 해석해보자면 android emulator가 permission을 묻지 않는다는 나와 비슷한 상황에 처한 질문자의 상황에 permission은 앱을 install하는 때에 딱 한번 물어보는 것이다. 그래서 emulator에서는 작업을 하기 위해서는 본인이 수동으로 emulator의 permission을 풀어줘야 한다는 것이다.(아래 읽어보면 굳이 안해도 됨)

 

https://o7planning.org/10533/how-to-disable-the-permissions-already-granted-to-the-android-application

 

How to disable the permissions already granted to the Android application? | o7planning.org

I put out a situation, you create an Android application, such a small application used Camera to record video. With Android API <23, you must grant permissions to use the Camera in AndroidManifest.xml. With Android API> = 23,  you need to ask the user by

o7planning.org

위의 사이트에서 emulator에서 어떻게 직접 permission을 줄 수 있는지 친절히 gif로 보여주고 있어서 따라서 카메라와 마이크의 접근 권한을 부여해주었고 다시 npm run android를 실행하자 이번에는 마이크와 카메라의 접근 권한이 둘다 authorized로 뜨는 것을 확인할 수 있었다.

 

그리고 나서 또 한참동안 이어진 카메라 연결과의 삽질...

import React, { useEffect, useState, useRef, useCallback } from 'react';
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
import { FC } from 'react';
import { appStore } from './store';
import {
  Camera,
  CameraPermissionStatus,
  useCameraDevices,
  CameraRuntimeError,
} from 'react-native-vision-camera';
import { PermissionsAndroid } from 'react-native';

export const Main: FC = () => {
  const { barStyle, backgroundColor } = appStore.useInitTheme();

  const [cameraPermission, setCameraPermission] = useState<CameraPermissionStatus>();
  const [microphonePermission, setMicrophonePermission] = useState<CameraPermissionStatus>();
  const camera = useRef<Camera>(null);
  const devices = useCameraDevices();
  const device = devices.back;

  useEffect(() => {
    Camera.getCameraPermissionStatus().then(setCameraPermission);
    Camera.getMicrophonePermissionStatus().then(setMicrophonePermission);
  }, [cameraPermission, microphonePermission]);

  const requestCameraPermission = async () => {
    try {
      const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
        title: 'Cool Photo App Camera Permission',
        message:
          'Cool Photo App needs access to your camera ' + 'so you can take awesome pictures.',
        buttonNeutral: 'Ask Me Later',
        buttonNegative: 'Cancel',
        buttonPositive: 'OK',
      });
      if (granted === PermissionsAndroid.RESULTS.GRANTED) {
        console.log('You can use the camera');
      } else {
        console.log('Camera permission denied');
      }
    } catch (err) {
      console.warn(err);
    }
  };

  useEffect(() => {
    requestCameraPermission();
  }, []);

  const onError = useCallback((error: CameraRuntimeError) => {
    console.error(error);
  }, []);

  if (device == null) {
    return <ActivityIndicator size={20} color={'red'} />;
  }

  return (
    <>
      {device && (
        <Camera style={StyleSheet.absoluteFill} isActive photo device={device} ref={camera} />
      )}
    </>
  );
};


  useEffect(() => {
    requestCameraPermission();
  }, []);

  const onError = useCallback((error: CameraRuntimeError) => {
    console.error(error);
  }, []);

  if (device == null) {
    return <ActivityIndicator size={20} color={'red'} />;
  }

  return (
    <>
      {device && (
        <Camera style={StyleSheet.absoluteFill} isActive photo device={device} ref={camera} />
      )}
    </>
  );
};

 

emulator상의 카메라 연결 인증샷... 이거 보려고 세시간 넘게 삽질한듯...

 

카메라 연결이 안될 때 몇가지 사용하였던 방법들을 나열해보자면...

 

1. requsteCameraPermission()함수를 useEffect()안에서 실행시킴

=> 당연하게도 저 코드는 내가 직접 작성한 것이 아니다. 이미 어플리케이션안에서 getCameraPermissionStatus()함수를 작동시켰는데 뭐가 문제야 + 직접 손으로 권한 authroized 시켜줬는데? 라고 의문이 들기는 하지만, 확실히 저 함수를 실행하고 나니 emulator상에서 앱 실행시 권한 요청에 대한 다이얼로그가 떴다. (하긴 그렇게 허술할 리가 없지...) 다시 생각해보면 getCameraPermissionStatus()는 정말 함수 이름대로 권한에 대한 상태값을 말그대로 가져오는것이고, 권한을 받는 것은 직접 코드를 짜줘야 하는것 같다... react-native-vision-camera 개발자가 쓴 글을 읽어보니 본인들이 이 모듈을 만들때 그것을 빠트렸고, 손수 이런 코드 추가하세요...라고 짜 놓은 코드도 있었다.ㅠㅠ

 

2. Camera 컴포넌트 최 상단을 <></>로 감싸줌... 이것도 구글링하다가 발견한 코드이다. react-vision-camera를 이용하는 대부분은 <Camera>를 별도의 페이지에서 사용하고 이를 main.tsx등에 임포트해서 사용하기 때문에 <GestureHandlerRootView> 내부에 직접적으로 <Camera>가 감싸져 있는 형태로 코드가 짜져있지는 않았다. 이것도 누군가 stackOverflow에서 언급을 해서 일단은 <></>안에 <Camera>를 넣어주었다.

 

3.device가 null이면 카메라가 연결되지 않도록 분기처리 !! <= 이건 react-native-vision-camera 개발자가 직접 저렇게 안쓰면 에러난다고 언급한 부분이다! 

 

4.emulator에서 카메라 설정을 바꿔줌... <= 사실상 여기서 모든 문제가 해결이 되었다. 계속해서 device.id가 undefined가 뜨며 화면전체가 black screen, green screen으로  보이는 현상이 지속되었다. 이걸 해결하기 위해 devices 부분을 console.log()를 찍어서 사용가능한 camera의 목록을 확인했지만, 대부분의 다른 사람들과는 달리 back에 undfined 이 뜨고, front 에만 카메라 두대가 있는 것을 확인했다. 그래서 devices.back이 아니라 front, front.devices, front.devices[0]등을 device에 연결해봐도 문제는 해결되지 않았다.  결국에 내가 보고자 하는 카메라는 android sdk상의 emulator의 카메라이고, 실제 내 핸드폰의 카메라가 아니니 이건 분명히 emulator상의 문제라고 판단하였고, android sdk의 advancedSetting에서 front와 back의 camera를 webcam으로 바꿔주니 emulator에 내 얼굴이 나왔다.  <= 조금 더 연구가 필요함, 왜 virtual camera가 연결이 안되는지...

728x90