mrousavy / react-native-vision-camera

πŸ“Έ A powerful, high-performance React Native Camera library.
https://react-native-vision-camera.com
MIT License
7.55k stars 1.1k forks source link

πŸ› Unmounting the Camera component does not turn off the torch #1032

Closed Aurum-Vale closed 1 year ago

Aurum-Vale commented 2 years ago

What were you trying to do?

Hi,

I am currently using vision-camera in a project to read QR Codes (using 'vision-camera-code-scanner' for the FrameProcessor). The project also uses react-navigation.

The user can open a page dedicated to QR Code scanning, which only has the active Camera and a button to toggle the torch. If the torch is kept on and the user goes back (popping the page out of the navigation current stack), the torch stays on.

The only way to turn it off is then to reopen the page, or minimize the whole application.

I did not find any imperative API to use with a ref either.

Reproduceable Code

import React from 'react';

import {
  TouchableOpacity,
} from 'react-native';

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

export default function QrPageExemple(props) {
  const [cameraPermission, setCameraPermission] = React.useState("");
  const [enableFlashlight, setEnableFlashLight] = React.useState(false);
  const devices = useCameraDevices();
  const device = devices.back;

  React.useEffect(() => {
    async function requestPermission() {
      const status = await Camera.requestCameraPermission();
      setCameraPermission(status);
    }

    requestPermission();

    // Cleaning, doesn't work
    return () => {
      setEnableFlashLight(false);
    };
  }, []);

  return (
    <>
      <Camera device={device} torch={enableFlashlight ? "on" : "off"} />
      <TouchableOpacity
        onPress={() => {
          setEnableFlashLight(!enableFlashlight);
        }}
      ></TouchableOpacity>
    </>
  );
}

What happened instead?

The torch stayed on after the Camera component was unmounted. Using a cleaning useEffect() hook to set the torch prop to false has no effect.

Relevant log output

No response

Device

Samsung Galaxy A51 (Android 12), untested on iOS

VisionCamera Version

2.13.2

Additional information

mrousavy commented 2 years ago

Hey! Does this happen on iOS too?

mrousavy commented 2 years ago

I believe this is a reanimated issue. Can you try downgrading to a Reanimated version that doesn't have layout animations yet?

Aurum-Vale commented 2 years ago

Testing on iOS is gonna take some time but I'll get back to you once done.

To downgrade, I'd need to go back to 2.2.0 which fails the build.

Main error (probably because I'm on react-native@0.68.1):

Cannot expand ZIP '//node_modules/react-native-reanimated/android/react-native-reanimated-68-hermes.aar' as it does not exist.

Aurum-Vale commented 2 years ago

On iOS 14.4 (iPhone 7), it works as intended, the torch turns off after about a second when I go back.

Aurum-Vale commented 2 years ago

On a side note, would it be useful to expose the API imperatively for it, like it already exists with focus()?

frankbolviken commented 2 years ago

I got the same issue. Really annoying :) Works on iOS but on Android it stays on.

In addition I cannot update this library behond 2.13.0 because of app not upgraded to later react-native and its a rather big operation right now for a business critical app. Is there any way of manually forcing it off? like using the cameraRef or something like this?

hirbod commented 2 years ago

@mrousavy For once, this error is not due to reanimation :D I tested that, it does indeed not turn the torch off when you unmount it. But I consider this as a weird UI, since you should really not move away from the screen or unmount while recording is running.

frankbolviken commented 2 years ago

I'm not recording tho. I'm scanning barcodes, or taking photos. And whenever the barcode is recognized a new screen is popping up, making the screen with the camera unmount. But the camera is still running / torch is still on :)

hirbod commented 2 years ago

Is your green-light indicator still on as well? Or is it only the torch? (thats important to know). If the former is true, you might really have that specific Reanimated issue.

mrousavy commented 2 years ago

@hirbod

@mrousavy For once, this error is not due to reanimation :D

We don't know that yet πŸ˜‰

@frankbolviken

Can you check if this gets called?

https://github.com/mrousavy/react-native-vision-camera/blob/8f327267d3189f357a46b0e9217c3aa7b9a8d800/android/src/main/java/com/mrousavy/camera/CameraView.kt#L302-L305

Or this func, which is making sure to unmount/mount accoridngly: https://github.com/mrousavy/react-native-vision-camera/blob/8f327267d3189f357a46b0e9217c3aa7b9a8d800/android/src/main/java/com/mrousavy/camera/CameraView.kt#L277-L291

hirbod commented 2 years ago

Yeah, in my case, I know that its not LayoutAnimation, since I removed it fully. But this issue here seems to be Android - might be different.

Aurum-Vale commented 2 years ago

Tested the same codebase on an Android 5.0, I could reproduce the issue as well. I did not notice anything with a greenlight indicator; do you please have a link to that specific Reanimated issue you talked about, so I can dig it and be fully sure it's not related to the bug I'm experiencing?

