Skip to content

[camera_avfoundation] Implementation swift migration - part 5 #9397

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Migrate reportInitializationState, setExposureMode, setExposureOffset…
…, setExposurePoint, setFocusMode, and setFocusPoint to Swift
  • Loading branch information
RobertOdrowaz committed Jun 20, 2025
commit 93badda5eb951489981c3ef30b765b058dce4d63
4 changes: 3 additions & 1 deletion packages/camera/camera_avfoundation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## 0.9.20+1

* Migrates `start`, `stop`, `close`, `receivedImageStreamData` methods to Swift.
* Migrates lifecycle methods (`start`, `stop`, `close`) to Swift.
* Migrates exposure and focus related methods to Swift.
* Migrates `receivedImageStreamData` and `reportInitializationState` methods to Swift.

## 0.9.20

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,33 @@ final class DefaultCamera: FLTCam, Camera {

/// Maximum number of frames pending processing.
/// To limit memory consumption, limit the number of frames pending processing.
/// After some testing, 4 was determined to be the best maximuńm value.
/// After some testing, 4 was determined to be the best maximum value.
/// https://github.com/flutter/plugins/pull/4520#discussion_r766335637
private var maxStreamingPendingFramesCount = 4

private var exposureMode = FCPPlatformExposureMode.auto
private var focusMode = FCPPlatformFocusMode.auto

func reportInitializationState() {
// Get all the state on the current thread, not the main thread.
let state = FCPPlatformCameraState.make(
withPreviewSize: FCPPlatformSize.make(
withWidth: Double(previewSize.width),
height: Double(previewSize.height)
),
exposureMode: exposureMode,
focusMode: focusMode,
exposurePointSupported: captureDevice.isExposurePointOfInterestSupported,
focusPointSupported: captureDevice.isFocusPointOfInterestSupported
)

FLTEnsureToRunOnMainQueue { [weak self] in
self?.dartAPI?.initialized(with: state) { _ in
// Ignore any errors, as this is just an event broadcast.
}
}
}

func receivedImageStreamData() {
streamingPendingFramesCount -= 1
}
Expand All @@ -41,6 +64,141 @@ final class DefaultCamera: FLTCam, Camera {
audioCaptureSession.stopRunning()
}

func setExposureMode(_ mode: FCPPlatformExposureMode) {
exposureMode = mode
applyExposureMode()
}

private func applyExposureMode() {
try? captureDevice.lockForConfiguration()
switch exposureMode {
case .locked:
// AVCaptureExposureMode.autoExpose automatically adjusts the exposure one time, and then locks exposure for the device
captureDevice.setExposureMode(.autoExpose)
case .auto:
if captureDevice.isExposureModeSupported(.continuousAutoExposure) {
captureDevice.setExposureMode(.continuousAutoExposure)
} else {
captureDevice.setExposureMode(.autoExpose)
}
@unknown default:
assertionFailure("Unknown exposure mode")
}
captureDevice.unlockForConfiguration()
}

func setExposureOffset(_ offset: Double) {
try? captureDevice.lockForConfiguration()
captureDevice.setExposureTargetBias(Float(offset), completionHandler: nil)
captureDevice.unlockForConfiguration()
}

func setExposurePoint(
_ point: FCPPlatformPoint?, withCompletion completion: @escaping (FlutterError?) -> Void
) {
guard captureDevice.isExposurePointOfInterestSupported else {
completion(
FlutterError(
code: "setExposurePointFailed",
message: "Device does not have exposure point capabilities",
details: nil))
return
}

let orientation = UIDevice.current.orientation
try? captureDevice.lockForConfiguration()
// A nil point resets to the center.
let exposurePoint = cgPoint(
for: point ?? FCPPlatformPoint.makeWith(x: 0.5, y: 0.5), withOrientation: orientation)
captureDevice.setExposurePointOfInterest(exposurePoint)
captureDevice.unlockForConfiguration()
// Retrigger auto exposure
applyExposureMode()
completion(nil)
}

func setFocusMode(_ mode: FCPPlatformFocusMode) {
focusMode = mode
applyFocusMode()
}

func setFocusPoint(_ point: FCPPlatformPoint?, completion: @escaping (FlutterError?) -> Void) {
guard captureDevice.isFocusPointOfInterestSupported else {
completion(
FlutterError(
code: "setFocusPointFailed",
message: "Device does not have focus point capabilities",
details: nil))
return
}

let orientation = deviceOrientationProvider.orientation()
try? captureDevice.lockForConfiguration()
// A nil point resets to the center.
captureDevice.setFocusPointOfInterest(
cgPoint(
for: point ?? FCPPlatformPoint.makeWith(x: 0.5, y: 0.5),
withOrientation: orientation)
)
captureDevice.unlockForConfiguration()
// Retrigger auto focus
applyFocusMode()
completion(nil)
}

private func applyFocusMode() {
applyFocusMode(focusMode, onDevice: captureDevice)
}

private func applyFocusMode(
_ focusMode: FCPPlatformFocusMode, onDevice captureDevice: FLTCaptureDevice
) {
try? captureDevice.lockForConfiguration()
switch focusMode {
case .locked:
// AVCaptureFocusMode.autoFocus automatically adjusts the focus one time, and then locks focus
if captureDevice.isFocusModeSupported(.autoFocus) {
captureDevice.setFocusMode(.autoFocus)
}
case .auto:
if captureDevice.isFocusModeSupported(.continuousAutoFocus) {
captureDevice.setFocusMode(.continuousAutoFocus)
} else if captureDevice.isFocusModeSupported(.autoFocus) {
captureDevice.setFocusMode(.autoFocus)
}
@unknown default:
assertionFailure("Unknown focus mode")
}
captureDevice.unlockForConfiguration()
}

private func cgPoint(
for point: FCPPlatformPoint, withOrientation orientation: UIDeviceOrientation
)
-> CGPoint
{
var x = point.x
var y = point.y
switch orientation {
case .portrait: // 90 ccw
y = 1 - point.x
x = point.y
case .portraitUpsideDown: // 90 cw
x = 1 - point.y
y = point.x
case .landscapeRight: // 180
x = 1 - point.x
y = 1 - point.y
case .landscapeLeft:
// No rotation required
break
default:
// No rotation required
break
}
return CGPoint(x: x, y: y)
}

func captureOutput(
_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ @interface FLTCam () <AVCaptureVideoDataOutputSampleBufferDelegate,
@property(nonatomic, copy) CaptureDeviceFactory captureDeviceFactory;
@property(nonatomic, copy) AudioCaptureDeviceFactory audioCaptureDeviceFactory;
@property(readonly, nonatomic) NSObject<FLTCaptureDeviceInputFactory> *captureDeviceInputFactory;
@property(assign, nonatomic) FCPPlatformExposureMode exposureMode;
@property(assign, nonatomic) FCPPlatformFocusMode focusMode;
@property(assign, nonatomic) FCPPlatformFlashMode flashMode;
@property(readonly, nonatomic) NSObject<FLTDeviceOrientationProviding> *deviceOrientationProvider;
@property(nonatomic, copy) AssetWriterFactory assetWriterFactory;
@property(nonatomic, copy) InputPixelBufferAdaptorFactory inputPixelBufferAdaptorFactory;
/// Reports the given error message to the Dart side of the plugin.
Expand Down Expand Up @@ -90,8 +87,6 @@ - (instancetype)initWithConfiguration:(nonnull FLTCamConfiguration *)configurati
_captureDeviceInputFactory = configuration.captureDeviceInputFactory;
_videoDimensionsForFormat = configuration.videoDimensionsForFormat;
_flashMode = _captureDevice.hasFlash ? FCPPlatformFlashModeAuto : FCPPlatformFlashModeOff;
_exposureMode = FCPPlatformExposureModeAuto;
_focusMode = FCPPlatformFocusModeAuto;
_lockedCaptureOrientation = UIDeviceOrientationUnknown;
_deviceOrientation = configuration.orientation;
_videoFormat = kCVPixelFormatType_32BGRA;
Expand Down Expand Up @@ -201,25 +196,6 @@ - (AVCaptureConnection *)createConnection:(NSError **)error {
return connection;
}

- (void)reportInitializationState {
// Get all the state on the current thread, not the main thread.
FCPPlatformCameraState *state = [FCPPlatformCameraState
makeWithPreviewSize:[FCPPlatformSize makeWithWidth:self.previewSize.width
height:self.previewSize.height]
exposureMode:self.exposureMode
focusMode:self.focusMode
exposurePointSupported:self.captureDevice.exposurePointOfInterestSupported
focusPointSupported:self.captureDevice.focusPointOfInterestSupported];

__weak typeof(self) weakSelf = self;
FLTEnsureToRunOnMainQueue(^{
[weakSelf.dartAPI initializedWithState:state
completion:^(FlutterError *error){
// Ignore any errors, as this is just an event broadcast.
}];
});
}

- (void)setVideoFormat:(OSType)videoFormat {
_videoFormat = videoFormat;
_captureVideoOutput.videoSettings =
Expand Down Expand Up @@ -619,60 +595,6 @@ - (void)setFlashMode:(FCPPlatformFlashMode)mode
completion(nil);
}

- (void)setExposureMode:(FCPPlatformExposureMode)mode {
_exposureMode = mode;
[self applyExposureMode];
}

- (void)applyExposureMode {
[_captureDevice lockForConfiguration:nil];
switch (self.exposureMode) {
case FCPPlatformExposureModeLocked:
// AVCaptureExposureModeAutoExpose automatically adjusts the exposure one time, and then
// locks exposure for the device
[_captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
break;
case FCPPlatformExposureModeAuto:
if ([_captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
[_captureDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
} else {
[_captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
}
break;
}
[_captureDevice unlockForConfiguration];
}

- (void)setFocusMode:(FCPPlatformFocusMode)mode {
_focusMode = mode;
[self applyFocusMode];
}

- (void)applyFocusMode {
[self applyFocusMode:_focusMode onDevice:_captureDevice];
}

- (void)applyFocusMode:(FCPPlatformFocusMode)focusMode
onDevice:(NSObject<FLTCaptureDevice> *)captureDevice {
[captureDevice lockForConfiguration:nil];
switch (focusMode) {
case FCPPlatformFocusModeLocked:
// AVCaptureFocusModeAutoFocus automatically adjusts the focus one time, and then locks focus
if ([captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
[captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
}
break;
case FCPPlatformFocusModeAuto:
if ([captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
[captureDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
} else if ([captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
[captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
}
break;
}
[captureDevice unlockForConfiguration];
}

- (void)pausePreview {
_isPreviewPaused = true;
}
Expand Down Expand Up @@ -736,80 +658,6 @@ - (void)setDescriptionWhileRecording:(NSString *)cameraName
completion(nil);
}

- (CGPoint)CGPointForPoint:(nonnull FCPPlatformPoint *)point
withOrientation:(UIDeviceOrientation)orientation {
double x = point.x;
double y = point.y;
switch (orientation) {
case UIDeviceOrientationPortrait: // 90 ccw
y = 1 - point.x;
x = point.y;
break;
case UIDeviceOrientationPortraitUpsideDown: // 90 cw
x = 1 - point.y;
y = point.x;
break;
case UIDeviceOrientationLandscapeRight: // 180
x = 1 - point.x;
y = 1 - point.y;
break;
case UIDeviceOrientationLandscapeLeft:
default:
// No rotation required
break;
}
return CGPointMake(x, y);
}

- (void)setExposurePoint:(FCPPlatformPoint *)point
withCompletion:(void (^)(FlutterError *_Nullable))completion {
if (!_captureDevice.exposurePointOfInterestSupported) {
completion([FlutterError errorWithCode:@"setExposurePointFailed"
message:@"Device does not have exposure point capabilities"
details:nil]);
return;
}
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
[_captureDevice lockForConfiguration:nil];
// A nil point resets to the center.
[_captureDevice
setExposurePointOfInterest:[self CGPointForPoint:(point
?: [FCPPlatformPoint makeWithX:0.5
y:0.5])
withOrientation:orientation]];
[_captureDevice unlockForConfiguration];
// Retrigger auto exposure
[self applyExposureMode];
completion(nil);
}

- (void)setFocusPoint:(FCPPlatformPoint *)point
withCompletion:(void (^)(FlutterError *_Nullable))completion {
if (!_captureDevice.focusPointOfInterestSupported) {
completion([FlutterError errorWithCode:@"setFocusPointFailed"
message:@"Device does not have focus point capabilities"
details:nil]);
return;
}
UIDeviceOrientation orientation = [_deviceOrientationProvider orientation];
[_captureDevice lockForConfiguration:nil];
// A nil point resets to the center.
[_captureDevice
setFocusPointOfInterest:[self
CGPointForPoint:(point ?: [FCPPlatformPoint makeWithX:0.5 y:0.5])
withOrientation:orientation]];
[_captureDevice unlockForConfiguration];
// Retrigger auto focus
[self applyFocusMode];
completion(nil);
}

- (void)setExposureOffset:(double)offset {
[_captureDevice lockForConfiguration:nil];
[_captureDevice setExposureTargetBias:offset completionHandler:nil];
[_captureDevice unlockForConfiguration];
}

- (void)startImageStreamWithMessenger:(NSObject<FlutterBinaryMessenger> *)messenger
completion:(void (^)(FlutterError *))completion {
[self startImageStreamWithMessenger:messenger
Expand Down
Loading