š Camera Preview orientation does not update when screen orientation locks
What's happening?
I have a following scenario in my application:
- I have a pre-recording screen with camera preview - orientation here is locked to portrait
- After pressing Record I'm changing orientation to landscape and I'm moving to "Recording screen"
- In "Recording screen" I have a small camera preview - this camera preview stays at portrait orientation
You can reproduce it by running the example app like so;
- Run the app
- Open permissions screen, allow app to use camera
- Rotate your device for landscape mode (screen is locked in portrait)
- Press "Go to camera"
- Screen will be changed to "camera" and rotated to landscape, only preview stays in wrong orientation
All videos are recorded properly, in landscape mode. I'm having issues only with preview orientation. Sometimes camera preview flips with screen locking but most of the time it stays in previous orientation.
Also if I'll shake device a little orientation change to the right one.
I'm using expo-screen-orientation for locking orientation
Example app: https://github.com/dawidzawada/camera-bug
Reproduceable Code
import {lockPlatformAsync, Orientation} from "expo-screen-orientation";
export default function PermissionsScreen() {
const {requestPermission} = useCameraPermission()
useEffect(() => {
void requestPermission()
}, []);
useFocusEffect(() => {
// Set portrait orientation for permissions screen
const asyncEffect = async () => {
await lockPlatformAsync({screenOrientationArrayIOS: [
Orientation.PORTRAIT_UP,
]
})
}
asyncEffect()
})
const handlePress = async () => {
// Set landscape orientation, then go to camera screen
const asyncEffect = async () => {
await lockPlatformAsync({screenOrientationArrayIOS: [
Orientation.LANDSCAPE_LEFT,
Orientation.LANDSCAPE_RIGHT
]
})
router.push('/camera')
}
asyncEffect()
}
return (
<View>
<Text>Permissions</Text>
<Button title="Go to camera" onPress={handlePress} />
</View>
)
}
export default function CameraScreen() {
const device = useCameraDevice('back');
return (
<View>
{device && <Camera device={device} style={{width:300, height: 160}} isActive={true} />}
</View>
)
}
Relevant log output
16:19:25.549: [info] šø VisionCamera.didSetProps(_:): Updating 19 props: [onPreviewStarted, preview, onViewReady, onPreviewOrientationChanged, cameraId, isMirrored, onError, width, onShutter, onInitialized, onStarted, enableBufferCompression, onStopped, height, onCodeScanned, onOutputOrientationChanged, onPreviewStopped, enableFrameProcessor, isActive]
16:19:25.549: [info] šø VisionCamera.configurePreviewOrientation(_:): Updating Preview rotation: portrait...
16:19:25.549: [info] šø VisionCamera.configureOutputOrientation(_:): Updating Outputs rotation: portrait...
16:19:25.550: [info] šø VisionCamera.configure(_:): configure { ... }: Waiting for lock...
16:19:25.550: [info] šø VisionCamera.configure(_:): configure { ... }: Updating CameraSession Configuration... Difference(inputChanged: true, outputsChanged: true, videoStabilizationChanged: true, orientationChanged: true, formatChanged: true, sidePropsChanged: true, torchChanged: true, zoomChanged: true, exposureChanged: true, audioSessionChanged: true, locationChanged: true)
16:19:25.551: [info] šø VisionCamera.configureDevice(configuration:): Configuring Input Device...
16:19:25.551: [info] šø VisionCamera.configureDevice(configuration:): Configuring Camera com.apple.avfoundation.avcapturedevice.built-in_video:0...
16:19:25.553: [info] šø VisionCamera.configureDevice(configuration:): Successfully configured Input Device!
16:19:25.553: [info] šø VisionCamera.configureOutputs(configuration:): Configuring Outputs...
16:19:25.553: [info] šø VisionCamera.configurePreviewOrientation(_:): Updating Preview rotation: portrait...
16:19:25.553: [info] šø VisionCamera.configureOutputOrientation(_:): Updating Outputs rotation: portrait...
16:19:25.553: [info] šø VisionCamera.configureOutputs(configuration:): Successfully configured all outputs!
16:19:25.553: [info] šø VisionCamera.setTargetOutputOrientation(_:): Setting target output orientation from device to device...
16:19:25.662: [debug] šø VisionCamera.deviceOrientation: Device Orientation changed from portrait -> landscapeLeft
16:19:25.662: [info] šø VisionCamera.configureOutputOrientation(_:): Updating Outputs rotation: landscapeRight...
16:19:25.824: [info] šø VisionCamera.init(frame:session:): Preview Layer started previewing.
16:19:25.824: [info] šø VisionCamera.configure(_:): Beginning AudioSession configuration...
16:19:25.824: [info] šø VisionCamera.configureAudioSession(configuration:): Configuring Audio Session...
16:19:25.825: [info] šø VisionCamera.configure(_:): Committed AudioSession configuration!
16:19:25.829: [info] šø VisionCamera.configure(_:): Beginning Location Output configuration...
16:19:25.830: [info] šø VisionCamera.configure(_:): Finished Location Output configuration!
Camera Device
"id": "com.apple.avfoundation.avcapturedevice.built-in_video:0",
"minZoom": 1,
"neutralZoom": 1,
"position": "back",
"name": "Back Camera",
"maxZoom": 123.75,
"minFocusDistance": 12,
"maxExposure": 8,
"minExposure": -8,
"supportsFocus": true,
"physicalDevices": [
"wide-angle-camera"
],
"isMultiCam": false,
"hardwareLevel": "full",
"sensorOrientation": "portrait",
"formats": [],
"hasFlash": true,
"supportsRawCapture": false,
"supportsLowLightBoost": false,
"hasTorch": true
}
Device
iPhone 12
VisionCamera Version
4.6.3
Can you reproduce this issue in the VisionCamera Example app?
Yes, I can reproduce the same issue in the Example app here
Additional information
- [X] I am using Expo
- [ ] I have enabled Frame Processors (react-native-worklets-core)
- [X] I have read the Troubleshooting Guide
- [X] I agree to follow this project's Code of Conduct
- [X] I searched for similar issues in this repository and found none.
Guten Tag, Hans here š»
Thanks for your detailed report! It looks like a valid issue with ze camera preview orientation. Since you provided a clear reproduction path and relevant logs, mrousavy can take a look at it.
Please allow some time for mrousavy to review and address this, as he is maintaining ze project in his free time. If you have any further information or updates, feel free to share them!
Note: If you think I made a mistake, please ping
@mrousavyto take a look.
I'm using latest expo-screen-orientation and there's nothing additional that could affect this preview behavior - have a look at the example app.
We are also using expo-screen-orientation, and we are experiencing abnormal rotation of the preview on iOS devices.
What's happening?
You can reproduce it by running the example app like so:
- Run
yarn iosto launch the app. - Grant camera permissions.
- Tilt the device vertically or horizontally.
- The preview rotates 90 degrees from the correct orientation.
Reproduceable Code
We are using expo-sensors to detect screen tilt and expo-screen-orientation to lock the screen orientation. For the screen lock, we use setTimeout to introduce a delay, simulating cases where in-app processing takes time. The issue does not seem to occur without this setTimeout.
App.js
import React, { useEffect, useState } from "react";
import { StyleSheet, Text, View, Platform } from "react-native";
import {
Camera,
useCameraPermission,
useCameraDevice,
} from "react-native-vision-camera";
import {
Orientation,
OrientationLock,
getOrientationLockAsync,
lockAsync,
} from "expo-screen-orientation";
import { Accelerometer } from "expo-sensors";
const getNextOrientationFromAccelerometerEvent = (x, y, z) => {
// when the device is flat, keep the current orientation
if (Math.abs(z) > 0.5) {
return undefined;
}
if (x >= 0.75) {
return Orientation.LANDSCAPE_LEFT;
}
if (x <= -0.75) {
return Orientation.LANDSCAPE_RIGHT;
}
if (y >= 0.75) {
return Orientation.PORTRAIT_UP;
}
if (y <= -0.75) {
return Orientation.PORTRAIT_DOWN;
}
return undefined;
};
// custom hook to get the current orientation of the device
const useCurrentOrientation = () => {
const [orientation, setOrientation] = useState(Orientation.PORTRAIT_UP);
useEffect(() => {
Accelerometer.setUpdateInterval(16);
console.log("register subscription");
const subscription = Accelerometer.addListener((event) => {
const nextOrientation = getNextOrientationFromAccelerometerEvent(
event.x,
event.y,
event.z,
);
if (nextOrientation) {
setOrientation(nextOrientation);
}
});
return () => {
console.log("remove subscription");
Accelerometer.removeSubscription(subscription);
};
}, []);
return orientation;
};
const getLockOrientation = (orientation) => {
switch (orientation) {
case Orientation.PORTRAIT_UP:
return OrientationLock.PORTRAIT_UP;
case Orientation.PORTRAIT_DOWN:
return OrientationLock.PORTRAIT_UP;
case Orientation.LANDSCAPE_RIGHT:
return Platform.OS === "ios"
? OrientationLock.LANDSCAPE_RIGHT
: OrientationLock.LANDSCAPE_LEFT;
case Orientation.LANDSCAPE_LEFT:
return Platform.OS === "ios"
? OrientationLock.LANDSCAPE_LEFT
: OrientationLock.LANDSCAPE_RIGHT;
default:
return undefined;
}
};
const App = () => {
const device = useCameraDevice("back");
const { hasPermission, requestPermission } = useCameraPermission();
const orientation = useCurrentOrientation();
useEffect(() => {
if (!hasPermission) {
requestPermission();
}
}, [hasPermission]);
// lock screen orientation based on the current orientation
useEffect(() => {
const f = async () => {
console.log("orientation changed", orientation);
const lockOrientation = await getOrientationLockAsync();
const nextLockOrientation = getLockOrientation(orientation);
if (nextLockOrientation && lockOrientation !== nextLockOrientation) {
// emulates the behavior of production apps
setTimeout(() => {
lockAsync(nextLockOrientation);
}, 500);
}
};
f();
}, [orientation]);
if (!hasPermission)
return (
<View>
<Text>Camera permission required</Text>
</View>
);
if (device == null)
return (
<View>
<Text>No camera device found</Text>
</View>
);
return (
<Camera style={StyleSheet.absoluteFill} device={device} isActive={true} />
);
};
export default App;
package.json
{
"name": "vision-camera-poc-2",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"ios": "expo run:ios --device",
"android": "expo run:android"
},
"dependencies": {
"@react-navigation/native": "^7.0.9",
"@react-navigation/native-stack": "^7.1.10",
"expo": "~50.0.17",
"expo-screen-orientation": "~6.4.1",
"expo-sensors": "~12.9.1",
"expo-status-bar": "~1.11.1",
"react": "18.2.0",
"react-native": "0.73.6",
"react-native-orientation-locker": "^1.7.0",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "4",
"react-native-vision-camera": "^4.6.3"
},
"devDependencies": {
"@babel/core": "^7.20.0"
},
"private": true
}
Relevant log output
2024-12-11 14:16:51.938310+0900 visioncamerapoc2[782:66087] [native] Invalidating <RCTCxxBridge: 0x108706430> (parent: <RCTBridge: 0x280cbcf50>, executor: (null))
2024-12-11 14:16:52.023030+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExponentConstants'
2024-12-11 14:16:52.031448+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExponentFileSystem'
2024-12-11 14:16:52.032647+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExpoKeepAwake'
2024-12-11 14:16:52.035738+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExpoScreenOrientation'
2024-12-11 14:16:52.036204+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExponentAccelerometer'
2024-12-11 14:16:52.036301+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExpoBarometer'
2024-12-11 14:16:52.036425+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExponentDeviceMotion'
2024-12-11 14:16:52.036528+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExponentGyroscope'
2024-12-11 14:16:52.036625+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExponentMagnetometer'
2024-12-11 14:16:52.036732+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExponentMagnetometerUncalibrated'
2024-12-11 14:16:52.037759+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'ExponentPedometer'
2024-12-11 14:16:52.039518+0900 visioncamerapoc2[782:66087] [expo] š¢ Registering module 'NativeModulesProxy'
Thread Performance Checker: Thread running at QOS_CLASS_USER_INTERACTIVE waiting on a lower QoS thread running at QOS_CLASS_DEFAULT. Investigate ways to avoid priority inversions
PID: 782, TID: 66087
Backtrace
=================================================================
3 visioncamerapoc2 0x000000010585f360 -[SRRunLoopThread runLoop] + 44
4 visioncamerapoc2 0x000000010585989c +[NSRunLoop(SRWebSocket) SR_networkRunLoop] + 56
5 visioncamerapoc2 0x000000010585dc90 -[SRProxyConnect _openConnection] + 72
6 visioncamerapoc2 0x000000010585d148 -[SRProxyConnect _configureProxy] + 916
7 visioncamerapoc2 0x000000010585c93c -[SRProxyConnect openNetworkStreamWithCompletion:] + 92
8 visioncamerapoc2 0x00000001058610f0 -[SRWebSocket open] + 624
9 visioncamerapoc2 0x000000010529a504 -[RCTReconnectingWebSocket start] + 148
10 visioncamerapoc2 0x0000000105285588 -[RCTPackagerConnection init] + 416
11 visioncamerapoc2 0x00000001052853c8 __49+[RCTPackagerConnection sharedPackagerConnection]_block_invoke + 36
12 libdispatch.dylib 0x000000010791a038 _dispatch_client_callout + 20
13 libdispatch.dylib 0x000000010791bb9c _dispatch_once_callout + 140
14 visioncamerapoc2 0x000000010528537c +[RCTPackagerConnection sharedPackagerConnection] + 88
15 visioncamerapoc2 0x0000000105313360 -[RCTDevSettings initialize] + 164
16 visioncamerapoc2 0x0000000105270ed0 -[RCTModuleData _initializeModule] + 92
17 visioncamerapoc2 0x0000000105270828 -[RCTModuleData setUpInstanceAndBridge:] + 2168
18 visioncamerapoc2 0x0000000105272324 -[RCTModuleData instance] + 1168
19 visioncamerapoc2 0x00000001052192b0 -[RCTCxxBridge moduleForName:lazilyLoadIfNecessary:] + 704
20 visioncamerapoc2 0x000000010527aff4 -[RCTModuleRegistry moduleForName:lazilyLoadIfNecessary:] + 140
21 visioncamerapoc2 0x000000010527af5c -[RCTModuleRegistry moduleForName:] + 48
22 visioncamerapoc2 0x000000010532351c -[RCTPerfMonitor devMenuItem] + 92
23 visioncamerapoc2 0x00000001053233e8 -[RCTPerfMonitor initialize] + 88
24 visioncamerapoc2 0x0000000105270ed0 -[RCTModuleData _initializeModule] + 92
25 visioncamerapoc2 0x0000000105270828 -[RCTModuleData setUpInstanceAndBridge:] + 2168
26 visioncamerapoc2 0x000000010527255c __25-[RCTModuleData instance]_block_invoke + 44
27 visioncamerapoc2 0x00000001052d3d68 RCTUnsafeExecuteOnMainQueueSync + 52
28 visioncamerapoc2 0x00000001052721c4 -[RCTModuleData instance] + 816
29 visioncamerapoc2 0x000000010521db90 __49-[RCTCxxBridge _prepareModulesWithDispatchGroup:]_block_invoke + 160
30 libdispatch.dylib 0x0000000107918520 _dispatch_call_block_and_release + 32
31 libdispatch.dylib 0x000000010791a038 _dispatch_client_callout + 20
32 libdispatch.dylib 0x000000010792a89c _dispatch_main_queue_drain + 1456
33 libdispatch.dylib 0x000000010792a2dc _dispatch_main_queue_callback_4CF + 44
34 CoreFoundation 0x00000001cb413c28 A900B459-0127-379E-9CBA-0EAB9C5D559F + 625704
35 CoreFoundation 0x00000001cb3f5560 A900B459-0127-379E-9CBA-0EAB9C5D559F + 501088
36 CoreFoundation 0x00000001cb3fa3ec CFRunLoopRunSpecific + 612
37 GraphicsServices 0x000000020691035c GSEventRunModal + 164
38 UIKitCore 0x00000001cd786f58 7D57A1D1-856F-338D-97DB-880C4EC8B02E + 3788632
39 UIKitCore 0x00000001cd786bbc UIApplicationMain + 340
40 visioncamerapoc2 0x0000000104fc47c0 main + 96
41 dyld 0x00000001ea92cdec C3FC2EE4-367F-3086-BEB8-420AD442BF88 + 89580
2024-12-11 14:16:53.337741+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'NativeModulesProxy'
2024-12-11 14:16:53.342762+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExponentConstants'
2024-12-11 14:16:53.344444+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExponentFileSystem'
14:16:53.381: [info] šø VisionCamera.constantsToExport(): Found 5 initial Camera Devices.
2024-12-11 14:16:53.409621+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExpoScreenOrientation'
2024-12-11 14:16:53.410389+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExponentPedometer'
2024-12-11 14:16:53.411717+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExponentAccelerometer'
2024-12-11 14:16:53.412346+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExpoBarometer'
2024-12-11 14:16:53.412903+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExponentDeviceMotion'
2024-12-11 14:16:53.413760+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExponentGyroscope'
2024-12-11 14:16:53.414069+0900 visioncamerapoc2[782:66644] [connection] nw_socket_handle_socket_event [C7:1] Socket SO_ERROR [61: Connection refused]
2024-12-11 14:16:53.414299+0900 visioncamerapoc2[782:67599] [connection] nw_connection_get_connected_socket_block_invoke [C7] Client called nw_connection_get_connected_socket on unconnected nw_connection
2024-12-11 14:16:53.414397+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExponentMagnetometer'
2024-12-11 14:16:53.414453+0900 visioncamerapoc2[782:67599] TCP Conn 0x2816a1ae0 Failed : error 0:61 [61]
2024-12-11 14:16:53.414675+0900 visioncamerapoc2[782:67599] [connection] nw_connection_copy_connected_local_endpoint_block_invoke [C7] Client called nw_connection_copy_connected_local_endpoint on unconnected nw_connection
2024-12-11 14:16:53.414904+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExponentMagnetometerUncalibrated'
2024-12-11 14:16:53.415353+0900 visioncamerapoc2[782:67599] [connection] nw_connection_copy_connected_remote_endpoint_block_invoke [C7] Client called nw_connection_copy_connected_remote_endpoint on unconnected nw_connection
2024-12-11 14:16:53.418227+0900 visioncamerapoc2[782:67601] [native] Unbalanced calls start/end for tag 19
2024-12-11 14:16:53.419096+0900 visioncamerapoc2[782:66087] [native] Running application main ({
initialProps = {
};
rootTag = 1;
})
2024-12-11 14:16:53.424701+0900 visioncamerapoc2[782:67601] [javascript] Running "main" with {"rootTag":1,"initialProps":{}}
2024-12-11 14:16:53.561808+0900 visioncamerapoc2[782:67601] [javascript] register subscription
2024-12-11 14:16:53.564317+0900 visioncamerapoc2[782:67601] [javascript] 'orientation changed', 1
2024-12-11 14:16:53.564771+0900 visioncamerapoc2[782:67601] [expo] š¢ Creating JS object for module 'ExpoKeepAwake'
2024-12-11 14:16:53.588793+0900 visioncamerapoc2[782:67601] [javascript] 'orientation changed', 2
14:16:58.265: [info] šø VisionCamera.didSetProps(_:): Updating 22 props: [onInitialized, cameraId, position, enableBufferCompression, onOutputOrientationChanged, onStarted, preview, top, onCodeScanned, right, isActive, isMirrored, onViewReady, onError, onStopped, onPreviewOrientationChanged, onPreviewStopped, enableFrameProcessor, onPreviewStarted, left, bottom, onShutter]
14:16:58.266: [info] šø VisionCamera.configurePreviewOrientation(_:): Updating Preview rotation: portrait...
14:16:58.267: [info] šø VisionCamera.configureOutputOrientation(_:): Updating Outputs rotation: portrait...
14:16:58.267: [info] šø VisionCamera.configure(_:): configure { ... }: Waiting for lock...
14:16:58.269: [info] šø VisionCamera.configure(_:): configure { ... }: Updating CameraSession Configuration... Difference(inputChanged: true, outputsChanged: true, videoStabilizationChanged: true, orientationChanged: true, formatChanged: true, sidePropsChanged: true, torchChanged: true, zoomChanged: true, exposureChanged: true, audioSessionChanged: true, locationChanged: true)
14:16:58.270: [info] šø VisionCamera.configureDevice(configuration:): Configuring Input Device...
14:16:58.270: [info] šø VisionCamera.configureDevice(configuration:): Configuring Camera com.apple.avfoundation.avcapturedevice.built-in_video:0...
14:16:58.277: [info] šø VisionCamera.configureDevice(configuration:): Successfully configured Input Device!
14:16:58.277: [info] šø VisionCamera.configureOutputs(configuration:): Configuring Outputs...
14:16:58.277: [info] šø VisionCamera.configurePreviewOrientation(_:): Updating Preview rotation: portrait...
14:16:58.277: [info] šø VisionCamera.configureOutputOrientation(_:): Updating Outputs rotation: portrait...
14:16:58.277: [info] šø VisionCamera.configureOutputs(configuration:): Successfully configured all outputs!
14:16:58.278: [info] šø VisionCamera.setTargetOutputOrientation(_:): Setting target output orientation from device to device...
14:16:58.501: [info] šø VisionCamera.init(frame:session:): Preview Layer started previewing.
14:16:58.502: [info] šø VisionCamera.configure(_:): Beginning AudioSession configuration...
14:16:58.503: [info] šø VisionCamera.configureAudioSession(configuration:): Configuring Audio Session...
14:16:58.504: [info] šø VisionCamera.configure(_:): Beginning Location Output configuration...
14:16:58.504: [info] šø VisionCamera.configure(_:): Committed AudioSession configuration!
14:16:58.507: [info] šø VisionCamera.configure(_:): Finished Location Output configuration!
2024-12-11 14:16:58.674987+0900 visioncamerapoc2[782:66087] [expo] š¢ Posting 'appBecomesActive' event to registered modules
Camera Device
iPhone11 (iOS 16.6.1)
Device
{
"minZoom": 1,
"isMultiCam": false,
"maxZoom": 16,
"hardwareLevel": "full",
"sensorOrientation": "portrait",
"maxExposure": 8,
"supportsLowLightBoost": false,
"supportsFocus": true,
"neutralZoom": 1,
"supportsRawCapture": false,
"physicalDevices": [
"wide-angle-camera"
],
"position": "back",
"formats": [],
"id": "com.apple.avfoundation.avcapturedevice.built-in_video:0",
"name": "Back Camera",
"hasFlash": true,
"minExposure": -8,
"minFocusDistance": 12,
"hasTorch": true
}
VisionCamera Version
4.6.3
https://github.com/user-attachments/assets/cae88d8e-8a5f-4991-b651-c65b7bc0d8e8
After several attempts I found a solution that helped me:
export const CameraScreen = () => {
const device = useCameraDevice('back');
const isFocused = useIsFocused();
const [orientationChanged, setOrientationChanged] = useState(false);
useEffect(() => {
ScreenOrientation.addOrientationChangeListener(() => {
setOrientationChanged(true);
});
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT);
return () => {
ScreenOrientation.removeOrientationChangeListeners();
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP);
};
}, []);
return (
<View style={{ flex: 1 }}>
{orientationChanged && device && (
<Camera
isActive={isFocused}
device={device}
style={StyleSheet.absoluteFill}
/>
)}
</View>
);
};
Basically, you need to rerender the Camera component after handling the OrientationChangeListener (you can also change the Camera prop "key"). I hope it will help you too.
It appears that the OS screen rotation animation is performed after OrientationManager.onDeviceOrientationChanged fires.
The following patch solved the problem for the time being.
diff --git a/node_modules/react-native-vision-camera/ios/Core/OrientationManager.swift b/node_modules/react-native-vision-camera/ios/Core/OrientationManager.swift
index 413f79d..0d509c7 100644
--- a/node_modules/react-native-vision-camera/ios/Core/OrientationManager.swift
+++ b/node_modules/react-native-vision-camera/ios/Core/OrientationManager.swift
@@ -103,7 +103,7 @@ final class OrientationManager {
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
NotificationCenter.default.addObserver(self,
selector: #selector(onDeviceOrientationChanged),
- name: UIDevice.orientationDidChangeNotification,
+ name: UIApplication.didChangeStatusBarFrameNotification,
object: nil)
}
@@ -113,7 +113,7 @@ final class OrientationManager {
// Stop UI-orientation updates
UIDevice.current.endGeneratingDeviceOrientationNotifications()
NotificationCenter.default.removeObserver(self,
- name: UIDevice.orientationDidChangeNotification,
+ name: UIApplication.didChangeStatusBarFrameNotification,
object: nil)
}
I've tested @motoshira patch and it is working, thanks for sharing it! š„
I haven't noticed any bugs, camera preview seems to flip properly every time, video output is still recorded in proper orientation. Tested this on my and test app linked above.
cc @mrousavy
Thanks @motoshira! Your workaround worked for me.
Iām curious about the root cause of this issue. A typical camera app uses several orientation handlers, including at a minimum:
- This package
- react-native-screens, as used in react-navigation.
Could the root cause be related to the order in which the events are handled?
https://github.com/mrousavy/react-native-vision-camera/issues/3051#issuecomment-3010647771
It appears that the OS screen rotation animation is performed after
OrientationManager.onDeviceOrientationChangedfires. The following patch solved the problem for the time being.diff --git a/node_modules/react-native-vision-camera/ios/Core/OrientationManager.swift b/node_modules/react-native-vision-camera/ios/Core/OrientationManager.swift index 413f79d..0d509c7 100644 --- a/node_modules/react-native-vision-camera/ios/Core/OrientationManager.swift +++ b/node_modules/react-native-vision-camera/ios/Core/OrientationManager.swift @@ -103,7 +103,7 @@ final class OrientationManager { UIDevice.current.beginGeneratingDeviceOrientationNotifications() NotificationCenter.default.addObserver(self, selector: #selector(onDeviceOrientationChanged), - name: UIDevice.orientationDidChangeNotification, + name: UIApplication.didChangeStatusBarFrameNotification, object: nil) } @@ -113,7 +113,7 @@ final class OrientationManager { // Stop UI-orientation updates UIDevice.current.endGeneratingDeviceOrientationNotifications() NotificationCenter.default.removeObserver(self, - name: UIDevice.orientationDidChangeNotification, + name: UIApplication.didChangeStatusBarFrameNotification, object: nil) }
This works great for our development build, but doesn't seem to work for us in non-development builds. We are working in a monorepo with pnpm, which is notorious for its issues with getting Expo to work within it though.
@motoshira @dawidzawada I know this issue is a bit old, but is the above patch still working for you two in production builds?