Skip to content

[camera] Fix iOS torch mode regression #7085

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
4 changes: 4 additions & 0 deletions packages/camera/camera_avfoundation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:@[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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]];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 2 additions & 3 deletions packages/camera/camera_avfoundation/ios/Classes/FLTCam.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_avfoundation/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down