Closed Cavallando closed 8 months ago
Was just trying some different things out and I was able to expose a stopSession
function in CameraViewManager.swift
to stop the capture session and this appropriately stops the activity:
@objc
final func stopSession(_ node: NSNumber) {
let component = getCameraView(withTag: node)
component.captureSession.stopRunning()
}
This doesn't solve the issue really but just wanted to give some more info.
Hey! Hmm, interesting.
Are you maybe using React Freeze? Try doing enableFreeze(false)
and see if that (temporarily) fixes the issue
Hey @mrousavy thanks for getting back to me! We are not currently using react freeze in our project. To expand on the changes I made above, it would only work when I changed override var bridge: RCTBridge!
in CameraViewManager to override weak var bridge: RCTBridge!
EDIT:
For your iOS implementation, would using a weak var
for the RCTBridge allow you to call captureSession.stopRunning()
in the deinit
of CameraView? Right now, it appears none of the deinit's are called when the component unmounts. This is probably me misunderstanding how the react native bridge works, but was thinking that without the weak initialization the bridge is strong so the deinit's never get called, apologies if this is irrelevant, just trying to understand things more!
I am using enableFreeze and I can confirm that the green light indicator will still be on for me occasionally. Most of the times, it will turn off, but sometimes it wont. The consumed memory will reduce but never return back to the value before the camera started as well. Maybe its somehow related
UIKit works a bit differently, deinit
is not a safe way to stop the camera session here because the view is never actually de-allocated. Just because you don't see it on-screen anymore does not mean the view is gone, it's still in the navigation hierarchy. It's just "moved away" (aka it does not have a parent window nor a parent view), maybe didMoveToWindow
helps.
Same here on the latest version. We're not using react-freeze
on our project. Component unmounts but the camera stays active.
Android Device
2.13.0
Dug in a bit and to help clarify, react-native-screens built react freeze and use it under the hood, but still they specify that it is opt in and one would need to call enableFreeze(true)
exported from the library. I can try this but at first read of their docs on it it feels unrelated.
@mrousavy thanks for the feed back on deinit
, that does make a ton of sense. that does make me think now that it could possibly relate to react-native-screens/react-navigation and Native Stack which we are using. Ill dig further and also take a look at didMoveToWindow
within vision camera development pod and see if I can find anything and report back!
I have some more insights here. I am pretty sure that the main issue is coming from react native screens (but not enableFreeze).
I have other cases, where popped and unmounted Videos keep playing in the background even when the lifecycle methods report they have been unmounted. RNS is somehow always keeping the last view in the "system". And I think that is exactly what is happening here with the Camera. This is also explaining why memory is never freed again fully after the camera or a video was loaded.
I think what we all have in common is that we are using enableScreens or native stack. Only the js-Stack with enableScreens disabled (extremely bad performance) is not having this issues.
It looks like the detach mechanism is doing something bad.
I wish I had the knowledge to find the issue, but I think that's where the problem is coming from.
I found plenty of issues on the react-native-video repo and I think this is all connected to the same root problem (I am using expo-av though).
The problem only happens with nested navigators. Screens inside a tab stack for example do not have this issue, but a modal or "overlayed" navigator (hierarchy above tabs) does introduce this issue.
@Cavallando are you using, by any chance, LayoutAnimation
by react-native-reanimated or a library, which uses LayoutAnimation?
I found the root cause - at least for me. https://github.com/software-mansion/react-native-reanimated/issues/3124
It was enough for my App to have a single mounted element which has LayoutAnimation
on it to break expo-av and react native vision cameras unmounting.
This worked for me (using beforeRemove
@react-navigation/native event listener):
const [isActive, setActive] = useState(true);
beforeRemove
event useEffect(() => {
const unsubscribe = navigation.addListener('beforeRemove', () => {
setActive(false);
});
return unsubscribe;
}, [navigation]);
<Camera
...
isActive={isActive}
/>
Hope this helps to you as well!!
any proper solution here!
I'm using version 2.14.1 and also have this issue. Looking at logcat, it seems as if the camera is running the whole time, although the screen is not visible. To my surprise, tapping the hardware button "Recent Apps" twice closes the camera properly. I attached the Log on pastebin.
It would be nice if you could expose a ReactMethod to close the camera on Android side.
Log excerpt:
09-08 16:45:30.872 27667 28850 D CameraStateRegistry: tryOpenCamera(Camera@e49c13a[id=0]) [Available Cameras: 1, Already Open: false (Previous state: CLOSED)] --> SUCCESS
09-08 16:45:30.872 27667 27667 I CameraView: Attaching 1 use-cases...
09-08 16:45:30.873 27667 28850 D CameraStateRegistry: Recalculating open cameras:
09-08 16:45:30.873 27667 28850 D CameraStateRegistry: Camera State
09-08 16:45:30.873 27667 28850 D CameraStateRegistry: -------------------------------------------------------------------
09-08 16:45:30.873 27667 28850 D CameraStateRegistry: Camera@e49c13a[id=0] OPENING
09-08 16:45:30.873 27667 28850 D CameraStateRegistry: Camera@ec5f71d[id=1] UNKNOWN
09-08 16:45:30.873 27667 28850 D CameraStateRegistry: -------------------------------------------------------------------
09-08 16:45:30.873 27667 28850 D CameraStateRegistry: Open count: 1 (Max allowed: 1)
09-08 16:45:30.873 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} Opening camera.
09-08 16:45:30.873 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} Transitioning camera internal state: INITIALIZED --> OPENING
09-08 16:45:30.874 27667 28850 D CameraStateMachine: New public camera state CameraState{type=OPENING, error=null} from OPENING and null
09-08 16:45:30.874 27667 28850 D CameraStateMachine: Publishing new public camera state CameraState{type=OPENING, error=null}
09-08 16:45:30.974 2151 3663 I CameraHardwareInterface: Opening camera 0
09-08 16:45:30.974 548 2538 I CamDev@1.0-impl: Opening camera 0
09-08 16:45:30.974 548 2538 I QCamera : <HAL><INFO> getCameraInfo: 343: Camera id 0 API version 256
09-08 16:45:30.974 548 2538 I QCamera : <HAL><INFO> getCamInfo: 8900: camera 0 resource cost is 100
09-08 16:45:30.974 548 2538 I QCamera : <HAL><INFO> cameraDeviceOpen: 408: Open camera id 0 API version 256
09-08 16:45:30.967 27667 27667 W Binder:27667_7: type=1400 audit(0.0:8644): avc: denied { read } for name="u:object_r:vendor_camera_prop:s0" dev="tmpfs" ino=14720 scontext=u:r:untrusted_app_25:s0:c512,c768 tcontext=u:object_r:vendor_camera_prop:s0 tclass=file permissive=0
09-08 16:45:30.976 27667 31163 E libc : Access denied finding property "vendor.camera.aux.packagelist"
09-08 16:45:30.976 27667 31163 E libc : Access denied finding property "vendor.camera.aux.packagelist2"
09-08 16:45:30.967 27667 27667 W Binder:27667_7: type=1400 audit(0.0:8645): avc: denied { read } for name="u:object_r:vendor_camera_prop:s0" dev="tmpfs" ino=14720 scontext=u:r:untrusted_app_25:s0:c512,c768 tcontext=u:object_r:vendor_camera_prop:s0 tclass=file permissive=0
09-08 16:45:31.129 548 2538 I QCamera : <HAL><INFO> openCamera: 1864: [KPI Perf]: E PROFILE_OPEN_CAMERA camera id 0
09-08 16:45:31.131 2151 3663 I CameraProviderManager: Camera device device@1.0/legacy/0 torch status is now NOT_AVAILABLE
09-08 16:45:31.131 2151 3663 I CameraService: onTorchStatusChangedLocked: Torch status changed for cameraId=0, newStatus=0
09-08 16:45:31.132 2217 2217 I mm-camera: < INFO> 395: enable_memleak_trace: start memleak tracking.
09-08 16:45:31.218 2217 2217 I mm-camera: <MCT >< INFO> 63: mct_controller_new: Creating new mct_controller with session-id 1
09-08 16:45:31.218 2217 31816 I mm-camera: <MCT >< INFO> 4684: mct_pipeline_start_session_thread: E sensor
09-08 16:45:31.218 2217 31816 I mm-camera: <MCT >< INFO> 4691: mct_pipeline_start_session_thread: Calling start_session on Module sensor
09-08 16:45:31.218 2217 31817 I mm-camera: <MCT >< INFO> 4684: mct_pipeline_start_session_thread: E iface
09-08 16:45:31.218 2217 31817 I mm-camera: <MCT >< INFO> 4691: mct_pipeline_start_session_thread: Calling start_session on Module iface
09-08 16:45:31.219 2217 31819 I mm-camera: <MCT >< INFO> 4684: mct_pipeline_start_session_thread: E isp
09-08 16:45:31.219 2217 31819 I mm-camera: <MCT >< INFO> 4691: mct_pipeline_start_session_thread: Calling start_session on Module isp
09-08 16:45:31.219 2217 31819 I mm-camera: <ISP >< INFO> 205: isp_module_start_session: session id 1
09-08 16:45:31.334 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} Transitioning camera internal state: REOPENING --> OPENED
09-08 16:45:31.334 27667 28850 D CameraStateRegistry: Recalculating open cameras:
09-08 16:45:31.334 27667 28850 D CameraStateRegistry: Camera State
09-08 16:45:31.334 27667 28850 D CameraStateRegistry: -------------------------------------------------------------------
09-08 16:45:31.334 27667 28850 D CameraStateRegistry: Camera@e49c13a[id=0] OPEN
09-08 16:45:31.334 27667 28850 D CameraStateRegistry: Camera@ec5f71d[id=1] UNKNOWN
09-08 16:45:31.334 27667 28850 D CameraStateRegistry: -------------------------------------------------------------------
09-08 16:45:31.334 27667 28850 D CameraStateRegistry: Open count: 1 (Max allowed: 1)
09-08 16:45:31.335 27667 28850 D CameraStateMachine: New public camera state CameraState{type=OPEN, error=null} from OPEN and null
09-08 16:45:31.335 27667 28850 D CameraStateMachine: Publishing new public camera state CameraState{type=OPEN, error=null}
09-08 16:45:31.335 27667 28850 D UseCaseAttachState: All use case: [androidx.camera.core.ImageAnalysis-0e3eeab4-1fb3-4dd2-9e2b-39b65fc2da1d84982627, androidx.camera.core.Preview-ce0a7cbb-6321-46da-b928-2483986dc59c211842450] for camera: 0
09-08 16:45:31.337 27667 28850 D UseCaseAttachState: Active and attached use case: [androidx.camera.core.ImageAnalysis-0e3eeab4-1fb3-4dd2-9e2b-39b65fc2da1d84982627, androidx.camera.core.Preview-ce0a7cbb-6321-46da-b928-2483986dc59c211842450] for camera: 0
09-08 16:45:31.493 548 31813 I QCamera : <HAL><INFO> startPreview: 3909: X rc = 0
09-08 16:45:31.493 548 3003 I QCamera : <HAL><INFO> start_preview: 388: [KPI Perf]: X ret = 0
09-08 16:45:31.496 2217 31837 I mm-camera: <MCT >< INFO> 3850: mct_pipeline_process_set: command=8000016
09-08 16:45:31.496 2217 31837 I mm-camera: <MCT >< INFO> 4203: mct_pipeline_process_set: store param event: 0xa
09-08 16:45:31.524 2217 31855 I mm-camera: <ISP >< INFO> 1260: isp_handler_module_handle_reg_update: Warning! Invalid reg_update state 0
09-08 16:45:31.526 2217 31839 I mm-camera: <MCT >< INFO> 1021: mct_controller_handle_SOF_proc: (sofdelay, curr_sofdelay) = (0, 0) kptr 1
9-08 16:45:36.789 2217 31839 I mm-camera: <MCT >< INFO> 1021: mct_controller_handle_SOF_proc: (sofdelay, curr_sofdelay) = (0, 0) kptr 79
09-08 16:45:36.836 27667 27667 D CameraView: Lifecycle went from RESUMED -> CREATED (isActive: true | isAttachedToWindow: false)
09-08 16:45:36.837 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} Use cases [androidx.camera.core.Preview-ce0a7cbb-6321-46da-b928-2483986dc59c211842450, androidx.camera.core.ImageAnalysis-0e3eeab4-1fb3-4dd2-9e2b-39b65fc2da1d84982627] now DETACHED for camera
09-08 16:45:36.837 27667 28850 D UseCaseAttachState: All use case: [] for camera: 0
09-08 16:45:36.839 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} Resetting Capture Session
09-08 16:45:36.840 27667 28850 D DeferrableSurface: surface closed, useCount=1 closed=true androidx.camera.core.SurfaceRequest$2@7c9aabf
09-08 16:45:36.840 27667 28850 D DeferrableSurface: surface closed, useCount=1 closed=true androidx.camera.core.impl.ImmediateSurface@14124ea
09-08 16:45:36.840 27667 28850 D SyncCaptureSessionImpl: [androidx.camera.camera2.internal.SynchronizedCaptureSessionImpl@2a93716] deferrableSurface closed
09-08 16:45:36.840 27667 28850 D SyncCaptureSessionImpl: [androidx.camera.camera2.internal.SynchronizedCaptureSessionImpl@2a93716] Session call close()
09-08 16:45:36.840 27667 28850 D SyncCaptureSessionImpl: [androidx.camera.camera2.internal.SynchronizedCaptureSessionImpl@2a93716] deferrableSurface closed
09-08 16:45:36.841 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} Releasing session in state OPENED
09-08 16:45:36.841 27667 28850 D UseCaseAttachState: Active and attached use case: [] for camera: 0
09-08 16:45:36.842 27667 28850 D UseCaseAttachState: Active and attached use case: [] for camera: 0
09-08 16:45:36.844 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} Closing camera.
09-08 16:45:36.844 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} Transitioning camera internal state: OPENED --> CLOSING
09-08 16:45:36.845 27667 28850 D CameraStateRegistry: Recalculating open cameras:
09-08 16:45:36.845 27667 28850 D CameraStateRegistry: Camera State
09-08 16:45:36.845 27667 28850 D CameraStateRegistry: -------------------------------------------------------------------
09-08 16:45:36.845 27667 28850 D CameraStateRegistry: Camera@e49c13a[id=0] CLOSING
09-08 16:45:36.845 27667 28850 D CameraStateRegistry: Camera@ec5f71d[id=1] UNKNOWN
09-08 16:45:36.845 27667 28850 D CameraStateRegistry: -------------------------------------------------------------------
09-08 16:45:36.845 27667 28850 D CameraStateRegistry: Open count: 1 (Max allowed: 1)
09-08 16:45:37.154 27667 28850 D SyncCaptureSessionImpl: [androidx.camera.camera2.internal.SynchronizedCaptureSessionImpl@924ae2] deferrableSurface closed
09-08 16:45:37.154 27667 28850 D SyncCaptureSessionImpl: [androidx.camera.camera2.internal.SynchronizedCaptureSessionImpl@924ae2] onClosed()
09-08 16:45:37.154 27667 28850 D DeferrableSurface: use count-1, useCount=0 closed=true androidx.camera.core.impl.ImmediateSurface@d209cc4
09-08 16:45:37.154 27667 28850 D DeferrableSurface: Surface no longer in use[total_surfaces=1, used_surfaces=0](androidx.camera.core.impl.ImmediateSurface@d209cc4}
09-08 16:45:37.154 27667 28850 D DeferrableSurface: Surface terminated[total_surfaces=0, used_surfaces=0](androidx.camera.core.impl.ImmediateSurface@d209cc4}
09-08 16:45:37.156 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} CameraDevice.onClosed()
09-08 16:45:37.157 27667 28850 D Camera2CameraImpl: {Camera@e49c13a[id=0]} Transitioning camera internal state: CLOSING --> INITIALIZED
09-08 16:45:37.159 27667 28850 D CameraStateRegistry: Recalculating open cameras:
09-08 16:45:37.159 27667 28850 D CameraStateRegistry: Camera State
09-08 16:45:37.159 27667 28850 D CameraStateRegistry: -------------------------------------------------------------------
09-08 16:45:37.159 27667 28850 D CameraStateRegistry: Camera@e49c13a[id=0] CLOSED
09-08 16:45:37.159 27667 28850 D CameraStateRegistry: Camera@ec5f71d[id=1] UNKNOWN
09-08 16:45:37.159 27667 28850 D CameraStateRegistry: -------------------------------------------------------------------
09-08 16:45:37.159 27667 28850 D CameraStateRegistry: Open count: 0 (Max allowed: 1)
I solved this issue with the help of the suggestions in this thread, namely moving the camera component from a modal dialog into its own activity that can be navigated to and adding a 'beforeRemove' listener to @react-navigation/native:
import {useNavigation} from "@react-navigation/native";
const navigation = useNavigation();
useEffect(() => {
navigation.addListener("beforeRemove", () => {
setIsActive(false)
});
return () => navigation.removeListener("beforeRemove", () => {
setIsActive(false)
});
}, [navigation]);
I think it would be helpful for a workaround if you could disable the camera using the ref.
Hi, i am with the same error, any solutions?
facing same error, tried @BdN3504 solution but didn't work.
If you want off camera on unmounted component (blurred screen in react navigation) you need set isActive to false before unmount (navigating back) you component, very important wait for one render before unmount, so you need define close handler like this
const closeCamera = useCallback(() => { setActive(false) setTimeout(() => { navigation.goBack(); }, 10); }, [navigation]);
.
Also I set gestureEnabled={false}
in Stack.Screen, I replaced android back button function useEffect(() => { const backHandler = BackHandler.addEventListener( 'hardwareBackPress', closeCamera, ); return () => backHandler.remove(); }, [closeCamera]);
and added close button with this handler.
All frameProcessor are shut down after that
I'll investigate this more in the V3 efforts so that you don't need such a workaround and it unmounts smoothly. :)
Using a tiny timeout after setting isActive
to false
before navigating away from the camera view fixed the issue in our environment, thanks for the tip @andreyboberskiy
Thanks @andreyboberskiy - that helped in most cases for me.
It doesn't seem to work when an error is thrown in the component that has the camera though, i've tried moving the camera component up the tree into an error boundary component but it still doesn't want to close.
@mrousavy is there any hacky way of killing all camera instances without leaving the app?
I ended up having to set the fallback to a component that renders a new camera component and then kill that one the same way as @andreyboberskiy described above - not a great solution but it will do for now
hey! I'm hoping that V3 solves this, if the lifecycle gets killed, so should the Camera. React Freeze is always a culprit for me.
I just simply did this thing
const focused = useIsFocused()
<Camera ... isActive={focused} ... />
I just simply did this thing
const focused = useIsFocused()
<Camera ... isActive={focused} ... />
I confirm, this the clean solution to have fresh Camera state on each focus/blur event.
This is now fixed in V3! 🥳
This is now fixed in V3! 🥳
iOS green dot is still active on react navigation page back even with using useIsFocused (useIsFocused is not changes on page back).
react-native-vision-camera: 3.6.4
That's weird - I am pretty sure I tested that... I'll try again later
I'm having a similar problem, I cant get the camera to unmount. None of the above fixes unmounts the camera for me.
Downgrading to 3.2.2 fixes the issue for me with the above fixes.
react-native-vision-camera: 3.6.4
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.
Is this fix (https://github.com/mrousavy/react-native-vision-camera/pull/2174) only for Android? What about iOS?
I don't think the issue exists on iOS at the moment. At least for me, the Camera closes when I minimize the app.
Finally, because @react-navigation/native-stack
can't get the blur event when page is going back, I use this custom hook to get page focus state by transitionEnd
event.
import { useEffect, useState } from 'react';
import { useNavigation } from '@react-navigation/native';
const useIsFocused = () => {
const navigation = useNavigation();
const [isFocused, setIsFocused] = useState(false);
useEffect(() => {
return navigation.addListener('transitionEnd', () => {
setIsFocused(state => !state);
});
}, [navigation]);
return isFocused;
};
export default useIsFocused;
I don't know how the camera works under the hood, but it seems like all camera instances are keep existing on page unmount. https://github.com/software-mansion/react-native-reanimated/issues/3124
My project version is "react-native": "0.72.6" , "react-native-vision-camera": "^3.6.17" ,
I also encountered the same problem. Is there a solution available? After uninstalling the camera in Android, it still remains active. I have set the isActive={false}
attribute。
const device = useCameraDevice('back')
const [isActive, setIsActive] = useState(false);
const isFocused = useIsFocused()
const appState = useAppState()
useEffect(() => {
if (device) {
// Delaying the camera by one second to solve the black screen problem
const timeout = setTimeout(() => {
setIsActive(
isFocused && appState === "active")
}, 1000)
return () => clearTimeout(timeout)
}
}, [isFocused, appState])
<Camera
ref={camera}
style={[StyleSheet.absoluteFill, { backgroundColor: 'black', }]}
resizeMode='cover'
isActive={isActive}
device={device}
codeScanner={codeScanner}
torch={isLight ? 'on' : 'off'}
/>
What were you trying to do?
We noticed that the Camera is still streaming frames even after the component is unmounted. This is made apparent by the green dot indicator in iOS. This appears to happen after we capture a video, take a snapshot or navigate away from the screen entirely.
I noticed an existing issue and docs where it was advised to set
isActive
to false which wouldn't work for us since we're using React navigation modals so the screen becomes unmounted.I confirmed the screen is actually getting unmounted through a useEffect cleanup function on our Camera component.
I also noticed from Vision Camera's API docs for
isActive
that unmounting should destroy the camera.Reproduceable Code
No response
What happened instead?
The camera never appears to stop using the devices camera unless we put the app in the background. After closing the modal and confirming it is no longer mounted, the green dot indicator still appears.
From the logs I see for Vision camera, it does not appear that
self.captureSession.stopRunning()
ever gets called because when the screen unmounts I do not see the logs:Relevant log output
Device
iOS Devices
VisionCamera Version
2.11.2, 2.12.2
Additional information