React Native Vision Camera: Installation, Picture Capture, and Video Recording
Tags: tutorial, reactnative
Published: 2023-06-24 20:48:53

## Introduction
[React-Native-Camera](https://github.com/react-native-camera/react-native-camera), while once popular and powerful, has been deprecated, leaving developers in search of an alternative. Enter VisionCamera, a fully featured camera library developed by [mrousavy](https://github.com/mrousavy). 

In this series of articles, we will explore the power and versatility of VisionCamera. From installation and basic usage to more advanced features such as picture capture, video recording, and even face detection, this guide will equip you with the knowledge and skills to leverage VisionCamera to its fullest potential in your React Native applications. So let's dive in and unlock the possibilities of VisionCamera together.

## Section 1: Setting up Vision Camera
### 1.1 Installing react-native-vision-camera:
Install _react-native-vision-camera_ with yarn:

`yarn add react-native-vision-camera`

And make sure to install pod dependencies:

`cd ios && pod install && cd ..`

### 1.2 Configuring iOS:
Open your project's `Info.plist` and add the following lines inside the outermost `<dict>` tag:

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

The `$(PRODUCT_NAME)` is a variable of your application name
### 1.3 Configuring Android:
Open your project's `AndroidManifest.xml` and add the following lines inside the `<manifest>` tag:

```
<uses-permission android:name="android.permission.CAMERA" />
```

### 1.4 Getting/Requesting Permissions:
To find out if a user has granted or denied:

```
const cameraPermission = await Camera.getCameraPermissionStatus()
```

To get permission to use Camera:

```
const newCameraPermission = await Camera.requestCameraPermission()
```

But if user denied, user has go to settings and allow camera permission manually. There for we should double check the camera permission and open settings if user is not granted.
So this is the full permission handle code:

```
import {Alert, Linking} from 'react-native';
import {Camera} from 'react-native-vision-camera';

export const requestCameraPermission = async () => {
  const cameraPermission = await Camera.requestCameraPermission();
  
  if (cameraPermission !== 'authorized') {
    Alert.alert(
      'You need to allow camera permission.',
      'Please go to Settings and allow camera permission',
      [
        {
          text: 'Open Settings',
          onPress: Linking.openSettings,
        },
      ],
    );
  }

  return cameraPermission;
};
```

## Section 2: Picture Capture
### 2.1: Camera setup
#### Camera devices

> Camera devices are the physical (or "virtual") devices that can be used to record videos or capture photos.

To get a camera device, we use the `useCameraDevices` hook and specific with camera could be used

```
const devices = useCameraDevices();

  const device = devices.back;

  if (!device) {
    return <ActivityIndicator />;
  }

return (
   <Camera
     style={StyleSheet.absoluteFill}
     device={device}
     isActive
   />
  );
```

#### camera ref
we use ref to access the photo capture function

```
  const camera = useRef<Camera>(null);
/*...*/

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

#### CameraWrapper component
I added this component with the aim of being able to easily switch between showing the camera as a separate screen or as a modal. You can skip this component

```
// CameraWrapper.tsx
import React from 'react';
import {ActivityIndicator, Modal, SafeAreaView, StyleSheet} from 'react-native';

interface CameraWrapperProps {
  isActive: boolean;
  onInactive?: () => void;
  children: React.ReactNode;
  loading?: boolean;
}

const CameraWrapper = (props: CameraWrapperProps) => {
  const {isActive, onInactive, children, loading} = props;

  return (
    <Modal
      visible={isActive}
      onRequestClose={onInactive}
      style={styles.container}>
      <SafeAreaView style={StyleSheet.absoluteFill}>
        {loading ? <ActivityIndicator style={styles.loading} /> : children}
      </SafeAreaView>
    </Modal>
  );
};

export default CameraWrapper;

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  loading: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});
```

### 2.2 Taking picture:
To taking a picture use the camera ref:

```
const photo = await camera.current.takePhoto()
```

#### Capture button
To do the image capture, I write a component that manages the capture. This is the photo button that receives the props as the camera ref and a callback function that returns the captured image:

```
import React, {RefObject, useCallback} from 'react';
import {StyleSheet, TouchableOpacity, View, ViewProps} from 'react-native';
import {Camera, PhotoFile} from 'react-native-vision-camera';

interface CaptureButtonProps extends ViewProps {
  camera: RefObject<Camera>;
  onMediaCaptured: (file: PhotoFile) => void;
}

const CaptureButton = (props: CaptureButtonProps) => {
  const {camera, onMediaCaptured, style, ...rest} = props;

  const onPress = useCallback(async () => {
    const photo = await camera.current?.takePhoto();
    onMediaCaptured(photo!);
  }, [camera, onMediaCaptured]);

  return (
    <TouchableOpacity
      {...rest}
      onPress={onPress}
      style={[styles.captureButton, style]}>
      <View style={styles.captureButtonInner} />
    </TouchableOpacity>
  );
};

