mrousavy / react-native-vision-camera

📸 A powerful, high-performance React Native Camera library.
https://react-native-vision-camera.com
MIT License
7.37k stars 1.08k forks source link

🐛 App unable to go to sleep once camera loads #3041

Open FoundersApproach opened 3 months ago

FoundersApproach commented 3 months ago

What's happening?

Once camera library loads in one screen and going back to previous screen in stack navigation then app unable to goes to sleep. May be camera reference object is unable to let app sleep ?

I did some object reference clearance in component unmount but no luck. Please help!

Reproduceable Code

import React, { useState, useEffect, useCallback } from 'react';
import { View, Text, TouchableOpacity, Alert, Platform, StyleSheet, PermissionsAndroid, Image, AppState } from 'react-native';
import {Camera, useCameraDevices, useCameraDevice,useCameraFormat, Templates, useCodeScanner } from 'react-native-vision-camera';
import TextRecognition from '@react-native-ml-kit/text-recognition';
import AppConstants from '../module/constantVairable'
import { getCameraFace, getCurrentOrientation, getDeviceHeight, getDeviceWidth, getSafeAreaInsetBottom, getSafeAreaInsetTop, getTorchOn, isOrientationPortrait, setCameraRef, setScanLog, verifyGTINChecksum } from '../api/api';
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import ImageResizer from '@bam.tech/react-native-image-resizer';
import { RFValue } from 'react-native-responsive-fontsize';
import RNFS from 'react-native-fs';
import Animated, { runOnJS } from 'react-native-reanimated';

let cameraRef = null
let capturePhotoInteval = false
let focusPointTimer = false

const ScannerFunction = (props) => {
    const device = useCameraDevice(getCameraFace());
    const [permissionGranted, setPermissionGranted] = useState(false);
    const [isInitialized, setIsInitialized] = useState(false);
    const [torch, setTorch] = useState(false);
    const [restartCamera, setRestartCamera] = useState(false);
    const [cameraActive, setCameraActive] = useState(true);

    const [tapLocation, setTapLocation] = useState(null);

    useEffect(() => {
        // let appStateListener = AppState.addEventListener('change', appStateChangeListener);
        if (Platform.OS === 'android') {
          askCameraPermission();
        }
        else {
          setPermissionGranted(true)
          if (device && device.hasTorch && getTorchOn()) {
            setTorch(true)
          }
        }
        startCaptureTimer(2)

        return () => {
          cameraRef = null
          console.log("ScannerFunction unmount>>>>")
          setCameraActive(false)
          setTorch(false)
          setPermissionGranted(false)
          stopCaptureTimer()
          setCameraRef(null)
        }
    }, []);

    const startCaptureTimer = (sec=1) => {
      // console.log("startCaptureTimer>>>>")
      if (capturePhotoInteval) {
        clearInterval(capturePhotoInteval);
      }

      capturePhotoInteval = setInterval(() => {
        // console.log("capturePhotoInteval")
        capturePhoto()
      }, 2 * 1000);
    }

    const stopCaptureTimer = () => {
      if (capturePhotoInteval) {
        // console.log("clear interval")
        clearInterval(capturePhotoInteval);
      }
    }

    const format1 = useCameraFormat(device, [
      { photoResolution: "max" },
    ])

    const askCameraPermission = async () => {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.CAMERA,
        {
          title: "Camera Permission",
          message: "This app needs access to your camera ",
        }
      );
      if (granted === PermissionsAndroid.RESULTS.GRANTED) {
        console.log("You can use the camera");
        setPermissionGranted(true)
      } else {
        console.log("Camera permission denied");
      }
    }

    const parseText = (line, wholeText="", photoUrl="") => {
    }

    const capturePhoto = async () => {

        try {
          if (props.is_active) {
            if (cameraRef) {

              const photo = await cameraRef.takePhoto({
                qualityPrioritization: "quality",
                flash: "off",
                enableAutoStabilization: true,
                enableShutterSound: false
              });

              let photoUrl = "file://"+photo.path

              stopCaptureTimer()
              const result = await TextRecognition.recognize(photoUrl);

              for (let block of result.blocks) {

                for (let line of block.lines) {
                  setScanLog(line.text)
                  parseText(line.text, result.text, photoUrl)
                }
              }

              startCaptureTimer(2)

            }
            else {
              // Alert.alert("Camera not found")
            }
          }

        } catch (error) {
          setCameraActive(false)
          setCameraRef(null)
          cameraRef = null
          props.onError(error)
          console.log("Error>>>>>>>>>>>>", error.message)
        }

    }

    const focus = useCallback((point: Point) => {
      const c = cameraRef
      if (c == null) return
      c.focus(point)
      setTapLocation({x: point.x - RFValue(35), y: point.y - RFValue(35)})
      if (focusPointTimer) {
        clearTimeout(focusPointTimer)
      }
      focusPointTimer = setTimeout(() => {
        setTapLocation(null)
      }, 2 * 1000)
    }, [])

    const gesture = Gesture.Tap()
    .onEnd(({ x, y }) => {
      runOnJS(focus)({ x, y })
    })

    const _onError = (error) => {
    }

    return(
      <View style={{flex: 1, overflow: "hidden", backgroundColor: "black"}}>
      {/* <GestureDetector gesture={_gesture}> */}
      {
        device && device.hasTorch ? (
          <TouchableOpacity 
              onPress={() => setTorch(!torch)}
              style={{position: 'absolute', zIndex: 999 ,
              top: isOrientationPortrait() ? RFValue(10) : null, 
              bottom: isOrientationPortrait() ? null : (getSafeAreaInsetTop() + getSafeAreaInsetBottom() + RFValue(10)),
              right: RFValue(10), 
              height: RFValue(35), 
              width: RFValue(35), 
              transform: [{rotate: isOrientationPortrait() ? '0deg' : '-270deg'}],
              borderRadius: RFValue(5) ,backgroundColor: "#ffffff99", 
              alignItems: 'center', justifyContent: 'center'}}>
              <Image resizeMode='contain' style={{height: '65%', width: '65%'}} 
                  source={torch ? Theme.icons.ic_camera_light_off : Theme.icons.ic_camera_light_on}></Image>
          </TouchableOpacity> 
        ) : null
      }

      {/* this.onStopped = this.onStopped.bind(this)
    this.onError = this.onError.bind(this) */}

      {
        device && permissionGranted && props.is_active ? (
          <View style={{flex: 1}}>
          <GestureDetector gesture={gesture}>
          <Camera
            onError={(error)=>props.onError(error)}
          orientation={getCurrentOrientation()}
          torch={torch ? "on" : "off"}
          onInitialized={() => {
            setIsInitialized(true);
            setCameraRef(cameraRef)
          }}
              style={
                isInitialized
                  ? {
                      position: "absolute",
                      top: 0,
                      left: 0,
                      right: 0,
                      bottom: 0
                    }
                  : {
                      width: 0,
                      height: 0,
                    }
               }
              device={device}
              isActive={cameraActive}
              photo={true}
              ref={(ref) => cameraRef = ref}

            />

            </GestureDetector>
            {
              tapLocation && (
                <Animated.View
                  style={[
                    styles.indicator,
                    {
                      transform: [
                        { translateX: tapLocation.x - 15 },
                        { translateY: tapLocation.y - 15 },
                      ],
                    },
                  ]}
                />
              )
            }

            </View>
        ) : null
      }
      </View>
    )
}
export default ScannerFunction;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'transparent',
  },
  indicator: {
    width: RFValue(70),
    height: RFValue(70),
    borderRadius: RFValue(70),
    position: 'absolute',
    borderWidth: 4,
    borderColor: Theme.colors.appThemeColor,
  },
});

