From aa9bdad657817cee8cfdea7fef656c680ed87037 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 9 Jun 2026 13:18:36 +0200 Subject: [PATCH 1/8] fix: Apply session-config dependent properties in `configure()` --- .../ios/Public/NativeCameraOutput.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-native-vision-camera/ios/Public/NativeCameraOutput.swift b/packages/react-native-vision-camera/ios/Public/NativeCameraOutput.swift index b1a1db6b75..e895b3ed61 100644 --- a/packages/react-native-vision-camera/ios/Public/NativeCameraOutput.swift +++ b/packages/react-native-vision-camera/ios/Public/NativeCameraOutput.swift @@ -53,7 +53,10 @@ public protocol NativeCameraOutput: AnyObject, ResolutionNegotiationParticipant * change, in a `beginConfiguration()`/`commitConfiguration()` * batch. * The `NativeCameraOutput` is expected to apply all configs - * such as orientation or mirroring settings in here. + * such as orientation or mirroring settings in here, as well + * as any output properties that depend on session configuration, + * like `isResponsiveCaptureEnabled` which depends on + * the `isResponsiveCaptureSupported` property. */ func configure(config: CameraOutputConfiguration) } From 2898b5fca30f43ede4db7767a92a1f9c7b99c451 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 9 Jun 2026 13:26:25 +0200 Subject: [PATCH 2/8] Move into configure() --- .../HybridCameraDepthFrameOutput.swift | 12 +++--- .../Outputs/HybridCameraFrameOutput.swift | 12 +++--- .../Outputs/HybridCameraPhotoOutput.swift | 40 ++++++++++--------- .../HybridCameraVideoFrameOutput.swift | 10 +++-- 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift index e600a6eac6..4f4bd5c9bd 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift @@ -59,11 +59,6 @@ final class HybridCameraDepthFrameOutput: HybridCameraDepthFrameOutputSpec, Nati // Configure `videoSettings` output.alwaysDiscardsLateDepthData = options.dropFramesWhileBusy output.isFilteringEnabled = options.enableFiltering - if #available(iOS 26.0, *), output.isDeferredStartSupported { - // Deferred start allows the Session to delay this output's startup in favor - // of preview-related outputs to make preview appear faster. - output.isDeferredStartEnabled = options.allowDeferredStart - } } func configure(config: CameraOutputConfiguration) { @@ -79,6 +74,13 @@ final class HybridCameraDepthFrameOutput: HybridCameraDepthFrameOutputSpec, Nati try? connection.setOrientation(outputOrientation) try? connection.setMirrorMode(config.mirrorMode) } + if #available(iOS 26.0, *), output.isDeferredStartSupported { + // Deferred start allows the Session to delay this output's startup in favor + // of preview-related outputs to make preview appear faster. + if output.isDeferredStartEnabled != options.allowDeferredStart { + output.isDeferredStartEnabled = options.allowDeferredStart + } + } } private func getMediaSampleMetadata( diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift index afba918669..c98840b5e0 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift @@ -61,11 +61,6 @@ final class HybridCameraFrameOutput: HybridCameraFrameOutputSpec, NativeCameraOu output.alwaysDiscardsLateVideoFrames = options.dropFramesWhileBusy // JS configures the video resolution, we don't want to downscale here by default. output.automaticallyConfiguresOutputBufferDimensions = false - if #available(iOS 26.0, *), output.isDeferredStartSupported { - // Deferred start allows the Session to delay this output's startup in favor - // of preview-related outputs to make preview appear faster. - output.isDeferredStartEnabled = options.allowDeferredStart - } // TODO: Add a flag called `allowBufferResizing` and probably get rid of `enablePreviewSizedOutputBuffers` // If `allowBufferResizing` is true, we can set `.videoSettings`/dimensions here to `options.targetResolution` // and `targetResolution` to `.any` (to not participate in resolution negotiations). @@ -99,6 +94,13 @@ final class HybridCameraFrameOutput: HybridCameraFrameOutputSpec, NativeCameraOu connection.isCameraIntrinsicMatrixDeliveryEnabled = true } } + if #available(iOS 26.0, *), output.isDeferredStartSupported { + // Deferred start allows the Session to delay this output's startup in favor + // of preview-related outputs to make preview appear faster. + if output.isDeferredStartEnabled != options.allowDeferredStart { + output.isDeferredStartEnabled = options.allowDeferredStart + } + } } private func videoSettingsForPixelFormat(_ targetPixelFormat: TargetVideoPixelFormat) -> [String: diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift index 1f5a54cf18..37bad78117 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift @@ -54,34 +54,34 @@ final class HybridCameraPhotoOutput: HybridCameraPhotoOutputSpec, NativeCameraOu self.output = AVCapturePhotoOutput() self.options = options super.init() + } + + func configure(config: CameraOutputConfiguration) { + guard let connection = output.connection(with: .video) else { + return + } + try? connection.setOrientation(outputOrientation) + try? connection.setMirrorMode(config.mirrorMode) - output.maxPhotoQualityPrioritization = options.qualityPrioritization.toAVQualityPrioritization() + let qualityPrioritization = options.qualityPrioritization.toAVQualityPrioritization() + if output.maxPhotoQualityPrioritization != qualityPrioritization { + output.maxPhotoQualityPrioritization = qualityPrioritization + } - if options.containerFormat == .dng { + let enableAppleProRAW = options.containerFormat == .dng && output.isAppleProRAWSupported + if output.isAppleProRAWEnabled != enableAppleProRAW { // If we capture RAW photos, try using Apple ProRAW. If not, Bayer14 RAW will be used. - output.isAppleProRAWEnabled = output.isAppleProRAWSupported + output.isAppleProRAWEnabled = enableAppleProRAW } if #available(iOS 26, *), - output.isCameraSensorOrientationCompensationSupported + output.isCameraSensorOrientationCompensationSupported, + output.isCameraSensorOrientationCompensationEnabled { // Don't rotate Photo buffers - we handle orientation later on in file capture or toImage(). output.isCameraSensorOrientationCompensationEnabled = false } - // Prepare the default Photo Settings to make the pipeline ready - some things (like flashMode) - // might change on a per-capture basis, but containerFormat and preview size is already known - // and can be prepared already. - try? prepareDefaultPhotoSettings() - } - - func configure(config: CameraOutputConfiguration) { - guard let connection = output.connection(with: .video) else { - return - } - try? connection.setOrientation(outputOrientation) - try? connection.setMirrorMode(config.mirrorMode) - if #available(iOS 16.0, *) { // Configure PhotoOutput to the currently selected Format's max photo size if let nativeDevice = connection.deviceInput { @@ -93,10 +93,14 @@ final class HybridCameraPhotoOutput: HybridCameraPhotoOutputSpec, NativeCameraOu { // Target max photo dimensions have changed, re-configure output.maxPhotoDimensions = nearestPhotoDimension - try? prepareDefaultPhotoSettings() } } } + + // Prepare the default Photo Settings to make the pipeline ready - some things (like flashMode) + // might change on a per-capture basis, but containerFormat and preview size is already known + // and can be prepared already. + try? prepareDefaultPhotoSettings() } private func prepareDefaultPhotoSettings() throws { diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift index 4abe05cbea..e20690ab08 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift @@ -64,10 +64,6 @@ final class HybridCameraVideoFrameOutput: HybridCameraVideoOutputSpec, NativeCam // JS configures the video resolution, we don't want to downscale here. output.automaticallyConfiguresOutputBufferDimensions = false if #available(iOS 26.0, *) { - // We only use this output for recording, allowing it to start deferred makes the session start faster. - if output.isDeferredStartSupported { - output.isDeferredStartEnabled = true - } // Allow capturing HDR output.preservesDynamicHDRMetadata = true } @@ -87,6 +83,12 @@ final class HybridCameraVideoFrameOutput: HybridCameraVideoOutputSpec, NativeCam } try? connection.setMirrorMode(config.mirrorMode) try? connection.setOrientation(outputOrientation) + if #available(iOS 26.0, *), output.isDeferredStartSupported { + // We only use this output for recording, allowing it to start deferred makes the session start faster. + if !output.isDeferredStartEnabled { + output.isDeferredStartEnabled = true + } + } } func getSupportedVideoCodecs() throws -> [VideoCodec] { From 03a6a53fa046a0b98f7a60d80eac0d8de16ed7fc Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 9 Jun 2026 13:29:59 +0200 Subject: [PATCH 3/8] fix: Set orientation compensation to false --- .../ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift index 37bad78117..72ed8fc9b8 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift @@ -74,10 +74,7 @@ final class HybridCameraPhotoOutput: HybridCameraPhotoOutputSpec, NativeCameraOu output.isAppleProRAWEnabled = enableAppleProRAW } - if #available(iOS 26, *), - output.isCameraSensorOrientationCompensationSupported, - output.isCameraSensorOrientationCompensationEnabled - { + if #available(iOS 26, *) { // Don't rotate Photo buffers - we handle orientation later on in file capture or toImage(). output.isCameraSensorOrientationCompensationEnabled = false } From 3bfa547d2a164d9cfa2f132f7725ba0d2908d056 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 9 Jun 2026 13:30:21 +0200 Subject: [PATCH 4/8] fix: Default mic can be iOS 17 --- .../AVFoundation/AVCaptureSession+addAudioInput.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addAudioInput.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addAudioInput.swift index f27e90e02a..a3a9f7cb58 100644 --- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addAudioInput.swift +++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addAudioInput.swift @@ -37,7 +37,7 @@ extension AVCaptureSession { private func getDefaultMicrophone() throws -> AVCaptureDevice { // TODO: Make microphone input selectable from JS - if #available(iOS 18.0, *) { + if #available(iOS 17.0, *) { if let microphone = AVCaptureDevice.default(.microphone, for: .audio, position: .unspecified) { return microphone } From 14da7e7e5335a8a64b5a2efec080ee4f95b649bc Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 9 Jun 2026 13:33:06 +0200 Subject: [PATCH 5/8] fix: initialize deferred start on outputs --- .../Outputs/HybridCameraDepthFrameOutput.swift | 12 +++++------- .../Outputs/HybridCameraFrameOutput.swift | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift index 4f4bd5c9bd..e600a6eac6 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift @@ -59,6 +59,11 @@ final class HybridCameraDepthFrameOutput: HybridCameraDepthFrameOutputSpec, Nati // Configure `videoSettings` output.alwaysDiscardsLateDepthData = options.dropFramesWhileBusy output.isFilteringEnabled = options.enableFiltering + if #available(iOS 26.0, *), output.isDeferredStartSupported { + // Deferred start allows the Session to delay this output's startup in favor + // of preview-related outputs to make preview appear faster. + output.isDeferredStartEnabled = options.allowDeferredStart + } } func configure(config: CameraOutputConfiguration) { @@ -74,13 +79,6 @@ final class HybridCameraDepthFrameOutput: HybridCameraDepthFrameOutputSpec, Nati try? connection.setOrientation(outputOrientation) try? connection.setMirrorMode(config.mirrorMode) } - if #available(iOS 26.0, *), output.isDeferredStartSupported { - // Deferred start allows the Session to delay this output's startup in favor - // of preview-related outputs to make preview appear faster. - if output.isDeferredStartEnabled != options.allowDeferredStart { - output.isDeferredStartEnabled = options.allowDeferredStart - } - } } private func getMediaSampleMetadata( diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift index c98840b5e0..570eb10744 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift @@ -69,6 +69,11 @@ final class HybridCameraFrameOutput: HybridCameraFrameOutputSpec, NativeCameraOu if #available(iOS 17.0, *), options.enablePreviewSizedOutputBuffers { output.deliversPreviewSizedOutputBuffers = true } + if #available(iOS 26.0, *), output.isDeferredStartSupported { + // Deferred start allows the Session to delay this output's startup in favor + // of preview-related outputs to make preview appear faster. + output.isDeferredStartEnabled = options.allowDeferredStart + } if #available(iOS 26.0, *) { // We don't need HDR metadata, as that's only useful for encoders. output.preservesDynamicHDRMetadata = false @@ -94,13 +99,6 @@ final class HybridCameraFrameOutput: HybridCameraFrameOutputSpec, NativeCameraOu connection.isCameraIntrinsicMatrixDeliveryEnabled = true } } - if #available(iOS 26.0, *), output.isDeferredStartSupported { - // Deferred start allows the Session to delay this output's startup in favor - // of preview-related outputs to make preview appear faster. - if output.isDeferredStartEnabled != options.allowDeferredStart { - output.isDeferredStartEnabled = options.allowDeferredStart - } - } } private func videoSettingsForPixelFormat(_ targetPixelFormat: TargetVideoPixelFormat) -> [String: From eb228643d1d508c1273da82974987ba58da75109 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 9 Jun 2026 13:33:24 +0200 Subject: [PATCH 6/8] fix: normalize preview-sized buffer setting --- .../ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift index 570eb10744..eb56fb8d97 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift @@ -66,8 +66,8 @@ final class HybridCameraFrameOutput: HybridCameraFrameOutputSpec, NativeCameraOu // and `targetResolution` to `.any` (to not participate in resolution negotiations). // If `allowBufferResizing` is false, we don't set `.videoSettings` ("native" resolution), and reflect our // target resolution via `targetResolution` to participate in resolution negotiations. - if #available(iOS 17.0, *), options.enablePreviewSizedOutputBuffers { - output.deliversPreviewSizedOutputBuffers = true + if #available(iOS 13.0, *) { + output.deliversPreviewSizedOutputBuffers = options.enablePreviewSizedOutputBuffers } if #available(iOS 26.0, *), output.isDeferredStartSupported { // Deferred start allows the Session to delay this output's startup in favor From aff7297d7cd1e990801c0b8ffff198c1099efb51 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 9 Jun 2026 13:33:47 +0200 Subject: [PATCH 7/8] fix: initialize photo quality prioritization --- .../Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift index 72ed8fc9b8..9a736548b8 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift @@ -54,6 +54,8 @@ final class HybridCameraPhotoOutput: HybridCameraPhotoOutputSpec, NativeCameraOu self.output = AVCapturePhotoOutput() self.options = options super.init() + + output.maxPhotoQualityPrioritization = options.qualityPrioritization.toAVQualityPrioritization() } func configure(config: CameraOutputConfiguration) { @@ -63,11 +65,6 @@ final class HybridCameraPhotoOutput: HybridCameraPhotoOutputSpec, NativeCameraOu try? connection.setOrientation(outputOrientation) try? connection.setMirrorMode(config.mirrorMode) - let qualityPrioritization = options.qualityPrioritization.toAVQualityPrioritization() - if output.maxPhotoQualityPrioritization != qualityPrioritization { - output.maxPhotoQualityPrioritization = qualityPrioritization - } - let enableAppleProRAW = options.containerFormat == .dng && output.isAppleProRAWSupported if output.isAppleProRAWEnabled != enableAppleProRAW { // If we capture RAW photos, try using Apple ProRAW. If not, Bayer14 RAW will be used. From c820bef6d7ad6eea0cea2c0a263e11373ef411e5 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 9 Jun 2026 13:37:31 +0200 Subject: [PATCH 8/8] fix: initialize recording deferred start --- .../Outputs/HybridCameraVideoFrameOutput.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift index e20690ab08..4abe05cbea 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift @@ -64,6 +64,10 @@ final class HybridCameraVideoFrameOutput: HybridCameraVideoOutputSpec, NativeCam // JS configures the video resolution, we don't want to downscale here. output.automaticallyConfiguresOutputBufferDimensions = false if #available(iOS 26.0, *) { + // We only use this output for recording, allowing it to start deferred makes the session start faster. + if output.isDeferredStartSupported { + output.isDeferredStartEnabled = true + } // Allow capturing HDR output.preservesDynamicHDRMetadata = true } @@ -83,12 +87,6 @@ final class HybridCameraVideoFrameOutput: HybridCameraVideoOutputSpec, NativeCam } try? connection.setMirrorMode(config.mirrorMode) try? connection.setOrientation(outputOrientation) - if #available(iOS 26.0, *), output.isDeferredStartSupported { - // We only use this output for recording, allowing it to start deferred makes the session start faster. - if !output.isDeferredStartEnabled { - output.isDeferredStartEnabled = true - } - } } func getSupportedVideoCodecs() throws -> [VideoCodec] {