export default CaptureButton;

const styles = StyleSheet.create({
  captureButton: {
    width: 80,
    height: 80,
    borderRadius: 40,
    padding: 4,
    borderWidth: 2,
    borderStyle: 'dotted',
    borderColor: 'white',
  },
  captureButtonInner: {
    flex: 1,
    borderRadius: 40,
    backgroundColor: 'white',
  },
});
```

And use it inside our TakePicture component:

```
  const onPhotoCaptured = useCallback((file: PhotoFile) => {
    console.log(file);
  }, []);

  return (
    <CameraWrapper isActive={isActive} loading={!device} onInactive={onInactive}>
      <Camera
        ref={camera}
        style={StyleSheet.absoluteFill}
        device={device!}
        isActive={isActive}
        photo
      />
      <CaptureButton
        camera={camera}
        style={styles.captureButton}
        onMediaCaptured={onPhotoCaptured}
      />
    </CameraWrapper>
  );
```

Console result:

![Captured picture log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhbh0d7zs19ua13bsbg4.png)
### 2.3 Preview captured picture
We can use react-native's Image to display the result of our capture feature

```
import React, {useMemo} from 'react';
import {Button, Image, Modal, SafeAreaView, StyleSheet} from 'react-native';

interface MediaPreviewProps {
  mediaPath?: string;
  onInactive?: () => void;
}

const MediaPreview = ({mediaPath, onInactive}: MediaPreviewProps) => {
  const source = useMemo(() => ({uri: mediaPath}), [mediaPath]);

  return (
    <Modal visible={!!mediaPath} onRequestClose={onInactive}>
      <SafeAreaView style={[StyleSheet.absoluteFill]}>
        <Image source={source} style={StyleSheet.absoluteFill} />
        <Button onPress={onInactive} title="Close" />
      </SafeAreaView>
    </Modal>
  );
};

export default MediaPreview;
```

### 2.4 Zooming
To let user to zoom with native pinch to zoom gesture you can enable with the [enableZoomGesture](https://www.react-native-vision-camera.com/docs/api/interfaces/CameraProps#enablezoomgesture) prop

```
      <Camera
        ref={camera}
        style={StyleSheet.absoluteFill}
        device={device!}
        isActive={isActive}
        photo
        // for zooming
        enableZoomGesture
      />
```

### 2.5 Focusing
To focus the camera to a specific point, simply use the Camera's [focus(...)](https://www.react-native-vision-camera.com/docs/api/classes/Camera#focus) function with `onTouchStart` of any View component:

```
      <Camera
        ref={camera}
        style={StyleSheet.absoluteFill}
        device={device!}
        isActive={isActive}
        photo
        // For focusing
        onTouchStart={handleFocus}
        enableZoomGesture
      />
```
Handling focus touch:

```
 const handleFocus = useCallback(
    async ({nativeEvent}: GestureResponderEvent) => {
      await camera?.current?.focus({
        x: Math.round(nativeEvent.pageX),
        y: Math.round(nativeEvent.pageX),
      });
    },
    [],
  );
```

## Section 3: Recording Videos
### 3.1 Record audio permissions (Optional):
If you want to record audio on recording video, you have to update your application's manifests
##### iOS
Open your project's `Info.plist` and add the following lines inside the outermost `<dict>` tag:

```
key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to your Microphone.</string>
```

#### Android
Open your project's `AndroidManifest.xml` and add the following lines inside the `<manifest>` tag:

```
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```

and we also have the `requestMicrophonePermission` function:

```
import {Alert, Linking} from 'react-native';
import {Camera} from 'react-native-vision-camera';

export const requestMicrophonePermission = async () => {
  const microphonePermission = await Camera.requestMicrophonePermission();
  
  if (microphonePermission !== 'authorized') {
    Alert.alert(
      'You need to allow microphone permission.',
      'Please go to Settings and allow microphone permission',
      [
        {
          text: 'Open Settings',
          
          onPress: Linking.openSettings,
        },
      ],
    );
  }

  return microphonePermission;
};
```

### 3.2 Update the Camera component:
In this section I will copy the component of the photography section, which means that video recording will also have full features like focus or zoom. For Camera to record video add `video` prop and if you want to record audio add `audio` prop:

```
      <Camera
        ref={camera}
        style={StyleSheet.absoluteFill}
        device={device!}
        isActive={isActive}
        onTouchStart={handleFocus}
        enableZoomGesture
        photo
        video
        audio // optional
      />
```
### 3.3 Hold Capture button to start recording video
To start recording, the user will hold down the capture button. The button will turn red indicating the recording status and when the user clicks this button again, the video recording will end.
I also updated the onMediaCaptured callback to add the VideoFile data type

```
// import

interface CaptureButtonProps extends ViewProps {
  camera: RefObject<Camera>;
  onMediaCaptured: (
    media: PhotoFile | VideoFile,
    type: 'photo' | 'video',
  ) => void;
}