Relevant log output

This issue seems different so no log helps

Camera Device

<Camera
            onError={(error)=>props.onError(error)}
          orientation={getCurrentOrientation()}
          torch={torch ? "on" : "off"}
          onInitialized={() => {
            setIsInitialized(true);
            setCameraRef(cameraRef)
          }}
              style={
                isInitialized
                  ? {
                      position: "absolute",
                      top: 0,
                      left: 0,
                      right: 0,
                      bottom: 0
                    }
                  : {
                      width: 0,
                      height: 0,
                    }
               }
              device={device}
              isActive={cameraActive}
              photo={true}
              ref={(ref) => cameraRef = ref}

            />

Device

iPhone 14 plus (ios 17.1.1)

VisionCamera Version

"react-native-vision-camera": "^3.9.2"

Can you reproduce this issue in the VisionCamera Example app?

Yes, I can reproduce the same issue in the Example app here

Additional information

maintenance-hans[bot] commented 3 months ago

Guten Tag, Hans here.

[!NOTE] New features, bugfixes, updates and other improvements are all handled mostly by @mrousavy in his free time. To support @mrousavy, please consider 💖 sponsoring him on GitHub 💖. Sponsored issues will be prioritized.

kevinranks commented 2 months ago

@FoundersApproach We're seeing a similar issue on iOS 16+. On iOS <= 15 the green dot showing the camera is active never goes away. Can you try capturing the "goBack" and then using setTimeout to set the camera in-active prior to going back?

Something like this:

const delayedGoBack = useCallback(() => {
     setCameraActive(false)
     setTimeout(() => navigation.goBack(), 10)
}, [])

Note: Would need to disable back gestures in order to make sure navigating back is always captured.

kevinranks commented 2 months ago

https://github.com/mrousavy/react-native-vision-camera/blob/77e98178f84824a0b1c76785469413b64dc96046/package/ios/React/CameraView.swift#L281

Looks like this specific line keeps the phone from going to sleep. It doesn't appear that this is implemented on Android for VC.

jkaufman commented 1 month ago

Perhaps this should just be removed? Idle management could be left to library consumers. This would benefit Expo users, especially, which uses keep-awake to allow stacked calls to hold and release wake lock / idle timer setting.