diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index abc280db2ad2..c057802fe75b 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.9.20+2 + +* Migrates exposure offset and zoom factor limit getters to Swift. +* Migrates `setImageFileFormat` method to Swift. +* Migrates pause and resume methods to Swift. +* Migrates capture orientation locking methods to Swift. +* Converts `setDeviceOrientation` method to property setter and migrated to Swift. + ## 0.9.20+1 * Migrates lifecycle methods (`start`, `stop`, `close`) to Swift. diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetDeviceOrientationTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetDeviceOrientationTests.swift index cd8d5d858a86..d7fbb505b7ea 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetDeviceOrientationTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSetDeviceOrientationTests.swift @@ -51,7 +51,7 @@ final class FLTCamSetDeviceOrientationTests: XCTestCase { videoSetVideoOrientationCalled = true } - camera.setDeviceOrientation(.landscapeLeft) + camera.deviceOrientation = .landscapeLeft XCTAssertTrue(photoSetVideoOrientationCalled) XCTAssertTrue(videoSetVideoOrientationCalled) @@ -75,7 +75,7 @@ final class FLTCamSetDeviceOrientationTests: XCTestCase { camera.lockCaptureOrientation(FCPPlatformDeviceOrientation.portraitDown) - camera.setDeviceOrientation(.landscapeLeft) + camera.deviceOrientation = .landscapeLeft XCTAssertTrue(photoSetVideoOrientationCalled) XCTAssertTrue(videoSetVideoOrientationCalled) @@ -89,7 +89,7 @@ final class FLTCamSetDeviceOrientationTests: XCTestCase { mockPhotoCaptureConnection.setVideoOrientationStub = { _ in XCTFail() } mockVideoCaptureConnection.setVideoOrientationStub = { _ in XCTFail() } - camera.setDeviceOrientation(.landscapeLeft) + camera.deviceOrientation = .landscapeLeft } func testSetDeviceOrientation_doesNotSetOrientations_forDuplicateUpdates() { @@ -104,8 +104,8 @@ final class FLTCamSetDeviceOrientationTests: XCTestCase { videoSetVideoOrientationCallCount += 1 } - camera.setDeviceOrientation(.landscapeRight) - camera.setDeviceOrientation(.landscapeRight) + camera.deviceOrientation = .landscapeRight + camera.deviceOrientation = .landscapeRight XCTAssertEqual(photoSetVideoOrientationCallCount, 1) XCTAssertEqual(videoSetVideoOrientationCallCount, 1) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift index 7cb2dd6dccdc..1996b785fb63 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCamera.swift @@ -65,6 +65,15 @@ final class MockCamera: NSObject, Camera { var isPreviewPaused: Bool = false var isStreamingImages: Bool = false + var deviceOrientation: UIDeviceOrientation { + get { + preconditionFailure("Attempted to access unimplemented property: deviceOrientation") + } + set { + setDeviceOrientationStub?(newValue) + } + } + var minimumExposureOffset: CGFloat { return getMinimumExposureOffsetStub?() ?? 0 } @@ -120,10 +129,6 @@ final class MockCamera: NSObject, Camera { captureToFileStub?(completion) } - func setDeviceOrientation(_ orientation: UIDeviceOrientation) { - setDeviceOrientationStub?(orientation) - } - func lockCaptureOrientation(_ orientation: FCPPlatformDeviceOrientation) { lockCaptureOrientationStub?(orientation) } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift index b890f2ddaa98..1acb3e2a90ce 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Camera.swift @@ -27,6 +27,8 @@ protocol Camera: FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate, var isPreviewPaused: Bool { get } var isStreamingImages: Bool { get } + var deviceOrientation: UIDeviceOrientation { get set } + var minimumAvailableZoomFactor: CGFloat { get } var maximumAvailableZoomFactor: CGFloat { get } var minimumExposureOffset: CGFloat { get } @@ -61,7 +63,6 @@ protocol Camera: FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate, func captureToFile(completion: @escaping (_ path: String?, _ error: FlutterError?) -> Void) - func setDeviceOrientation(_ orientation: UIDeviceOrientation) func lockCaptureOrientation(_ orientation: FCPPlatformDeviceOrientation) func unlockCaptureOrientation() diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift index d3c3c81275fd..1297256d9c39 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift @@ -103,8 +103,8 @@ public final class CameraPlugin: NSObject, FlutterPlugin { self.captureSessionQueue.async { [weak self] in guard let strongSelf = self else { return } - // `FLTCam.setDeviceOrientation` must be called on capture session queue. - strongSelf.camera?.setDeviceOrientation(orientation) + // `Camera.deviceOrientation` must be set on capture session queue. + strongSelf.camera?.deviceOrientation = orientation // `CameraPlugin.sendDeviceOrientation` can be called on any queue. strongSelf.sendDeviceOrientation(orientation) } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift index 109a7f768145..4ebe0efec87f 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift @@ -10,6 +10,21 @@ import CoreMotion #endif final class DefaultCamera: FLTCam, Camera { + override var deviceOrientation: UIDeviceOrientation { + get { super.deviceOrientation } + set { + guard newValue != super.deviceOrientation else { return } + + super.deviceOrientation = newValue + updateOrientation() + } + } + + var minimumExposureOffset: CGFloat { CGFloat(captureDevice.minExposureTargetBias) } + var maximumExposureOffset: CGFloat { CGFloat(captureDevice.maxExposureTargetBias) } + var minimumAvailableZoomFactor: CGFloat { captureDevice.minAvailableVideoZoomFactor } + var maximumAvailableZoomFactor: CGFloat { captureDevice.maxAvailableVideoZoomFactor } + /// The queue on which `latestPixelBuffer` property is accessed. /// To avoid unnecessary contention, do not access `latestPixelBuffer` on the `captureSessionQueue`. private let pixelBufferSynchronizationQueue = DispatchQueue( @@ -64,6 +79,33 @@ final class DefaultCamera: FLTCam, Camera { audioCaptureSession.stopRunning() } + func pauseVideoRecording() { + isRecordingPaused = true + videoIsDisconnected = true + audioIsDisconnected = true + } + + func resumeVideoRecording() { + isRecordingPaused = false + } + + func lockCaptureOrientation(_ pigeonOrientation: FCPPlatformDeviceOrientation) { + let orientation = FCPGetUIDeviceOrientationForPigeonDeviceOrientation(pigeonOrientation) + if lockedCaptureOrientation != orientation { + lockedCaptureOrientation = orientation + updateOrientation() + } + } + + func unlockCaptureOrientation() { + lockedCaptureOrientation = .unknown + updateOrientation() + } + + func setImageFileFormat(_ fileFormat: FCPPlatformImageFileFormat) { + self.fileFormat = fileFormat + } + func setExposureMode(_ mode: FCPPlatformExposureMode) { exposureMode = mode applyExposureMode() @@ -199,6 +241,14 @@ final class DefaultCamera: FLTCam, Camera { return CGPoint(x: x, y: y) } + func pausePreview() { + isPreviewPaused = true + } + + func resumePreview() { + isPreviewPaused = false + } + func captureOutput( _ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/FLTCam.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/FLTCam.m index ce7ec49118be..c44b6e184ed3 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/FLTCam.m @@ -42,14 +42,12 @@ @interface FLTCam () *videoCaptureSession; @property(readonly, nonatomic) NSObject *audioCaptureSession; @property(readonly, nonatomic) NSObject *deviceOrientationProvider; +@property(assign, nonatomic) UIDeviceOrientation lockedCaptureOrientation; +@property(assign, nonatomic) UIDeviceOrientation deviceOrientation; /// Initializes an `FLTCam` instance with the given configuration. /// @param error report to the caller if any error happened creating the camera. - (instancetype)initWithConfiguration:(FLTCamConfiguration *)configuration error:(NSError **)error; -- (void)setDeviceOrientation:(UIDeviceOrientation)orientation; - (void)captureToFileWithCompletion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; -- (void)setImageFileFormat:(FCPPlatformImageFileFormat)fileFormat; /// Starts recording a video with an optional streaming messenger. /// If the messenger is non-nil then it will be called for each /// captured frame, allowing streaming concurrently with recording. @@ -70,16 +66,9 @@ NS_ASSUME_NONNULL_BEGIN messengerForStreaming:(nullable NSObject *)messenger; - (void)stopVideoRecordingWithCompletion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; -- (void)pauseVideoRecording; -- (void)resumeVideoRecording; -- (void)lockCaptureOrientation:(FCPPlatformDeviceOrientation)orientation - NS_SWIFT_NAME(lockCaptureOrientation(_:)); -- (void)unlockCaptureOrientation; - (void)setFlashMode:(FCPPlatformFlashMode)mode withCompletion:(void (^)(FlutterError *_Nullable))completion; -- (void)pausePreview; -- (void)resumePreview; - (void)setDescriptionWhileRecording:(NSString *)cameraName withCompletion:(void (^)(FlutterError *_Nullable))completion; @@ -89,6 +78,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)setZoomLevel:(CGFloat)zoom withCompletion:(void (^)(FlutterError *_Nullable))completion; - (void)setUpCaptureSessionForAudioIfNeeded; +// Methods exposed for the Swift DefaultCamera subclass +- (void)updateOrientation; + @end NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index c4a77a5a9a8b..779bc7fb09fc 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.20+1 +version: 0.9.20+2 environment: sdk: ^3.6.0