hirbod commented 2 years ago

@Helicosaurus-Rex https://github.com/software-mansion/react-native-reanimated/issues/3124 But my findings have been iOS only, but there are some similar reports (at least when using react-native-svg) for android. I am not aware of REA + Camera issues with Android though.

MSaxena92 commented 2 years ago

Having a similar issue on android, the torch stays on even after providing a clean-up function in useEffect when unmounting takes place react-native-reanimated@2.8.0 react-native-vision-camera@2.13.3

annasychugina commented 2 years ago

It's because of reanimated. These changes https://github.com/software-mansion/react-native-reanimated/pull/3157 fix this problem for me

react-native-reanimated >= 2.5.0, react-native-vision-camera latest

MSaxena92 commented 2 years ago

Changes in #3157 are specific to iOS, can't find something for android.

annasychugina commented 2 years ago

I had problem only on IOS. Personally I didn't reproduce this problem on android

cjanietz commented 2 years ago

I see this problem on Android as well and also I have by now tried many variants of getting this fixed. What did no help yet:

I will check a little more and add any info I discover

cjanietz commented 2 years ago

I have found the issue: updateLifecycleState is called from onDetachedFromWindow and it assumes the hostLifecycleState is the indicator to update the Camera. However this is only bound to the actual current running host session. Removing the view from the tree will not cause it to be marked as "DESTROYED". If I however force the lifecycle state to be set to DESTROYED from onDetachFromWindow the Camera will be released appropriately and the torch be turned off. I am not sure how this works for anyone correctly right now, but to me it looks like a general issue. @mrousavy I would be happy to open a PR if you agree with me

mrousavy commented 2 years ago

Oh - I see - could you shoot me a PR? @cjanietz

ktarasenkowow commented 2 years ago

I have found the issue: updateLifecycleState is called from onDetachedFromWindow and it assumes the hostLifecycleState is the indicator to update the Camera. However this is only bound to the actual current running host session. Removing the view from the tree will not cause it to be marked as "DESTROYED". If I however force the lifecycle state to be set to DESTROYED from onDetachFromWindow the Camera will be released appropriately and the torch be turned off. I am not sure how this works for anyone correctly right now, but to me it looks like a general issue. @mrousavy I would be happy to open a PR if you agree with me

It was helped me=) Look like (in CameraView.kt):

override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    updateLifecycleState()
    lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
  }
mrousavy commented 2 years ago

But what happens if the view gets re-attached? If we set it to destroyed here, it cannot recover from that. E.g. when minimizing the app and opening it again. Or navigating away with RN Freeze...

ktarasenkowow commented 2 years ago

But what happens if the view gets re-attached? If we set it to destroyed here, it cannot recover from that. E.g. when minimizing the app and opening it again. Or navigating away with RN Freeze...

I checked through navigate-back-navigate screen and it work fine.

xulihang commented 2 years ago

The FrameProcessor is also running after the component is unmounted. Maybe related as well? My project: https://github.com/xulihang/vision-camera-dynamsoft-label-recognizer/tree/main/example

Productivix commented 2 years ago

hi have the issue , on RN 0.70, I call the camera <Camera style={CommonPrxStyles.scanwindow} device={device} isActive={!scanned} frameProcessor={frameProcessor} frameProcessorFps={5} torch={!scanned ? 'on' : 'off'} />

scanned is a state set by a button (typical toggle state on press)
On Android Samsung S9 , the torch sets on at press, as the camera (good) , but when press again the camera goes off (well) but the torch stays on (not expected) , untill I place the app on back . When I resume the app the torch stays off. Hope to be clear. stack : "dependencies": { "react": "18.1.0", "react-native": "0.70.0", "react-native-a-beep": "^1.2.0", "react-native-reanimated": "^2.10.0", "react-native-vision-camera": "^2.14.1", "vision-camera-code-scanner": "^0.2.0" }, Very good project , I remain at disposal for your test /improvments .

hasunpark commented 2 years ago

πŸŽ‰πŸŽ‰Hi Guys, I have workaround that android vision-camera make allow torch control by refπŸŽ‰πŸŽ‰

Allow controlling torch by ref in android

diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt
new file mode 100644
index 0000000..4057c33
--- /dev/null
+++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt
@@ -0,0 +1,9 @@
+package com.mrousavy.camera
+
+import kotlinx.coroutines.guava.await
+
+suspend fun CameraView.enableTorch(status: Boolean){
+    val cameraControl = camera?.cameraControl ?: throw CameraNotReadyError()
+
+    cameraControl.enableTorch(status)
+}
\ No newline at end of file
diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
index 7672b15..e5e1a89 100644
--- a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
+++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
@@ -400,4 +400,15 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
       promise.reject("NO_ACTIVITY", "No PermissionAwareActivity was found! Make sure the app has launched before calling this function.")
     }
   }