const CaptureButton = (props: CaptureButtonProps) => {
  const {camera, onMediaCaptured, style, ...rest} = props;
  const [isRecording, setIsRecording] = React.useState(false);

  const innerStyle = useMemo(
    () => ({
      backgroundColor: isRecording ? 'red' : 'white',
    }),
    [isRecording],
  );

  const takePhoto = useCallback(async () => {
    if (isRecording) {
      setIsRecording(false);
      return camera.current?.stopRecording();
    }
    const photo = await camera.current?.takePhoto();
    onMediaCaptured(photo!, 'photo');
  }, [camera, onMediaCaptured, isRecording]);

  const startRecordingVideo = useCallback(async () => {
    setIsRecording(true);
    return camera.current?.startRecording({
      onRecordingFinished: video => onMediaCaptured(video, 'video'),
      onRecordingError: error => console.error(error),
    });
  }, [camera, onMediaCaptured]);

  return (
    <TouchableOpacity
      {...rest}
      style={[styles.captureButton, style]}
      onPress={takePhoto}
      onLongPress={startRecordingVideo}>
      <View style={[styles.captureButtonInner, innerStyle]} />
    </TouchableOpacity>
  );
};

export default CaptureButton;
```

### 3.4 Preview video
We use [react-native-video](https://github.com/react-native-video/react-native-video) to display the recorded video.

Install it with yarn:

`yarn add react-native-video`

And make sure to install pod dependencies:

`cd ios && pod install && cd ..`

Usage:

```
          <Video
            source={source}
            style={StyleSheet.absoluteFill}
            resizeMode="cover"
            posterResizeMode="cover"
            allowsExternalPlayback={false}
          automaticallyWaitsToMinimizeStalling={false}
            disableFocus={true}
            repeat={true}
            useTextureView={false}
            controls={false}
            playWhenInactive={true}
          />
```

## Section 4: Saving the media
### 4.1 Requesting Permissions

```
import {Alert, Linking, PermissionsAndroid, Platform} from 'react-native';

export const requestSavePermission = async () => {
  if (Platform.OS !== 'android') {
    return true;
  }

  const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE;
  if (permission == null) {
    return false;
  }
  let hasPermission = await PermissionsAndroid.check(permission);
  if (!hasPermission) {
    const permissionRequestResult = await PermissionsAndroid.request(
      permission,
    );
    hasPermission = permissionRequestResult === 'granted';
  }
  if (!hasPermission) {
    Alert.alert(
      'You need to allow storage permission.',
      'Please go to Settings and allow storage permission',
      [
        {
          text: 'Open Settings',
          // On pressing the button, we will open the settings
          onPress: Linking.openSettings,
        },
      ],
    );
  }

  return hasPermission;
};
```

We use [@react-native-camera-roll/camera-roll](https://github.com/react-native-cameraroll/react-native-cameraroll) to saving media.

Install it with yarn:
`yarn add @react-native-camera-roll/camera-roll`
And make sure to install pod dependencies:

`cd ios && pod install && cd ..`

Open your project's `Info.plist` and add the following lines inside the outermost `<dict>` tag:

```	
<key>NSPhotoLibraryUsageDescription</key>
	<string>$(PRODUCT_NAME) needs access to your Photo Library.</string>
```

Open your project's `AndroidManifest.xml` and add `android:requestLegacyExternalStorage="true"` to `application` tag and the following inside `manifest` tag:

```
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```

Usage:

```
 const handleSave = useCallback(async () => {
    setSaveStatus('saving');
    const hasPermission = await requestSavePermission();

    if (hasPermission) {
      await CameraRoll.save(`file://${mediaPath}`, {
        type,
      });
      setSaveStatus('saved');
    }
  }, [mediaPath, type]);
```

## Conclusion:
We have explored the power and versatility of VisionCamera as an alternative to the deprecated React-Native-Camera library. We have covered the installation process and basic configuration steps for both iOS and Android platforms. Additionally, we have discussed how to handle camera permissions and provided code examples for requesting and checking camera permissions.

I hope that this series of articles has provided you with a comprehensive understanding of VisionCamera and its capabilities. By leveraging VisionCamera, you can enhance your React Native applications with powerful camera features, such as picture capture and video recording. In the next article, we will dive deeper into customizing the camera interface by exploring options for zooming and configuring photo/video settings.

Stay tuned for the next article, where we will explore custom zoom options and provide insights on configuring photo and video settings to further enhance your camera functionality.

To access the complete source code and examples discussed in this series, please visit the GitHub repository: [this](https://github.com/thaind0/RnVisionCameraGuide)

## References:
https://www.react-native-vision-camera.com
https://github.com/mrousavy/react-native-vision-camera
https://www.npmjs.com/package/react-native-video
https://www.npmjs.com/package/@react-native-camera-roll/camera-roll