diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 113f2583fb29..3a8e703e08fa 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.18+15 + +* Migrates the CameraPlugin class to Swift. + ## 0.9.18+14 * Creates Swift Package Manager target for Swift implementation. diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index f58349a9d99b..7d6ca0e5068b 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -36,7 +36,6 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97DB234D2D566D0700CEFE66 /* CameraPreviewPauseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DB234C2D566D0700CEFE66 /* CameraPreviewPauseTests.swift */; }; - E0CDBAC227CD9729002561D9 /* QueueTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* QueueTestUtils.m */; }; E11D6A8F2D81B81D0031E6C5 /* MockCaptureVideoDataOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D6A8E2D81B81D0031E6C5 /* MockCaptureVideoDataOutput.swift */; }; E11D6A912D82C7740031E6C5 /* FLTCamExposureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D6A902D82C7740031E6C5 /* FLTCamExposureTests.swift */; }; E12C4FF62D68C69000515E70 /* CameraPluginDelegatingMethodTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12C4FF52D68C69000515E70 /* CameraPluginDelegatingMethodTests.swift */; }; @@ -131,8 +130,6 @@ 9DDC4CE84A8B378AE4A8CD9C /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A8F314CD1C64E9257EBC811D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; B61D98BBC8FB276D1C4A7BB2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - E0CDBAC027CD9729002561D9 /* QueueTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QueueTestUtils.h; sourceTree = ""; }; - E0CDBAC127CD9729002561D9 /* QueueTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueueTestUtils.m; sourceTree = ""; }; E11D6A8E2D81B81D0031E6C5 /* MockCaptureVideoDataOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCaptureVideoDataOutput.swift; sourceTree = ""; }; E11D6A902D82C7740031E6C5 /* FLTCamExposureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLTCamExposureTests.swift; sourceTree = ""; }; E12C4FF52D68C69000515E70 /* CameraPluginDelegatingMethodTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPluginDelegatingMethodTests.swift; sourceTree = ""; }; @@ -147,12 +144,12 @@ E142F13F2D85AD7900824824 /* MockCaptureConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCaptureConnection.swift; sourceTree = ""; }; E142F1412D85AFA400824824 /* MockGlobalEventApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGlobalEventApi.swift; sourceTree = ""; }; E15139172D80980900FEE47B /* FLTCamSetDeviceOrientationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLTCamSetDeviceOrientationTests.swift; sourceTree = ""; }; + E15BC7E32D86D08700F66474 /* MockFlutterTextureRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFlutterTextureRegistry.swift; sourceTree = ""; }; + E15BC7E52D86D17D00F66474 /* MockFlutterBinaryMessenger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFlutterBinaryMessenger.swift; sourceTree = ""; }; E15BC7E72D86D29F00F66474 /* MockAssetWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAssetWriter.swift; sourceTree = ""; }; E15BC7E92D86D41F00F66474 /* MockAssetWriterInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAssetWriterInput.swift; sourceTree = ""; }; E15BC7EB2D86D50200F66474 /* MockAssetWriterInputPixelBufferAdaptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAssetWriterInputPixelBufferAdaptor.swift; sourceTree = ""; }; E15BC7ED2D86D85500F66474 /* MockCaptureDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCaptureDevice.swift; sourceTree = ""; }; - E15BC7E32D86D08700F66474 /* MockFlutterTextureRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFlutterTextureRegistry.swift; sourceTree = ""; }; - E15BC7E52D86D17D00F66474 /* MockFlutterBinaryMessenger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFlutterBinaryMessenger.swift; sourceTree = ""; }; E16602942D8471C0003CFE12 /* FLTCamZoomTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLTCamZoomTests.swift; sourceTree = ""; }; E1A5F4E22D80259C0005BA64 /* FLTCamSetFlashModeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLTCamSetFlashModeTests.swift; sourceTree = ""; }; E1ABED702D943DC700AED9CC /* MockCaptureDeviceInputFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockCaptureDeviceInputFactory.swift; sourceTree = ""; }; @@ -190,8 +187,6 @@ children = ( 7F29EB3F2D281C6D00740257 /* Mocks */, 03BB766C2665316900CE5A93 /* Info.plist */, - E0CDBAC027CD9729002561D9 /* QueueTestUtils.h */, - E0CDBAC127CD9729002561D9 /* QueueTestUtils.m */, E142681E2D8566230046CBBC /* CameraTestUtils.swift */, 979B3DF92D5B6BA2009BDE1A /* ExceptionCatcher.h */, 979B3DFA2D5B6BC7009BDE1A /* ExceptionCatcher.m */, @@ -565,7 +560,6 @@ E15139182D80980900FEE47B /* FLTCamSetDeviceOrientationTests.swift in Sources */, 972CA92B2D5A1D8C004B846F /* CameraPropertiesTests.swift in Sources */, E15BC7E42D86D08700F66474 /* MockFlutterTextureRegistry.swift in Sources */, - E0CDBAC227CD9729002561D9 /* QueueTestUtils.m in Sources */, 978296CF2D5F744B0009BDD3 /* PhotoCaptureTests.swift in Sources */, 979B3E002D5B9E6C009BDE1A /* CameraMethodChannelTests.swift in Sources */, E142F13C2D8596F100824824 /* MockCaptureDeviceFormat.swift in Sources */, diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/QueueTestUtils.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/QueueTestUtils.h deleted file mode 100644 index 0d5d7ee6bd18..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/QueueTestUtils.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -@import Foundation; - -NS_ASSUME_NONNULL_BEGIN - -/// Calls `dispatch_queue_set_specific` with a key that is used to identify the queue. -/// This method is needed for comaptibility of Swift tests with Objective-C code. -/// In Swift, the API for settinng key-value pairs on a queue is different, so Swift tests -/// need to call this method to set the key-value pair on the queue in a way that's -/// compatible with the existing Objective-C code. -extern void FLTDispatchQueueSetSpecific(dispatch_queue_t queue, const void *key); - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/QueueTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/QueueTestUtils.m deleted file mode 100644 index 32198b7f461c..000000000000 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/QueueTestUtils.m +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "QueueTestUtils.h" - -@import camera_avfoundation; - -void FLTDispatchQueueSetSpecific(dispatch_queue_t queue, const void *key) { - dispatch_queue_set_specific(queue, key, (void *)key, NULL); -} diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h index d7eaeeac4e1f..83c9c862be13 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h @@ -3,4 +3,3 @@ // found in the LICENSE file. #import "ExceptionCatcher.h" -#import "QueueTestUtils.h" 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 new file mode 100644 index 000000000000..b46787b0f419 --- /dev/null +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.swift @@ -0,0 +1,520 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import ObjectiveC + +// Import Objectice-C part of the implementation when SwiftPM is used. +#if canImport(camera_avfoundation_objc) + import camera_avfoundation_objc +#endif + +public typealias CaptureNamedDeviceFactory = (String) -> FLTCaptureDevice + +public final class CameraPlugin: NSObject, FlutterPlugin, FCPCameraApi { + let registry: FlutterTextureRegistry + let messenger: FlutterBinaryMessenger + let globalEventAPI: FCPCameraGlobalEventApi + let deviceDiscoverer: FLTCameraDeviceDiscovering + let permissionManager: FLTCameraPermissionManager + let captureDeviceFactory: CaptureNamedDeviceFactory + let captureSessionFactory: CaptureSessionFactory + let captureDeviceInputFactory: FLTCaptureDeviceInputFactory + + /// All FLTCam's state access and capture session related operations should be on run on this queue. + var captureSessionQueue: DispatchQueue + + /// An internal camera object that manages camera's state and performs camera operations. + var camera: FLTCam? + + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = CameraPlugin(registry: registrar.textures(), messenger: registrar.messenger()) + + SetUpFCPCameraApi(registrar.messenger(), instance) + } + + convenience init(registry: FlutterTextureRegistry, messenger: FlutterBinaryMessenger) { + self.init( + registry: registry, + messenger: messenger, + globalAPI: FCPCameraGlobalEventApi(binaryMessenger: messenger), + deviceDiscoverer: FLTDefaultCameraDeviceDiscoverer(), + permissionManager: FLTCameraPermissionManager( + permissionService: FLTDefaultPermissionService()), + deviceFactory: { name in + FLTDefaultCaptureDevice(device: AVCaptureDevice(uniqueID: name)!) + }, + captureSessionFactory: { FLTDefaultCaptureSession(captureSession: AVCaptureSession()) }, + captureDeviceInputFactory: FLTDefaultCaptureDeviceInputFactory() + ) + } + + public init( + registry: FlutterTextureRegistry, + messenger: FlutterBinaryMessenger, + globalAPI: FCPCameraGlobalEventApi, + deviceDiscoverer: FLTCameraDeviceDiscovering, + permissionManager: FLTCameraPermissionManager, + deviceFactory: @escaping CaptureNamedDeviceFactory, + captureSessionFactory: @escaping CaptureSessionFactory, + captureDeviceInputFactory: FLTCaptureDeviceInputFactory + ) { + self.registry = registry + self.messenger = messenger + self.globalEventAPI = globalAPI + self.deviceDiscoverer = deviceDiscoverer + self.permissionManager = permissionManager + self.captureDeviceFactory = deviceFactory + self.captureSessionFactory = captureSessionFactory + self.captureDeviceInputFactory = captureDeviceInputFactory + + self.captureSessionQueue = DispatchQueue(label: "io.flutter.camera.captureSessionQueue") + + super.init() + + FLTDispatchQueueSetSpecific(captureSessionQueue, FLTCaptureSessionQueueSpecific) + + UIDevice.current.beginGeneratingDeviceOrientationNotifications() + NotificationCenter.default.addObserver( + forName: UIDevice.orientationDidChangeNotification, + object: UIDevice.current, + queue: .main + ) { [weak self] notification in + self?.orientationChanged(notification) + } + } + + public func detachFromEngine(for registrar: FlutterPluginRegistrar) { + UIDevice.current.endGeneratingDeviceOrientationNotifications() + } + + static func flutterErrorFromNSError(_ error: NSError) -> FlutterError { + return FlutterError( + code: "Error \(error.code)", message: error.localizedDescription, details: error.domain) + } + + func orientationChanged(_ notification: Notification) { + guard let device = notification.object as? UIDevice else { return } + let orientation = device.orientation + + if orientation == .faceUp || orientation == .faceDown { + // Do not change when oriented flat. + return + } + + self.captureSessionQueue.async { [weak self] in + // `FLTCam.setDeviceOrientation` must be called on capture session queue. + self?.camera?.setDeviceOrientation(orientation) + // `CameraPlugin.sendDeviceOrientation` can be called on any queue. + self?.sendDeviceOrientation(orientation) + } + } + + func sendDeviceOrientation(_ orientation: UIDeviceOrientation) { + DispatchQueue.main.async { [weak self] in + self?.globalEventAPI.deviceOrientationChangedOrientation( + FCPGetPigeonDeviceOrientationForOrientation(orientation) + ) { _ in + // Ignore errors; this is essentially a broadcast stream, and + // it's fine if the other end doesn't receive the message + // (e.g., if it doesn't currently have a listener set up). + } + } + } + + public func availableCameras( + completion: @escaping ([FCPPlatformCameraDescription]?, FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + guard let self = self else { return } + + var discoveryDevices: [AVCaptureDevice.DeviceType] = [ + .builtInWideAngleCamera, + .builtInTelephotoCamera, + ] + + if #available(iOS 13.0, *) { + discoveryDevices.append(.builtInUltraWideCamera) + } + + let devices = self.deviceDiscoverer.discoverySession( + withDeviceTypes: discoveryDevices, + mediaType: .video, + position: .unspecified) + + var reply: [FCPPlatformCameraDescription] = [] + + for device in devices { + var lensFacing: FCPPlatformCameraLensDirection + + switch device.position { + case .back: + lensFacing = .back + case .front: + lensFacing = .front + case .unspecified: + lensFacing = .external + @unknown default: + lensFacing = .external + } + + let cameraDescription = FCPPlatformCameraDescription.make( + withName: device.uniqueID, + lensDirection: lensFacing + ) + reply.append(cameraDescription) + } + + completion(reply, nil) + } + } + + public func createCamera( + withName cameraName: String, + settings: FCPPlatformMediaSettings, + completion: @escaping (NSNumber?, FlutterError?) -> Void + ) { + // Create FLTCam only if granted camera access (and audio access if audio is enabled) + captureSessionQueue.async { [weak self] in + self?.permissionManager.requestCameraPermission { error in + guard let strongSelf = self else { return } + + if let error = error { + completion(nil, error) + } else { + // Request audio permission on `create` call with `enableAudio` argument instead of the + // `prepareForVideoRecording` call. This is because `prepareForVideoRecording` call is + // optional, and used as a workaround to fix a missing frame issue on iOS. + if settings.enableAudio { + // Setup audio capture session only if granted audio access. + strongSelf.permissionManager.requestAudioPermission { [weak self] audioError in + // cannot use the outter `strongSelf` + guard let strongSelf = self else { return } + + if let audioError = audioError { + completion(nil, audioError) + } else { + strongSelf.createCameraOnSessionQueue( + withName: cameraName, + settings: settings, + completion: completion) + } + } + } else { + strongSelf.createCameraOnSessionQueue( + withName: cameraName, + settings: settings, + completion: completion) + } + } + } + } + } + + public func createCameraOnSessionQueue( + withName: String, settings: FCPPlatformMediaSettings, + completion: @escaping (NSNumber?, FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.sessionQueueCreateCamera(name: withName, settings: settings, completion: completion) + } + } + + // This must be called on captureSessionQueue. It is extracted from + // initializeCamera:withImageFormat:completion: to make it easier to reason about strong/weak + // self pointers. + private func sessionQueueCreateCamera( + name: String, + settings: FCPPlatformMediaSettings, + completion: @escaping (NSNumber?, FlutterError?) -> Void + ) { + let mediaSettingsAVWrapper = FLTCamMediaSettingsAVWrapper() + + let camConfiguration = FLTCamConfiguration( + mediaSettings: settings, + mediaSettingsWrapper: mediaSettingsAVWrapper, + captureDeviceFactory: { [self] in + self.captureDeviceFactory(name) + }, + captureSessionFactory: captureSessionFactory, + captureSessionQueue: captureSessionQueue, + captureDeviceInputFactory: captureDeviceInputFactory + ) + + var error: NSError? + let newCamera = FLTCam(configuration: camConfiguration, error: &error) + + if let error = error { + completion(nil, CameraPlugin.flutterErrorFromNSError(error)) + } else { + camera?.close() + camera = newCamera + + FLTEnsureToRunOnMainQueue { [weak self] in + guard let self = self else { return } + completion(NSNumber(value: self.registry.register(newCamera)), nil) + } + } + } + + public func initializeCamera( + _ cameraId: Int, + withImageFormat imageFormat: FCPPlatformImageFormatGroup, + completion: @escaping (FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.sessionQueueInitializeCamera( + cameraId, + withImageFormat: imageFormat, + completion: completion) + } + } + + // This must be called on captureSessionQueue. It is extracted from + // initializeCamera:withImageFormat:completion: to make it easier to reason about strong/weak + // self pointers. + private func sessionQueueInitializeCamera( + _ cameraId: Int, + withImageFormat imageFormat: FCPPlatformImageFormatGroup, + completion: @escaping (FlutterError?) -> Void + ) { + camera?.videoFormat = FCPGetPixelFormatForPigeonFormat(imageFormat) + + camera?.onFrameAvailable = { [weak self] in + if !(self?.camera?.isPreviewPaused ?? true) { + FLTEnsureToRunOnMainQueue { [weak self] in + self?.registry.textureFrameAvailable(Int64(cameraId)) + } + } + } + + camera?.dartAPI = FCPCameraEventApi( + binaryMessenger: messenger, + messageChannelSuffix: "\(cameraId)" + ) + + camera?.reportInitializationState() + sendDeviceOrientation(UIDevice.current.orientation) + camera?.start() + completion(nil) + } + + public func startImageStream(completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + guard let self = self else { return } + self.camera?.startImageStream(with: self.messenger) + completion(nil) + } + } + + public func stopImageStream(completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.stopImageStream() + completion(nil) + } + } + + public func receivedImageStreamData(completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.receivedImageStreamData() + completion(nil) + } + } + + public func disposeCamera(_ cameraId: Int, completion: @escaping (FlutterError?) -> Void) { + registry.unregisterTexture(Int64(cameraId)) + captureSessionQueue.async { [weak self] in + self?.camera?.close() + self?.camera = nil + completion(nil) + } + } + + public func lockCapture( + _ orientation: FCPPlatformDeviceOrientation, + completion: @escaping (FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.camera?.lockCapture(orientation) + completion(nil) + } + } + + public func unlockCaptureOrientation(completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.unlockCaptureOrientation() + completion(nil) + } + } + + public func takePicture(completion: @escaping (String?, FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.captureToFile(completion: completion) + } + } + + public func prepareForVideoRecording(completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.setUpCaptureSessionForAudioIfNeeded() + completion(nil) + } + } + + public func startVideoRecording( + withStreaming enableStream: Bool, completion: @escaping (FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + guard let self = self else { return } + self.camera?.startVideoRecording( + completion: completion, + messengerForStreaming: enableStream ? self.messenger : nil) + } + } + + public func stopVideoRecording(completion: @escaping (String?, FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.stopVideoRecording(completion: completion) + } + } + + public func pauseVideoRecording(completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.pauseVideoRecording() + completion(nil) + } + } + + public func resumeVideoRecording(completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.resumeVideoRecording() + completion(nil) + } + } + + public func setFlashMode( + _ mode: FCPPlatformFlashMode, completion: @escaping (FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.camera?.setFlashMode(mode, withCompletion: completion) + } + } + + public func setExposureMode( + _ mode: FCPPlatformExposureMode, completion: @escaping (FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.camera?.setExposureMode(mode) + completion(nil) + } + } + + public func setExposurePoint( + _ point: FCPPlatformPoint?, completion: @escaping (FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.camera?.setExposurePoint(point, withCompletion: completion) + } + } + + public func getMinimumExposureOffset(_ completion: @escaping (NSNumber?, FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + if let minOffset = self?.camera?.minimumExposureOffset { + completion(NSNumber(value: minOffset), nil) + } else { + completion(nil, nil) + } + } + } + + public func getMaximumExposureOffset(_ completion: @escaping (NSNumber?, FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + if let maxOffset = self?.camera?.maximumExposureOffset { + completion(NSNumber(value: maxOffset), nil) + } else { + completion(nil, nil) + } + } + } + + public func setExposureOffset(_ offset: Double, completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.setExposureOffset(offset) + completion(nil) + } + } + + public func setFocusMode( + _ mode: FCPPlatformFocusMode, completion: @escaping (FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.camera?.setFocusMode(mode) + completion(nil) + } + } + + public func setFocus(_ point: FCPPlatformPoint?, completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.setFocusPoint(point, completion: completion) + } + } + + public func getMinimumZoomLevel(_ completion: @escaping (NSNumber?, FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + if let minZoom = self?.camera?.minimumAvailableZoomFactor { + completion(NSNumber(value: minZoom), nil) + } else { + completion(nil, nil) + } + } + } + + public func getMaximumZoomLevel(_ completion: @escaping (NSNumber?, FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + if let maxZoom = self?.camera?.maximumAvailableZoomFactor { + completion(NSNumber(value: maxZoom), nil) + } else { + completion(nil, nil) + } + } + } + + public func setZoomLevel(_ zoom: Double, completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.setZoomLevel(zoom, withCompletion: completion) + } + } + + public func pausePreview(completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.pausePreview() + completion(nil) + } + } + + public func resumePreview(completion: @escaping (FlutterError?) -> Void) { + captureSessionQueue.async { [weak self] in + self?.camera?.resumePreview() + completion(nil) + } + } + + public func updateDescriptionWhileRecordingCameraName( + _ cameraName: String, + completion: @escaping (FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.camera?.setDescriptionWhileRecording(cameraName, withCompletion: completion) + } + } + + public func setImageFileFormat( + _ format: FCPPlatformImageFileFormat, + completion: @escaping (FlutterError?) -> Void + ) { + captureSessionQueue.async { [weak self] in + self?.camera?.setImageFileFormat(format) + completion(nil) + } + } +} diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Placeholder.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Placeholder.swift deleted file mode 100644 index e7217c7d7b3f..000000000000 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/Placeholder.swift +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/CameraPlugin.m deleted file mode 100644 index b9e1ef1850c3..000000000000 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/CameraPlugin.m +++ /dev/null @@ -1,529 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "./include/camera_avfoundation/CameraPlugin.h" -#import "./include/camera_avfoundation/CameraPlugin_Test.h" - -@import AVFoundation; -@import Flutter; - -#import "./include/camera_avfoundation/CameraProperties.h" -#import "./include/camera_avfoundation/FLTCam.h" -#import "./include/camera_avfoundation/FLTCameraDeviceDiscovering.h" -#import "./include/camera_avfoundation/FLTCameraPermissionManager.h" -#import "./include/camera_avfoundation/FLTThreadSafeEventChannel.h" -#import "./include/camera_avfoundation/QueueUtils.h" -#import "./include/camera_avfoundation/messages.g.h" - -static FlutterError *FlutterErrorFromNSError(NSError *error) { - return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] - message:error.localizedDescription - details:error.domain]; -} - -@interface CameraPlugin () -@property(readonly, nonatomic) NSObject *registry; -@property(readonly, nonatomic) NSObject *messenger; -@property(nonatomic) FCPCameraGlobalEventApi *globalEventAPI; -@property(readonly, nonatomic) FLTCameraPermissionManager *permissionManager; -@property(readonly, nonatomic) NSObject *deviceDiscoverer; -@property(readonly, nonatomic) CaptureNamedDeviceFactory captureDeviceFactory; -@property(readonly, nonatomic) CaptureSessionFactory captureSessionFactory; -@property(readonly, nonatomic) NSObject *captureDeviceInputFactory; -@end - -@implementation CameraPlugin - -+ (void)registerWithRegistrar:(NSObject *)registrar { - CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] - messenger:[registrar messenger]]; - SetUpFCPCameraApi([registrar messenger], instance); -} - -- (instancetype)initWithRegistry:(NSObject *)registry - messenger:(NSObject *)messenger { - id permissionService = [[FLTDefaultPermissionService alloc] init]; - - return [self initWithRegistry:registry - messenger:messenger - globalAPI:[[FCPCameraGlobalEventApi alloc] initWithBinaryMessenger:messenger] - deviceDiscoverer:[[FLTDefaultCameraDeviceDiscoverer alloc] init] - permissionManager:[[FLTCameraPermissionManager alloc] - initWithPermissionService:permissionService] - deviceFactory:^NSObject *(NSString *name) { - return [[FLTDefaultCaptureDevice alloc] - initWithDevice:[AVCaptureDevice deviceWithUniqueID:name]]; - } - captureSessionFactory:^NSObject *(void) { - return [[FLTDefaultCaptureSession alloc] - initWithCaptureSession:[[AVCaptureSession alloc] init]]; - } - captureDeviceInputFactory:[[FLTDefaultCaptureDeviceInputFactory alloc] init]]; -} - -- (instancetype)initWithRegistry:(NSObject *)registry - messenger:(NSObject *)messenger - globalAPI:(FCPCameraGlobalEventApi *)globalAPI - deviceDiscoverer:(NSObject *)deviceDiscoverer - permissionManager:(FLTCameraPermissionManager *)permissionManager - deviceFactory:(CaptureNamedDeviceFactory)deviceFactory - captureSessionFactory:(CaptureSessionFactory)captureSessionFactory - captureDeviceInputFactory: - (NSObject *)captureDeviceInputFactory { - self = [super init]; - NSAssert(self, @"super init cannot be nil"); - _registry = registry; - _messenger = messenger; - _globalEventAPI = globalAPI; - _captureSessionQueue = dispatch_queue_create("io.flutter.camera.captureSessionQueue", NULL); - _deviceDiscoverer = deviceDiscoverer; - _permissionManager = permissionManager; - _captureDeviceFactory = deviceFactory; - _captureSessionFactory = captureSessionFactory; - _captureDeviceInputFactory = captureDeviceInputFactory; - - dispatch_queue_set_specific(_captureSessionQueue, FLTCaptureSessionQueueSpecific, - (void *)FLTCaptureSessionQueueSpecific, NULL); - - [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(orientationChanged:) - name:UIDeviceOrientationDidChangeNotification - object:[UIDevice currentDevice]]; - return self; -} - -- (void)detachFromEngineForRegistrar:(NSObject *)registrar { - [UIDevice.currentDevice endGeneratingDeviceOrientationNotifications]; -} - -- (void)orientationChanged:(NSNotification *)note { - UIDevice *device = note.object; - UIDeviceOrientation orientation = device.orientation; - - if (orientation == UIDeviceOrientationFaceUp || orientation == UIDeviceOrientationFaceDown) { - // Do not change when oriented flat. - return; - } - - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - // `FLTCam::setDeviceOrientation` must be called on capture session queue. - [weakSelf.camera setDeviceOrientation:orientation]; - // `CameraPlugin::sendDeviceOrientation` can be called on any queue. - [weakSelf sendDeviceOrientation:orientation]; - }); -} - -- (void)sendDeviceOrientation:(UIDeviceOrientation)orientation { - __weak typeof(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - [weakSelf.globalEventAPI - deviceOrientationChangedOrientation:FCPGetPigeonDeviceOrientationForOrientation(orientation) - completion:^(FlutterError *error){ - // Ignore errors; this is essentially a broadcast stream, and - // it's fine if the other end - // doesn't receive the message (e.g., if it doesn't currently - // have a listener set up). - }]; - }); -} - -#pragma mark FCPCameraApi Implementation - -- (void)availableCamerasWithCompletion: - (nonnull void (^)(NSArray *_Nullable, - FlutterError *_Nullable))completion { - dispatch_async(self.captureSessionQueue, ^{ - NSMutableArray *discoveryDevices = - [@[ AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera ] - mutableCopy]; - if (@available(iOS 13.0, *)) { - [discoveryDevices addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera]; - } - NSArray *> *devices = - [self.deviceDiscoverer discoverySessionWithDeviceTypes:discoveryDevices - mediaType:AVMediaTypeVideo - position:AVCaptureDevicePositionUnspecified]; - NSMutableArray *reply = - [[NSMutableArray alloc] initWithCapacity:devices.count]; - for (NSObject *device in devices) { - FCPPlatformCameraLensDirection lensFacing; - switch (device.position) { - case AVCaptureDevicePositionBack: - lensFacing = FCPPlatformCameraLensDirectionBack; - break; - case AVCaptureDevicePositionFront: - lensFacing = FCPPlatformCameraLensDirectionFront; - break; - case AVCaptureDevicePositionUnspecified: - lensFacing = FCPPlatformCameraLensDirectionExternal; - break; - } - [reply addObject:[FCPPlatformCameraDescription makeWithName:device.uniqueID - lensDirection:lensFacing]]; - } - completion(reply, nil); - }); -} - -- (void)createCameraWithName:(nonnull NSString *)cameraName - settings:(nonnull FCPPlatformMediaSettings *)settings - completion: - (nonnull void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { - // Create FLTCam only if granted camera access (and audio access if audio is enabled) - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [self->_permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { - typeof(self) strongSelf = weakSelf; - if (!strongSelf) return; - - if (error) { - completion(nil, error); - } else { - // Request audio permission on `create` call with `enableAudio` argument instead of the - // `prepareForVideoRecording` call. This is because `prepareForVideoRecording` call is - // optional, and used as a workaround to fix a missing frame issue on iOS. - if (settings.enableAudio) { - // Setup audio capture session only if granted audio access. - [self->_permissionManager - requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { - // cannot use the outter `strongSelf` - typeof(self) strongSelf = weakSelf; - if (!strongSelf) return; - if (error) { - completion(nil, error); - } else { - [strongSelf createCameraOnSessionQueueWithName:cameraName - settings:settings - completion:completion]; - } - }]; - } else { - [strongSelf createCameraOnSessionQueueWithName:cameraName - settings:settings - completion:completion]; - } - } - }]; - }); -} - -- (void)initializeCamera:(NSInteger)cameraId - withImageFormat:(FCPPlatformImageFormatGroup)imageFormat - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf sessionQueueInitializeCamera:cameraId - withImageFormat:imageFormat - completion:completion]; - }); -} - -- (void)startImageStreamWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera startImageStreamWithMessenger:weakSelf.messenger]; - completion(nil); - }); -} - -- (void)stopImageStreamWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera stopImageStream]; - completion(nil); - }); -} - -- (void)receivedImageStreamDataWithCompletion: - (nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera receivedImageStreamData]; - completion(nil); - }); -} - -- (void)takePictureWithCompletion:(nonnull void (^)(NSString *_Nullable, - FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera captureToFileWithCompletion:completion]; - }); -} - -- (void)prepareForVideoRecordingWithCompletion: - (nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setUpCaptureSessionForAudioIfNeeded]; - completion(nil); - }); -} - -- (void)startVideoRecordingWithStreaming:(BOOL)enableStream - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - typeof(self) strongSelf = weakSelf; - if (!strongSelf) return; - [strongSelf.camera - startVideoRecordingWithCompletion:completion - messengerForStreaming:(enableStream ? strongSelf.messenger : nil)]; - }); -} - -- (void)stopVideoRecordingWithCompletion:(nonnull void (^)(NSString *_Nullable, - FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera stopVideoRecordingWithCompletion:completion]; - }); -} - -- (void)pauseVideoRecordingWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera pauseVideoRecording]; - completion(nil); - }); -} - -- (void)resumeVideoRecordingWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera resumeVideoRecording]; - completion(nil); - }); -} - -- (void)getMinimumZoomLevel:(nonnull void (^)(NSNumber *_Nullable, - FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - completion(@(weakSelf.camera.minimumAvailableZoomFactor), nil); - }); -} - -- (void)getMaximumZoomLevel:(nonnull void (^)(NSNumber *_Nullable, - FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - completion(@(weakSelf.camera.maximumAvailableZoomFactor), nil); - }); -} - -- (void)setZoomLevel:(double)zoom completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setZoomLevel:zoom withCompletion:completion]; - }); -} - -- (void)setFlashMode:(FCPPlatformFlashMode)mode - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setFlashMode:mode withCompletion:completion]; - }); -} - -- (void)setExposureMode:(FCPPlatformExposureMode)mode - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setExposureMode:mode]; - completion(nil); - }); -} - -- (void)setExposurePoint:(nullable FCPPlatformPoint *)point - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setExposurePoint:point withCompletion:completion]; - }); -} - -- (void)getMinimumExposureOffset:(nonnull void (^)(NSNumber *_Nullable, - FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - completion(@(weakSelf.camera.minimumExposureOffset), nil); - }); -} - -- (void)getMaximumExposureOffset:(nonnull void (^)(NSNumber *_Nullable, - FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - completion(@(weakSelf.camera.maximumExposureOffset), nil); - }); -} - -- (void)setExposureOffset:(double)offset - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setExposureOffset:offset]; - completion(nil); - }); -} - -- (void)setFocusMode:(FCPPlatformFocusMode)mode - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setFocusMode:mode]; - completion(nil); - }); -} - -- (void)setFocusPoint:(nullable FCPPlatformPoint *)point - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setFocusPoint:point withCompletion:completion]; - }); -} - -- (void)lockCaptureOrientation:(FCPPlatformDeviceOrientation)orientation - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera lockCaptureOrientation:orientation]; - completion(nil); - }); -} - -- (void)unlockCaptureOrientationWithCompletion: - (nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera unlockCaptureOrientation]; - completion(nil); - }); -} - -- (void)pausePreviewWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera pausePreview]; - completion(nil); - }); -} - -- (void)resumePreviewWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera resumePreview]; - completion(nil); - }); -} - -- (void)setImageFileFormat:(FCPPlatformImageFileFormat)format - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setImageFileFormat:format]; - completion(nil); - }); -} - -- (void)updateDescriptionWhileRecordingCameraName:(nonnull NSString *)cameraName - completion: - (nonnull void (^)(FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera setDescriptionWhileRecording:cameraName withCompletion:completion]; - }); -} - -- (void)disposeCamera:(NSInteger)cameraId - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - [_registry unregisterTexture:cameraId]; - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf.camera close]; - weakSelf.camera = nil; - completion(nil); - }); -} - -#pragma mark Private - -// This must be called on captureSessionQueue. It is extracted from -// initializeCamera:withImageFormat:completion: to make it easier to reason about strong/weak -// self pointers. -- (void)sessionQueueInitializeCamera:(NSInteger)cameraId - withImageFormat:(FCPPlatformImageFormatGroup)imageFormat - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - [_camera setVideoFormat:FCPGetPixelFormatForPigeonFormat(imageFormat)]; - - __weak CameraPlugin *weakSelf = self; - _camera.onFrameAvailable = ^{ - typeof(self) strongSelf = weakSelf; - if (!strongSelf) return; - if (![strongSelf.camera isPreviewPaused]) { - FLTEnsureToRunOnMainQueue(^{ - [weakSelf.registry textureFrameAvailable:cameraId]; - }); - } - }; - _camera.dartAPI = [[FCPCameraEventApi alloc] - initWithBinaryMessenger:_messenger - messageChannelSuffix:[NSString stringWithFormat:@"%ld", cameraId]]; - [_camera reportInitializationState]; - [self sendDeviceOrientation:[UIDevice currentDevice].orientation]; - [_camera start]; - completion(nil); -} - -- (void)createCameraOnSessionQueueWithName:(NSString *)name - settings:(FCPPlatformMediaSettings *)settings - completion:(nonnull void (^)(NSNumber *_Nullable, - FlutterError *_Nullable))completion { - __weak typeof(self) weakSelf = self; - dispatch_async(self.captureSessionQueue, ^{ - [weakSelf sessionQueueCreateCameraWithName:name settings:settings completion:completion]; - }); -} - -// This must be called on captureSessionQueue. It is extracted from -// initializeCamera:withImageFormat:completion: to make it easier to reason about strong/weak -// self pointers. -- (void)sessionQueueCreateCameraWithName:(NSString *)name - settings:(FCPPlatformMediaSettings *)settings - completion:(nonnull void (^)(NSNumber *_Nullable, - FlutterError *_Nullable))completion { - FLTCamMediaSettingsAVWrapper *mediaSettingsAVWrapper = - [[FLTCamMediaSettingsAVWrapper alloc] init]; - - FLTCamConfiguration *camConfiguration = - [[FLTCamConfiguration alloc] initWithMediaSettings:settings - mediaSettingsWrapper:mediaSettingsAVWrapper - captureDeviceFactory:^NSObject *_Nonnull { - return self.captureDeviceFactory(name); - } - captureSessionFactory:_captureSessionFactory - captureSessionQueue:_captureSessionQueue - captureDeviceInputFactory:_captureDeviceInputFactory]; - - NSError *error; - FLTCam *cam = [[FLTCam alloc] initWithConfiguration:camConfiguration error:&error]; - - if (error) { - completion(nil, FlutterErrorFromNSError(error)); - } else { - [_camera close]; - _camera = cam; - __weak typeof(self) weakSelf = self; - FLTEnsureToRunOnMainQueue(^{ - completion(@([weakSelf.registry registerTexture:cam]), nil); - }); - } -} - -@end diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/QueueUtils.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/QueueUtils.m index 8ea83ede2b71..cb0895b5c2e8 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/QueueUtils.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/QueueUtils.m @@ -13,3 +13,7 @@ void FLTEnsureToRunOnMainQueue(dispatch_block_t block) { block(); } } + +void FLTDispatchQueueSetSpecific(dispatch_queue_t queue, const void *key) { + dispatch_queue_set_specific(queue, key, (void *)key, NULL); +} diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/CameraPlugin_Test.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/CameraPlugin_Test.h deleted file mode 100644 index dd15e3f3e6d5..000000000000 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/CameraPlugin_Test.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This header is available in the Test module. Import via "@import camera_avfoundation.Test;" - -#import "CameraPlugin.h" -#import "FLTCam.h" -#import "FLTCamConfiguration.h" -#import "FLTCameraDeviceDiscovering.h" -#import "FLTCameraPermissionManager.h" -#import "FLTCaptureDevice.h" -#import "messages.g.h" - -NS_ASSUME_NONNULL_BEGIN - -typedef NSObject *_Nonnull (^CaptureNamedDeviceFactory)(NSString *name); - -/// APIs exposed for unit testing. -@interface CameraPlugin () - -/// All FLTCam's state access and capture session related operations should be on run on this queue. -@property(nonatomic, strong) dispatch_queue_t captureSessionQueue; - -/// An internal camera object that manages camera's state and performs camera operations. -@property(nonatomic, strong) FLTCam *_Nullable camera; - -/// Inject @p FlutterTextureRegistry and @p FlutterBinaryMessenger for unit testing. -- (instancetype)initWithRegistry:(NSObject *)registry - messenger:(NSObject *)messenger; - -/// Inject @p FlutterTextureRegistry, @p FlutterBinaryMessenger, and Pigeon callback handler for -/// unit testing. -- (instancetype)initWithRegistry:(NSObject *)registry - messenger:(NSObject *)messenger - globalAPI:(FCPCameraGlobalEventApi *)globalAPI - deviceDiscoverer:(id)deviceDiscoverer - permissionManager:(FLTCameraPermissionManager *)permissionManager - deviceFactory:(CaptureNamedDeviceFactory)deviceFactory - captureSessionFactory:(CaptureSessionFactory)captureSessionFactory - captureDeviceInputFactory:(id)captureDeviceInputFactory - NS_DESIGNATED_INITIALIZER; - -/// Hide the default public constructor. -- (instancetype)init NS_UNAVAILABLE; - -/// Called by the @c NSNotificationManager each time the device's orientation is changed. -/// -/// @param notification @c NSNotification instance containing a reference to the `UIDevice` object -/// that triggered the orientation change. -- (void)orientationChanged:(NSNotification *)notification; - -/// Creates FLTCam on session queue and reports the creation result. -/// @param name the name of the camera. -/// @param settings the creation settings. -/// @param completion the callback to inform the Dart side of the plugin of creation. -- (void)createCameraOnSessionQueueWithName:(NSString *)name - settings:(FCPPlatformMediaSettings *)settings - completion:(void (^)(NSNumber *_Nullable, - FlutterError *_Nullable))completion; -@end - -NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/QueueUtils.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/QueueUtils.h index a7e22da716d0..bc8fc49840dd 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/QueueUtils.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/QueueUtils.h @@ -16,4 +16,13 @@ extern const char* FLTCaptureSessionQueueSpecific; /// @param block the block to be run on the main queue. extern void FLTEnsureToRunOnMainQueue(dispatch_block_t block); +/// Calls `dispatch_queue_set_specific` with a key that is used to identify the +/// queue. This method is needed for compatibility of Swift implementation with +/// Objective-C code. In Swift, the API for setting key-value pairs on a queue +/// is different, so Swift code need to call this method to set the key-value +/// pair on the queue in a way that's compatible with the existing Objective-C +/// code. +extern void FLTDispatchQueueSetSpecific(dispatch_queue_t queue, + const void* key); + NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/CameraPlugin.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/camera_avfoundation.h similarity index 50% rename from packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/CameraPlugin.h rename to packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/camera_avfoundation.h index 586b2fc87085..96ffe59f0eb6 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/CameraPlugin.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/camera_avfoundation.h @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import - +#import "FLTCam.h" +#import "FLTCamConfiguration.h" +#import "FLTCameraDeviceDiscovering.h" +#import "FLTCameraPermissionManager.h" +#import "FLTCaptureDevice.h" +#import "QueueUtils.h" #import "messages.g.h" - -@interface CameraPlugin : NSObject -@end diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 1d40fd04c635..4fa7d262479b 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.18+14 +version: 0.9.18+15 environment: sdk: ^3.4.0