+  
+  @ReactMethod
+  fun enableTorch(viewTag: Int,status:Boolean, promise: Promise){
+    coroutineScope.launch{
+      withPromise(promise){
+        val view = findCameraView(viewTag)
+        view.enableTorch(status)
+        return@withPromise null
+      }
+    }
+  }
 }
diff --git a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h
index a2ccdcb..597691c 100644
--- a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h    
+++ b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h    
@@ -53,7 +53,7 @@ objc_name : NSObject<FrameProcessorPluginBase>
 @end                                                                                \
 @implementation objc_name (FrameProcessorPlugin)                                    \
                                                                                     \
-__attribute__((constructor)) static void VISION_CONCAT(initialize_, objc_name)()    \
++(void)load  \
 {                                                                                   \
   [FrameProcessorPluginRegistry addFrameProcessorPlugin:@"__" @ #name callback:^id(Frame* frame, NSArray<id>* args) {    \
     return [objc_name callback:frame withArgs:args];                               \
diff --git a/node_modules/react-native-vision-camera/src/Camera.tsx b/node_modules/react-native-vision-camera/src/Camera.tsx
index 68417ac..fbfeb64 100644
--- a/node_modules/react-native-vision-camera/src/Camera.tsx
+++ b/node_modules/react-native-vision-camera/src/Camera.tsx
@@ -293,6 +293,19 @@ export class Camera extends React.PureComponent<CameraProps> {
       throw tryParseNativeCameraError(e);
     }
   }
+
+  /**
+   * Control the cameras torch by given status.
+   * @param {Boolean} status
+   */
+  public async enableTorch(status: Boolean): Promise<void> {
+    try{
+      return await CameraModule.enableTorch(this.handle,status)
+    } catch (e){
+      throw tryParseNativeCameraError(e)
+    }
+  }
+
   //#endregion

   /**

Usage

   useEffect(() => {
        const listener = BackHandler.addEventListener('hardwareBackPress', function () {
            ref.current.enableTorch(false);
            return false;
        });
        return () => {
            listener.remove();
        };
    }, []);

you can get react-native-vision-camera+2.15.1.patch file at the link

how to use .patch file patch-package

Working On

After finish these jobs i will make a PR, until now you can fix your bug with this patch!! See ya πŸŽ‰

mrousavy commented 2 years ago

Nice work! Seems good to me.

I will work on a fix soon

hasunpark commented 2 years ago

Nice work! Seems good to me.

I will work on a fix soon

Thank you always for your dedication. Hope i can help you!

Shoaib-ali7860 commented 1 year ago

we can also stop recording before unmounting the screen
React.useEffect(() => { permission();

return navigation.addListener('beforeRemove', async (e: any) => {
    await stopRecording()

  });

}, []);

Productivix commented 1 year ago

yes indeed it works easy ! and in pure js, without ref, it is:

return ()=>
    navigation.addListener('beforeRemove', async (e) => {
      if (torch)  setTorch('off')
    });

  },[navigation,torch])

with only : <Camera ref={cameraRef} style={CommonPrxStyles.scanSmallWindow} device={device} isActive={true} // isActive={!scanned} torch={torch} photo={true} />

enzzoperez commented 1 year ago

πŸŽ‰πŸŽ‰Hi Guys, I have workaround that android vision-camera make allow torch control by refπŸŽ‰πŸŽ‰

Allow controlling torch by ref in android

diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt
new file mode 100644
index 0000000..4057c33
--- /dev/null
+++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraView+EnableTorch.kt
@@ -0,0 +1,9 @@
+package com.mrousavy.camera
+
+import kotlinx.coroutines.guava.await
+
+suspend fun CameraView.enableTorch(status: Boolean){
+    val cameraControl = camera?.cameraControl ?: throw CameraNotReadyError()
+
+    cameraControl.enableTorch(status)
+}
\ No newline at end of file
diff --git a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
index 7672b15..e5e1a89 100644
--- a/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
+++ b/node_modules/react-native-vision-camera/android/src/main/java/com/mrousavy/camera/CameraViewModule.kt
@@ -400,4 +400,15 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
       promise.reject("NO_ACTIVITY", "No PermissionAwareActivity was found! Make sure the app has launched before calling this function.")
     }
   }
+  
+  @ReactMethod
+  fun enableTorch(viewTag: Int,status:Boolean, promise: Promise){
+    coroutineScope.launch{
+      withPromise(promise){
+        val view = findCameraView(viewTag)
+        view.enableTorch(status)
+        return@withPromise null
+      }
+    }
+  }
 }
diff --git a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h
index a2ccdcb..597691c 100644
--- a/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h  
+++ b/node_modules/react-native-vision-camera/ios/Frame Processor/FrameProcessorPlugin.h  
@@ -53,7 +53,7 @@ objc_name : NSObject<FrameProcessorPluginBase>
 @end                                                                                \
 @implementation objc_name (FrameProcessorPlugin)                                    \
                                                                                     \
-__attribute__((constructor)) static void VISION_CONCAT(initialize_, objc_name)()    \
++(void)load  \
 {                                                                                   \
   [FrameProcessorPluginRegistry addFrameProcessorPlugin:@"__" @ #name callback:^id(Frame* frame, NSArray<id>* args) {    \
     return [objc_name callback:frame withArgs:args];                               \
diff --git a/node_modules/react-native-vision-camera/src/Camera.tsx b/node_modules/react-native-vision-camera/src/Camera.tsx
index 68417ac..fbfeb64 100644
--- a/node_modules/react-native-vision-camera/src/Camera.tsx
+++ b/node_modules/react-native-vision-camera/src/Camera.tsx
@@ -293,6 +293,19 @@ export class Camera extends React.PureComponent<CameraProps> {
       throw tryParseNativeCameraError(e);
     }
   }
+
+  /**
+   * Control the cameras torch by given status.
+   * @param {Boolean} status
+   */
+  public async enableTorch(status: Boolean): Promise<void> {
+    try{
+      return await CameraModule.enableTorch(this.handle,status)
+    } catch (e){
+      throw tryParseNativeCameraError(e)
+    }
+  }
+
   //#endregion

   /**

Usage

   useEffect(() => {
        const listener = BackHandler.addEventListener('hardwareBackPress', function () {
            ref.current.enableTorch(false);
            return false;
        });
        return () => {
            listener.remove();
        };
    }, []);

you can get react-native-vision-camera+2.15.1.patch file at the link

how to use .patch file patch-package

Working On

  • typescript method auto complete
  • supporting ios

After finish these jobs i will make a PR, until now you can fix your bug with this patch!! See ya πŸŽ‰

Thank so much, it worked for me, but first I had to patch the patch-package πŸ˜„ because I had a buffer bug https://github.com/ds300/patch-package/issues/166

I used this patch https://gist.github.com/brussee/e382ed12ca007a88170289e54b526063 but with maxBuffer: 1024 * 1024 * 1000,

hasunpark commented 1 year ago

it's glad helped you πŸ˜„, and thanks for patch-package bug info

markoprodanovic commented 1 year ago

Same issue with react-native app running on Samsung Galaxy A12

Unfortunately the patch isn't working for me. Even after applying still seeing: cameraRef.current.enableTorch(false) // enableTorch is unknown

Tried applying the patch several times and even deleting node_modules and reinstalling from scratch.

hasunpark commented 1 year ago

@markoprodanovic can you give some example that can reproduce the issue?

markoprodanovic commented 1 year ago

@whtjs it was a mistake on my end and a misunderstanding of how to use patch-package. For context, I was overwriting the patch file by running my own yarn patch-package react-native-vision-camera. I was also working with v0.5.2 instead of v0.5.1. Once I sorted out those things it worked great! Thank you :)

Would be great to get this in an official release

hasunpark commented 1 year ago

@markoprodanovic i will notice you by a comment that you can get rid of patch files :)

mrousavy commented 1 year ago

Hey! I've rewritten the entire Android codebase of VisionCamera from CameraX to Camera2 in the efforts of ✨ VisionCamera V3.

I just now completed the Camera2 rewrite and I believe the core structure is running, but there might be some edge cases to iron out. Can you try and test the PR #1674 for me to see if you can still reproduce this issue here?

Here's an instruction on how you can test that: https://github.com/mrousavy/react-native-vision-camera/pull/1674#issuecomment-1684104217

If the issue cannot be reproduced with that version/PR anymore, then hoorayy, I fixed it! πŸŽ‰ Otherwise please let me know and I'll keep this issue open to keep track of it.

Thank you!

gbalduzzi commented 1 year ago

I'm experiencing the same problem on v3 on iOS

mrousavy commented 12 months ago

Hey! I just found out that I really forgot to close and dispose the locked Camera resources, so I just fixed that in this PR: https://github.com/mrousavy/react-native-vision-camera/pull/2174 Now the Camera fully & synchronously closes all resources (CameraSession, CameraDevice, OpenGL context, Video & Photo outputs, Photo Synchronizer) once the view gets removed from the React view hierarchy ("unmounted"), and things like Flash, Torch, NFC, and other Camera components should be working again.

There is still a small issue that causes once Camera component to turn into a blackscreen when navigating back and forth between two Camera components, that's a pretty rare edge case but I will still try to fix that soon when I have some free time.

If you appreciate my time, expertise and dedication to this project, pleas πŸ’– consider sponsoring me on GitHub πŸ’– to support the development of this project.