DEV Community

thaind0
thaind0

Posted on

React Native Vision Camera: Installation, Picture Capture, and Video Recording

Introduction

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.

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'>
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

1.4 Getting/Requesting Permissions:

To find out if a user has granted or denied:

const cameraPermission = await Camera.getCameraPermissionStatus()
Enter fullscreen mode Exit fullscreen mode

To get permission to use Camera:

const newCameraPermission = await Camera.requestCameraPermission()
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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
   />
  );
Enter fullscreen mode Exit fullscreen mode

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
   />
  );
Enter fullscreen mode Exit fullscreen mode

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',
  },
});
Enter fullscreen mode Exit fullscreen mode

2.2 Taking picture:

To taking a picture use the camera ref:

const photo = await camera.current.takePhoto()
Enter fullscreen mode Exit fullscreen mode

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',
  },
});
Enter fullscreen mode Exit fullscreen mode

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>
  );
Enter fullscreen mode Exit fullscreen mode

Console result:

Captured picture log

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;
Enter fullscreen mode Exit fullscreen mode

2.4 Zooming

To let user to zoom with native pinch to zoom gesture you can enable with the enableZoomGesture prop

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

2.5 Focusing

To focus the camera to a specific point, simply use the Camera's focus(...) function with onTouchStart of any View component:

      <Camera
        ref={camera}
        style={StyleSheet.absoluteFill}
        device={device!}
        isActive={isActive}
        photo
        // For focusing
        onTouchStart={handleFocus}
        enableZoomGesture
      />
Enter fullscreen mode Exit fullscreen mode

Handling focus touch:

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

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>
Enter fullscreen mode Exit fullscreen mode

Android

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

<uses-permission android:name="android.permission.RECORD_AUDIO" />
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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
      />
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

3.4 Preview video

We use 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}
          />
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

We use @react-native-camera-roll/camera-roll 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>
Enter fullscreen mode Exit fullscreen mode

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"/>
Enter fullscreen mode Exit fullscreen mode

Usage:

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

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

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

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

Top comments (0)