From f9739fda3214419806989d8884b323a5addfb1d9 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 12 Aug 2026 20:30:00 +0200 Subject: [PATCH 1/2] fix: emit current orientation on subscribe --- .../orientation/HybridDeviceOrientationManager.kt | 6 +++++- .../orientation/HybridInterfaceOrientationManager.kt | 11 ++++++++--- .../Orientation/HybridDeviceOrientationManager.swift | 3 +++ .../HybridInterfaceOrientationManager.swift | 8 ++++++++ .../src/hooks/useOrientation.ts | 1 + 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridDeviceOrientationManager.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridDeviceOrientationManager.kt index 085cb0175a..40d9a40c88 100644 --- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridDeviceOrientationManager.kt +++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridDeviceOrientationManager.kt @@ -32,9 +32,11 @@ class HybridDeviceOrientationManager : HybridOrientationManagerSpec() { override fun startOrientationUpdates(onChanged: (orientation: CameraOrientation) -> Unit) { orientationListener?.disable() + currentOrientation?.let(onChanged) orientationListener = object : OrientationEventListener(context) { override fun onOrientationChanged(rotationDegrees: Int) { + if (orientationListener !== this) return if (rotationDegrees == ORIENTATION_UNKNOWN) { // phone is laying flat - orientation is unknown! Avoid sending out event. return @@ -51,6 +53,8 @@ class HybridDeviceOrientationManager : HybridOrientationManagerSpec() { } override fun stopOrientationUpdates() { - orientationListener?.disable() + val listener = orientationListener + orientationListener = null + listener?.disable() } } diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridInterfaceOrientationManager.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridInterfaceOrientationManager.kt index b2a9339616..576e9ba6a4 100644 --- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridInterfaceOrientationManager.kt +++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridInterfaceOrientationManager.kt @@ -32,6 +32,11 @@ class HybridInterfaceOrientationManager : HybridOrientationManagerSpec() { listener?.let { listener -> displayManager.unregisterDisplayListener(listener) } + val defaultDisplay = displayManager.displays.firstOrNull() + if (defaultDisplay != null) { + currentOrientation = CameraOrientation.fromSurfaceRotation(defaultDisplay.rotation) + } + currentOrientation?.let(onChanged) val listener = object : DisplayManager.DisplayListener { override fun onDisplayAdded(displayId: Int) = Unit @@ -39,6 +44,7 @@ class HybridInterfaceOrientationManager : HybridOrientationManagerSpec() { override fun onDisplayRemoved(displayId: Int) = Unit override fun onDisplayChanged(displayId: Int) { + if (this@HybridInterfaceOrientationManager.listener !== this) return val display = displayManager.getDisplay(displayId) ?: return val surfaceRotation = display.rotation val orientation = CameraOrientation.fromSurfaceRotation(surfaceRotation) @@ -54,9 +60,8 @@ class HybridInterfaceOrientationManager : HybridOrientationManagerSpec() { } override fun stopOrientationUpdates() { - listener?.let { listener -> - displayManager.unregisterDisplayListener(listener) - } + val currentListener = listener listener = null + currentListener?.let(displayManager::unregisterDisplayListener) } } diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridDeviceOrientationManager.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridDeviceOrientationManager.swift index 48396bd14a..2759093fd8 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridDeviceOrientationManager.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridDeviceOrientationManager.swift @@ -29,6 +29,9 @@ final class HybridDeviceOrientationManager: HybridOrientationManagerSpec { if motionManager.isAccelerometerActive { motionManager.stopAccelerometerUpdates() } + if let currentOrientation { + onChanged(currentOrientation) + } if motionManager.isAccelerometerAvailable { motionManager.startAccelerometerUpdates(to: operationQueue) { diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift index 4a37053bb6..29b10175c7 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift @@ -34,6 +34,13 @@ final class HybridInterfaceOrientationManager: HybridOrientationManagerSpec { // Start new listener (beginGeneratingDeviceOrientationNotifications() can be nested) UIDevice.current.beginGeneratingDeviceOrientationNotifications() + let interfaceOrientation = UIApplication.shared.interfaceOrientation + if interfaceOrientation != .unknown { + let orientation = CameraOrientation(interfaceOrientation: interfaceOrientation) + self.currentOrientation = orientation + onChanged(orientation) + } + self.observer = NotificationCenter.default.addObserver( forName: UIDevice.orientationDidChangeNotification, object: nil, @@ -60,6 +67,7 @@ final class HybridInterfaceOrientationManager: HybridOrientationManagerSpec { if let observer = self.observer { logger.info("Stopping interface orientation updates...") NotificationCenter.default.removeObserver(observer) + self.observer = nil UIDevice.current.endGeneratingDeviceOrientationNotifications() } } diff --git a/packages/react-native-vision-camera/src/hooks/useOrientation.ts b/packages/react-native-vision-camera/src/hooks/useOrientation.ts index 44a5614bbd..396a869ea0 100644 --- a/packages/react-native-vision-camera/src/hooks/useOrientation.ts +++ b/packages/react-native-vision-camera/src/hooks/useOrientation.ts @@ -20,6 +20,7 @@ export function useOrientation( ): CameraOrientation | undefined { const orientationManager = useOrientationManager(source) const currentOrientation = useRef(orientationManager?.currentOrientation) + currentOrientation.current = orientationManager?.currentOrientation const subscribe = useCallback( (onStoreChange: () => void) => { From ba7035e36aa4085e2d379cb0651c6bc63432ce63 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 12 Aug 2026 20:37:50 +0200 Subject: [PATCH 2/2] fix: reject invalid orientation values --- .../extensions/CameraOrientation+degrees.kt | 23 ++++++------------- .../extensions/ImageProxy+orientation.kt | 1 + .../HybridDeviceOrientationManager.kt | 2 +- .../Converters/UI+CameraOrientation.swift | 4 ++-- .../HybridInterfaceOrientationManager.swift | 6 ++--- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/CameraOrientation+degrees.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/CameraOrientation+degrees.kt index 92117cc4b1..b7b7d64dff 100644 --- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/CameraOrientation+degrees.kt +++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/CameraOrientation+degrees.kt @@ -12,23 +12,14 @@ val CameraOrientation.degrees: Int } } -fun CameraOrientation.Companion.fromDegrees(degrees: Int): CameraOrientation { - val normalizedDegrees = normalizeDegrees(degrees) - return when (normalizedDegrees) { - in 45..135 -> CameraOrientation.LEFT - in 135..225 -> CameraOrientation.DOWN - in 225..315 -> CameraOrientation.RIGHT - else -> CameraOrientation.UP +fun CameraOrientation.Companion.fromDegrees(degrees: Int): CameraOrientation? = + when (degrees) { + in 0 until 45, in 315 until 360 -> CameraOrientation.UP + in 45 until 135 -> CameraOrientation.LEFT + in 135 until 225 -> CameraOrientation.DOWN + in 225 until 315 -> CameraOrientation.RIGHT + else -> null } -} - -private fun CameraOrientation.Companion.normalizeDegrees(degrees: Int): Int { - val normalized = degrees % 360 - if (normalized < 0) { - return normalized + 360 - } - return normalized -} /** * Returns the logical counter-orientation diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/ImageProxy+orientation.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/ImageProxy+orientation.kt index 1d23d56063..993326b662 100644 --- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/ImageProxy+orientation.kt +++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/ImageProxy+orientation.kt @@ -7,4 +7,5 @@ val ImageProxy.orientation: CameraOrientation get() { val degrees = imageInfo.rotationDegrees return CameraOrientation.fromDegrees(degrees) + ?: throw Error("Invalid ImageProxy rotation degrees: $degrees") } diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridDeviceOrientationManager.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridDeviceOrientationManager.kt index 40d9a40c88..49fca360be 100644 --- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridDeviceOrientationManager.kt +++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/orientation/HybridDeviceOrientationManager.kt @@ -41,7 +41,7 @@ class HybridDeviceOrientationManager : HybridOrientationManagerSpec() { // phone is laying flat - orientation is unknown! Avoid sending out event. return } - val orientation = CameraOrientation.fromDegrees(rotationDegrees) + val orientation = CameraOrientation.fromDegrees(rotationDegrees) ?: return if (currentOrientation != orientation) { Log.i(TAG, "Device orientation changed! $orientation") currentOrientation = orientation diff --git a/packages/react-native-vision-camera/ios/Extensions/Converters/UI+CameraOrientation.swift b/packages/react-native-vision-camera/ios/Extensions/Converters/UI+CameraOrientation.swift index 97757edafc..3b7a293a27 100644 --- a/packages/react-native-vision-camera/ios/Extensions/Converters/UI+CameraOrientation.swift +++ b/packages/react-native-vision-camera/ios/Extensions/Converters/UI+CameraOrientation.swift @@ -23,7 +23,7 @@ extension CameraOrientation { fatalError("UIImage.Orientation has unknown value: \(uiOrientation)") } } - init(interfaceOrientation: UIInterfaceOrientation) { + init?(interfaceOrientation: UIInterfaceOrientation) { switch interfaceOrientation { case .portrait: self = .up @@ -34,7 +34,7 @@ extension CameraOrientation { case .landscapeRight: self = .left case .unknown: - self = .up + return nil @unknown default: fatalError("UIInterfaceOrientation has unknown value: \(interfaceOrientation)") } diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift index 29b10175c7..fb73fb59c8 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Orientation/HybridInterfaceOrientationManager.swift @@ -35,8 +35,7 @@ final class HybridInterfaceOrientationManager: HybridOrientationManagerSpec { UIDevice.current.beginGeneratingDeviceOrientationNotifications() let interfaceOrientation = UIApplication.shared.interfaceOrientation - if interfaceOrientation != .unknown { - let orientation = CameraOrientation(interfaceOrientation: interfaceOrientation) + if let orientation = CameraOrientation(interfaceOrientation: interfaceOrientation) { self.currentOrientation = orientation onChanged(orientation) } @@ -48,11 +47,10 @@ final class HybridInterfaceOrientationManager: HybridOrientationManagerSpec { ) { [weak self] _ in guard let self else { return } let interfaceOrientation = UIApplication.shared.interfaceOrientation - guard interfaceOrientation != .unknown else { + guard let orientation = CameraOrientation(interfaceOrientation: interfaceOrientation) else { logger.warning("UIInterfaceOrientation is .unknown!") return } - let orientation = CameraOrientation(interfaceOrientation: interfaceOrientation) if self.currentOrientation != orientation { logger.info("Interface orientation changed: \(orientation.stringValue)") self.currentOrientation = orientation