Prerequisites
Reproduction
https://github.com/mrousavy/react-native-vision-camera/pull/4015/changes
Steps to reproduce
Reproduction: PR #4015 adds two failing harness tests to visioncamera.controller.harness.ts:
- keeps an exposure lock applied between configure() and start() — scene-independent: start() resets an explicit setExposureLocked(...) back to continuous auto-exposure at the format's max ISO.
- resumes auto-exposure near previously converged values on a fresh session — a fresh session restarts AE at max ISO instead of resuming near the previously converged exposure.
What did you expect to happen?
The preview should appear at (or very near) the correct exposure, with no visible exposure ramp — matching both VisionCamera v4 (same app, same device — no visible adjustment on camera open, including cold starts) and the native iOS Camera app.
Auto-exposure should start from a neutral mid-range default — or, when the AVCaptureDevice still holds exposure values from a previous session (which it demonstrably does right up until start() is called, see logs), from those retained values — rather than from the format's maximum ISO.
What actually happened?
On every CameraSession.configure() + start(), auto-exposure begins pinned at the format's maximum ISO (5184 on this format) and converges down to the correct value over ~1–1.5s. The preview is severely overexposed for that entire period. No crash.
Two additional data points:
- When a previous session had run, polling
controller.iso before start() returns that session's correct converged value (e.g. ISO 258). The first sample after start() jumps to ISO 5184 (max). So the start sequence itself resets exposure to max, discarding valid AE state.
- Applying
controller.setExposureLocked(duration, iso) with known-good values inside onConfigured (after configure, before start) does not help: the session start still resets exposure to max ISO, overriding the lock.
Affected platforms
iOS (device)
Device(s) affected
iPhone 17 Pro
VisionCamera version
5.0.11
React Native version
0.83.6
React Native architecture
New Architecture (Fabric / bridgeless)
Features being used
Relevant logs / stack trace
AE telemetry polled from the CameraController every ~100ms (iso, exposureDuration, exposureBias, exposureMode, zoom; timestamps relative to isActive=true; "session configured" / "session started" logged from onConfigured / onStarted):
[AE] session configured
[AE] +127ms iso: 0 shutter: 1/NaN bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
[AE] +234ms iso: 0 shutter: 1/NaN bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
[AE] session started
[AE] +381ms iso: 5184 shutter: 1/30 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00 <-- starts at max ISO
[AE] +484ms iso: 3027 shutter: 1/30 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
[AE] +601ms iso: 1768 shutter: 1/30 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
[AE] +718ms iso: 1768 shutter: 1/30 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
[AE] +834ms iso: 798 shutter: 1/36 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
[AE] +951ms iso: 319 shutter: 1/36 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
[AE] +1051ms iso: 156 shutter: 1/36 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
[AE] +1151ms iso: 108 shutter: 1/38 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00 <-- converged
[AE] +1268ms iso: 108 shutter: 1/50 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
A separate run showing the device still holding a previous session's correct exposure until start() resets it:
[AE] session configured
[AE] +113ms iso: 258 shutter: 1/38 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00 <-- retained from previous session (correct)
[AE] +300ms iso: 0 shutter: 1/NaN bias: 0.00 mode: continuous-auto-exposure zoom: 2.00
[AE] session started
[AE] +411ms iso: 5184 shutter: 1/30 bias: 0.00 mode: continuous-auto-exposure zoom: 2.00 <-- reset to max ISO
The negotiated session config (from onSessionConfigSelected):
CameraSessionConfig(format: <AVCaptureDeviceFormat: 'vide'/'420f' 3264x2448, { 1- 30 fps},
photo dims:{3264x2448,4032x3024}, fov:103.625, gdc fov:106.201, max zoom:189.00 (upscales @1.24),
secondaries:{4.00,16.00}, system zoom range:1.0-24.0, AF System:2, ISO:54.0-5184.0,
SS:0.000014-1.000000, system exposure bias range:-2.0-2.0, supports HDR, supports wide color>,
selectedFPS: Optional(30.0), selectedVideoStabilizationMode: nil,
selectedPreviewStabilizationMode: nil, selectedVideoDynamicRange: Optional(...sdr...),
isPhotoHDREnabled: false)
Additional context
-
exposureBias is 0.00 throughout (the exposure prop is not involved), and zoom is correct (2.00) from the first sample (no lens-switch involved).
-
No effect on the behavior:
- Adding/removing constraints: { fps: 30 }, { videoDynamicRange: CommonDynamicRanges.ANY_SDR }, { resolutionBias: videoOutput }
- Adding/removing a video output alongside the photo output
- Removing the zoom / exposure props entirely
- setExposureLocked(...) in onConfigured as described above
Submission
Prerequisites
Reproduction
https://github.com/mrousavy/react-native-vision-camera/pull/4015/changes
Steps to reproduce
Reproduction: PR #4015 adds two failing harness tests to visioncamera.controller.harness.ts:
What did you expect to happen?
The preview should appear at (or very near) the correct exposure, with no visible exposure ramp — matching both VisionCamera v4 (same app, same device — no visible adjustment on camera open, including cold starts) and the native iOS Camera app.
Auto-exposure should start from a neutral mid-range default — or, when the AVCaptureDevice still holds exposure values from a previous session (which it demonstrably does right up until start() is called, see logs), from those retained values — rather than from the format's maximum ISO.
What actually happened?
On every
CameraSession.configure()+start(), auto-exposure begins pinned at the format's maximum ISO (5184 on this format) and converges down to the correct value over ~1–1.5s. The preview is severely overexposed for that entire period. No crash.Two additional data points:
controller.isobeforestart()returns that session's correct converged value (e.g. ISO 258). The first sample afterstart()jumps to ISO 5184 (max). So the start sequence itself resets exposure to max, discarding valid AE state.controller.setExposureLocked(duration, iso)with known-good values insideonConfigured(after configure, before start) does not help: the session start still resets exposure to max ISO, overriding the lock.Affected platforms
iOS (device)
Device(s) affected
iPhone 17 Pro
VisionCamera version
5.0.11
React Native version
0.83.6
React Native architecture
New Architecture (Fabric / bridgeless)
Features being used
Relevant logs / stack trace
Additional context
exposureBias is 0.00 throughout (the exposure prop is not involved), and zoom is correct (2.00) from the first sample (no lens-switch involved).
No effect on the behavior:
Submission