From 404f85111c980b9e7a2db69cb2fe0a5476054b8c Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 9 Jul 2024 14:26:09 -0400 Subject: [PATCH 1/2] [camera] Fix iOS torch mode regression Fixes a regression from the Pigeon conversion, where converting from a Pigeon flash mode to an OS flash mode now fails instead of returning a magic sentinel value, so the logic to handle it needed to be adjusted, but wasn't in the conversion. Fixes https://github.com/flutter/flutter/issues/151453 --- .../camera/camera_avfoundation/CHANGELOG.md | 4 ++ .../ios/RunnerTests/CameraSettingsTests.m | 2 +- .../example/ios/RunnerTests/CameraTestUtils.h | 4 +- .../example/ios/RunnerTests/CameraTestUtils.m | 20 ++++--- .../ios/RunnerTests/FLTCamPhotoCaptureTests.m | 52 +++++++++++++++++++ .../camera_avfoundation/ios/Classes/FLTCam.m | 5 +- .../camera/camera_avfoundation/pubspec.yaml | 2 +- 7 files changed, 76 insertions(+), 13 deletions(-) diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 5012f7d965b..aaa1057e92d 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.16+1 + +* Fixes regression taking a picture in torch mode. + ## 0.9.16 * Converts Dart-to-host communcation to Pigeon. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m index 1962a6b7457..da8fe2647f7 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.m @@ -142,7 +142,7 @@ - (void)testSettings_shouldPassConfigurationToCameraDeviceAndWriter { [[TestMediaSettingsAVWrapper alloc] initWithTestCase:self]; FLTCam *camera = FLTCreateCamWithCaptureSessionQueueAndMediaSettings( - dispatch_queue_create("test", NULL), settings, injectedWrapper); + dispatch_queue_create("test", NULL), settings, injectedWrapper, nil); // Expect FPS configuration is passed to camera device. [self waitForExpectations:@[ diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h index eded154995e..295cbce3649 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h @@ -12,11 +12,13 @@ NS_ASSUME_NONNULL_BEGIN /// @param mediaSettings media settings configuration parameters /// @param mediaSettingsAVWrapper provider to perform media settings operations (for unit test /// dependency injection). +/// @param captureDeviceFactory a callback to create capture device instances /// @return an FLTCam object. extern FLTCam *_Nullable FLTCreateCamWithCaptureSessionQueueAndMediaSettings( dispatch_queue_t _Nullable captureSessionQueue, FCPPlatformMediaSettings *_Nullable mediaSettings, - FLTCamMediaSettingsAVWrapper *_Nullable mediaSettingsAVWrapper); + FLTCamMediaSettingsAVWrapper *_Nullable mediaSettingsAVWrapper, + CaptureDeviceFactory _Nullable captureDeviceFactory); extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue); diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 0dac5c4a59b..e1a3aaec702 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -18,12 +18,13 @@ } FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue) { - return FLTCreateCamWithCaptureSessionQueueAndMediaSettings(captureSessionQueue, nil, nil); + return FLTCreateCamWithCaptureSessionQueueAndMediaSettings(captureSessionQueue, nil, nil, nil); } FLTCam *FLTCreateCamWithCaptureSessionQueueAndMediaSettings( dispatch_queue_t captureSessionQueue, FCPPlatformMediaSettings *mediaSettings, - FLTCamMediaSettingsAVWrapper *mediaSettingsAVWrapper) { + FLTCamMediaSettingsAVWrapper *mediaSettingsAVWrapper, + CaptureDeviceFactory captureDeviceFactory) { if (!mediaSettings) { mediaSettings = FCPGetDefaultMediaSettings(FCPPlatformResolutionPresetMedium); } @@ -51,14 +52,19 @@ OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - id fltCam = [[FLTCam alloc] initWithCameraName:@"camera" - mediaSettings:mediaSettings - mediaSettingsAVWrapper:mediaSettingsAVWrapper - orientation:UIDeviceOrientationPortrait + id fltCam = [[FLTCam alloc] initWithMediaSettings:mediaSettings + mediaSettingsAVWrapper:mediaSettingsAVWrapper + orientation:UIDeviceOrientationPortrait videoCaptureSession:videoSessionMock audioCaptureSession:audioSessionMock captureSessionQueue:captureSessionQueue - error:nil]; + captureDeviceFactory:captureDeviceFactory ?: ^AVCaptureDevice *(void) { + return [AVCaptureDevice deviceWithUniqueID:@"camera"]; + } + videoDimensionsForFormat:^CMVideoDimensions(AVCaptureDeviceFormat *format) { + return CMVideoFormatDescriptionGetDimensions(format.formatDescription); + } + error:nil]; id captureVideoDataOutputMock = [OCMockObject niceMockForClass:[AVCaptureVideoDataOutput class]]; diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m index f81625f849f..20d0836ac7d 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m @@ -169,4 +169,56 @@ - (void)testCaptureToFile_mustReportFileExtensionWithJpgWhenHEVCNotAvailableAndF }); [self waitForExpectationsWithTimeout:1 handler:nil]; } + +- (void)testCaptureToFile_handlesTorchMode { + XCTestExpectation *pathExpectation = + [self expectationWithDescription: + @"Must send file path to result if save photo delegate completes with file path."]; + + id captureDeviceMock = OCMClassMock([AVCaptureDevice class]); + OCMStub([captureDeviceMock hasTorch]).andReturn(YES); + OCMStub([captureDeviceMock isTorchAvailable]).andReturn(YES); + OCMStub([captureDeviceMock torchMode]).andReturn(AVCaptureTorchModeAuto); + OCMExpect([captureDeviceMock setTorchMode:AVCaptureTorchModeOn]); + + dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); + dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, + (void *)FLTCaptureSessionQueueSpecific, NULL); + + FLTCam *cam = FLTCreateCamWithCaptureSessionQueueAndMediaSettings(captureSessionQueue, nil, nil, + ^AVCaptureDevice *(void) { + return captureDeviceMock; + }); + + AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings]; + id mockSettings = OCMClassMock([AVCapturePhotoSettings class]); + OCMStub([mockSettings photoSettings]).andReturn(settings); + + NSString *filePath = @"test"; + + id mockOutput = OCMClassMock([AVCapturePhotoOutput class]); + OCMStub([mockOutput capturePhotoWithSettings:OCMOCK_ANY delegate:OCMOCK_ANY]) + .andDo(^(NSInvocation *invocation) { + FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; + // Completion runs on IO queue. + dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); + dispatch_async(ioQueue, ^{ + delegate.completionHandler(filePath, nil); + }); + }); + cam.capturePhotoOutput = mockOutput; + + // `FLTCam::captureToFile` runs on capture session queue. + dispatch_async(captureSessionQueue, ^{ + [cam setFlashMode:FCPPlatformFlashModeTorch + withCompletion:^(FlutterError *_){ + }]; + [cam captureToFileWithCompletion:^(NSString *result, FlutterError *error) { + XCTAssertEqual(result, filePath); + [pathExpectation fulfill]; + }]; + }); + [self waitForExpectationsWithTimeout:1 handler:nil]; + OCMVerifyAll(captureDeviceMock); +} @end diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 45ab3e08e66..bf7166580f0 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -374,9 +374,8 @@ - (void)captureToFileWithCompletion:(void (^)(NSString *_Nullable, extension = @"jpg"; } - AVCaptureFlashMode avFlashMode = FCPGetAVCaptureFlashModeForPigeonFlashMode(_flashMode); - if (avFlashMode != -1) { - [settings setFlashMode:avFlashMode]; + if (_flashMode != FCPPlatformFlashModeTorch) { + [settings setFlashMode:FCPGetAVCaptureFlashModeForPigeonFlashMode(_flashMode)]; } NSError *error; NSString *path = [self getTemporaryFilePathWithExtension:extension diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index b91fe76ae3f..004ddcd9b51 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.16 +version: 0.9.16+1 environment: sdk: ^3.2.3 From 554efbd835b171d9b9198acba661c38b404190f8 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 9 Jul 2024 15:41:34 -0400 Subject: [PATCH 2/2] Add comment about condition --- packages/camera/camera_avfoundation/ios/Classes/FLTCam.m | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index 2b5f3584f69..42383a98d31 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -374,6 +374,7 @@ - (void)captureToFileWithCompletion:(void (^)(NSString *_Nullable, extension = @"jpg"; } + // If the flash is in torch mode, no capture-level flash setting is needed. if (_flashMode != FCPPlatformFlashModeTorch) { [settings setFlashMode:FCPGetAVCaptureFlashModeForPigeonFlashMode(_flashMode)]; }