From bb8ab112472fddab85584dddd2a504e138cf5820 Mon Sep 17 00:00:00 2001 From: Michael Tai Date: Wed, 6 Aug 2025 17:54:19 -0400 Subject: [PATCH 1/2] Making Handler @Sendable for XCode 26 beta 5 --- .../Manager/Helpers/Capture Device/CaptureDevice.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Internal/Manager/Helpers/Capture Device/CaptureDevice.swift b/Sources/Internal/Manager/Helpers/Capture Device/CaptureDevice.swift index ee35084ed..9ff7c4906 100644 --- a/Sources/Internal/Manager/Helpers/Capture Device/CaptureDevice.swift +++ b/Sources/Internal/Manager/Helpers/Capture Device/CaptureDevice.swift @@ -47,8 +47,8 @@ protocol CaptureDevice: NSObject { func lockForConfiguration() throws func unlockForConfiguration() func isExposureModeSupported(_ exposureMode: AVCaptureDevice.ExposureMode) -> Bool - func setExposureModeCustom(duration: CMTime, iso: Float, completionHandler: ((CMTime) -> Void)?) - func setExposureTargetBias(_ bias: Float, completionHandler handler: ((CMTime) -> ())?) + func setExposureModeCustom(duration: CMTime, iso: Float, completionHandler: (@Sendable (CMTime) -> Void)?) + func setExposureTargetBias(_ bias: Float, completionHandler handler: (@Sendable (CMTime) -> ())?) } From 29b28a4776efd292a85ac12e32a8bfcf09a7c104 Mon Sep 17 00:00:00 2001 From: Michael Tai Date: Sat, 9 Aug 2025 19:37:15 -0400 Subject: [PATCH 2/2] enable video stabilization feature --- .../Internal/Manager/CameraManager+Attributes.swift | 1 + Sources/Internal/Manager/CameraManager.swift | 4 ++++ .../CaptureSession+AVCaptureSession.swift | 12 +++++++++++- .../CaptureSession+MockCaptureSession.swift | 4 +++- .../Helpers/Capture Session/CaptureSession.swift | 1 + .../Public+CameraSettings+MCamera.swift | 9 +++++++++ 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Sources/Internal/Manager/CameraManager+Attributes.swift b/Sources/Internal/Manager/CameraManager+Attributes.swift index 74b064de1..42ddbf866 100644 --- a/Sources/Internal/Manager/CameraManager+Attributes.swift +++ b/Sources/Internal/Manager/CameraManager+Attributes.swift @@ -18,6 +18,7 @@ struct CameraManagerAttributes { var outputType: CameraOutputType = .photo var cameraPosition: CameraPosition = .back var isAudioSourceAvailable: Bool = true + var stabilizationLevel: AVCaptureVideoStabilizationMode = .auto var zoomFactor: CGFloat = 1.0 var flashMode: CameraFlashMode = .off var lightMode: CameraLightMode = .off diff --git a/Sources/Internal/Manager/CameraManager.swift b/Sources/Internal/Manager/CameraManager.swift index 1a1cc327e..ddac324de 100644 --- a/Sources/Internal/Manager/CameraManager.swift +++ b/Sources/Internal/Manager/CameraManager.swift @@ -58,6 +58,7 @@ extension CameraManager { setupCameraLayer() try setupDeviceInputs() try setupDeviceOutput() + setVideoStabilizationMode(parent: self) try setupFrameRecorder() notificationCenterManager.setup(parent: self) motionManager.setup(parent: self) @@ -84,6 +85,9 @@ private extension CameraManager { try photoOutput.setup(parent: self) try videoOutput.setup(parent: self) } + func setVideoStabilizationMode(parent: CameraManager) { + captureSession.setStabilizationLevel(parent.attributes.stabilizationLevel) + } func setupFrameRecorder() throws(MCameraError) { let captureVideoOutput = AVCaptureVideoDataOutput() captureVideoOutput.setSampleBufferDelegate(cameraMetalView, queue: .main) diff --git a/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession+AVCaptureSession.swift b/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession+AVCaptureSession.swift index e9c14b64a..c44435c19 100644 --- a/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession+AVCaptureSession.swift +++ b/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession+AVCaptureSession.swift @@ -13,7 +13,17 @@ import AVKit extension AVCaptureSession: @unchecked @retroactive Sendable {} extension AVCaptureSession: CaptureSession { - var deviceInputs: [any CaptureDeviceInput] { inputs as? [any CaptureDeviceInput] ?? [] } + + var deviceInputs: [any CaptureDeviceInput] { + inputs as? [any CaptureDeviceInput] ?? [] + } + + func setStabilizationLevel(_ level: AVCaptureVideoStabilizationMode) { + for connection in self.connections + where connection.isVideoStabilizationSupported { + connection.preferredVideoStabilizationMode = level + } + } } diff --git a/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession+MockCaptureSession.swift b/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession+MockCaptureSession.swift index 7ce47e59a..9ad4c8c22 100644 --- a/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession+MockCaptureSession.swift +++ b/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession+MockCaptureSession.swift @@ -12,7 +12,9 @@ import AVKit extension MockCaptureSession: @unchecked Sendable {} -class MockCaptureSession: NSObject, CaptureSession { required override init() {} +class MockCaptureSession: NSObject, CaptureSession { + func setStabilizationLevel(_ level: AVCaptureVideoStabilizationMode) { } + required override init() {} // MARK: Attributes var isRunning: Bool { _isRunning } var deviceInputs: [any CaptureDeviceInput] { _deviceInputs } diff --git a/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession.swift b/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession.swift index 4bf258718..2e20874fb 100644 --- a/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession.swift +++ b/Sources/Internal/Manager/Helpers/Capture Session/CaptureSession.swift @@ -24,4 +24,5 @@ protocol CaptureSession: Sendable { func add(input: (any CaptureDeviceInput)?) throws(MCameraError) func remove(input: (any CaptureDeviceInput)?) func add(output: AVCaptureOutput?) throws(MCameraError) + func setStabilizationLevel(_ level: AVCaptureVideoStabilizationMode) } diff --git a/Sources/Public/Camera Settings/Public+CameraSettings+MCamera.swift b/Sources/Public/Camera Settings/Public+CameraSettings+MCamera.swift index 483823fb6..cc2c2ea9e 100644 --- a/Sources/Public/Camera Settings/Public+CameraSettings+MCamera.swift +++ b/Sources/Public/Camera Settings/Public+CameraSettings+MCamera.swift @@ -154,6 +154,15 @@ public extension MCamera { If disabled, the camera will not record audio, and will not ask for permission to access the microphone. */ func setAudioAvailability(_ isAvailable: Bool) -> Self { manager.attributes.isAudioSourceAvailable = isAvailable; return self } + + /** + By default, the camera system disables Stabilization. + + Setting all video connections to the desired AVCaptureVideoStabilizationMode. + Note: if the desired AVCaptureVideoStabilizationMode is not available, the system will fall back to .off. + It is recommended to perform a separate test for the highest available AVCaptureVideoStabilizationMode. + */ + func setVideoStabilization(_ stabilizationLevel: AVCaptureVideoStabilizationMode) -> Self { manager.attributes.stabilizationLevel = stabilizationLevel; return self } /** Changes the initial camera zoom level.