diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 7d853bf524c..abc280db2ad 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.9.20+1 + +* 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 * Fixes incorrect types in image stream events. 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 30b0cb0bbe6..b890f2ddaa9 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 @@ -34,15 +34,23 @@ protocol Camera: FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate, func setUpCaptureSessionForAudioIfNeeded() + /// Informs the Dart side of the plugin of the current camera state and capabilities. func reportInitializationState() /// Acknowledges the receipt of one image stream frame. + /// + /// This should be called each time a frame is received. Failing to call it may + /// cause later frames to be dropped instead of streamed. func receivedImageStreamData() func start() func stop() /// 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. + /// + /// @param messenger Nullable messenger for capturing each frame. func startVideoRecording( completion: @escaping (_ error: FlutterError?) -> Void, messengerForStreaming: FlutterBinaryMessenger? @@ -61,12 +69,31 @@ protocol Camera: FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate, func setExposureMode(_ mode: FCPPlatformExposureMode) func setExposureOffset(_ offset: Double) + + /// Sets the exposure point, in a (0,1) coordinate system. + /// + /// If @c point is nil, the exposure point will reset to the center. func setExposurePoint( _ point: FCPPlatformPoint?, withCompletion: @escaping (_ error: FlutterError?) -> Void ) + /// Sets FocusMode on the current AVCaptureDevice. + /// + /// If the @c focusMode is set to FocusModeAuto the AVCaptureDevice is configured to use + /// AVCaptureFocusModeContinuousModeAutoFocus when supported, otherwise it is set to + /// AVCaptureFocusModeAutoFocus. If neither AVCaptureFocusModeContinuousModeAutoFocus nor + /// AVCaptureFocusModeAutoFocus are supported focus mode will not be set. + /// If @c focusMode is set to FocusModeLocked the AVCaptureDevice is configured to use + /// AVCaptureFocusModeAutoFocus. If AVCaptureFocusModeAutoFocus is not supported focus mode will not + /// be set. + /// + /// @param mode The focus mode that should be applied. func setFocusMode(_ mode: FCPPlatformFocusMode) + + /// Sets the focus point, in a (0,1) coordinate system. + /// + /// If @c point is nil, the focus point will reset to the center. func setFocusPoint( _ point: FCPPlatformPoint?, completion: @escaping (_ error: FlutterError?) -> Void 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 3c4e0b9bb37..109a7f76814 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 @@ -23,10 +23,182 @@ 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 + } + + func start() { + videoCaptureSession.startRunning() + audioCaptureSession.startRunning() + } + + func stop() { + videoCaptureSession.stopRunning() + 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 ?? .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, @@ -240,6 +412,22 @@ final class DefaultCamera: FLTCam, Camera { } } + func close() { + stop() + for input in videoCaptureSession.inputs { + videoCaptureSession.removeInput(FLTDefaultCaptureInput(input: input)) + } + for output in videoCaptureSession.outputs { + videoCaptureSession.removeOutput(output) + } + for input in audioCaptureSession.inputs { + audioCaptureSession.removeInput(FLTDefaultCaptureInput(input: input)) + } + for output in audioCaptureSession.outputs { + audioCaptureSession.removeOutput(output) + } + } + func copyPixelBuffer() -> Unmanaged? { var pixelBuffer: CVPixelBuffer? pixelBufferSynchronizationQueue.sync { 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 9e3dfda732e..ce7ec49118b 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 @@ -32,8 +32,6 @@ @interface FLTCam () *videoCaptureSession; -@property(readonly, nonatomic) NSObject *audioCaptureSession; @property(readonly, nonatomic) NSObject *captureVideoInput; @property(readonly, nonatomic) CGSize captureSize; @@ -59,10 +57,7 @@ @interface FLTCam () *captureDeviceInputFactory; -@property(assign, nonatomic) FCPPlatformExposureMode exposureMode; -@property(assign, nonatomic) FCPPlatformFocusMode focusMode; @property(assign, nonatomic) FCPPlatformFlashMode flashMode; -@property(readonly, nonatomic) NSObject *deviceOrientationProvider; @property(nonatomic, copy) AssetWriterFactory assetWriterFactory; @property(nonatomic, copy) InputPixelBufferAdaptorFactory inputPixelBufferAdaptorFactory; /// Reports the given error message to the Dart side of the plugin. @@ -92,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; @@ -203,35 +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)start { - [_videoCaptureSession startRunning]; - [_audioCaptureSession startRunning]; -} - -- (void)stop { - [_videoCaptureSession stopRunning]; - [_audioCaptureSession stopRunning]; -} - - (void)setVideoFormat:(OSType)videoFormat { _videoFormat = videoFormat; _captureVideoOutput.videoSettings = @@ -478,22 +442,6 @@ - (BOOL)setCaptureSessionPreset:(FCPPlatformResolutionPreset)resolutionPreset return bestFormat; } -- (void)close { - [self stop]; - for (AVCaptureInput *input in [_videoCaptureSession inputs]) { - [_videoCaptureSession removeInput:[[FLTDefaultCaptureInput alloc] initWithInput:input]]; - } - for (AVCaptureOutput *output in [_videoCaptureSession outputs]) { - [_videoCaptureSession removeOutput:output]; - } - for (AVCaptureInput *input in [_audioCaptureSession inputs]) { - [_audioCaptureSession removeInput:[[FLTDefaultCaptureInput alloc] initWithInput:input]]; - } - for (AVCaptureOutput *output in [_audioCaptureSession outputs]) { - [_audioCaptureSession removeOutput:output]; - } -} - - (void)dealloc { [_motionManager stopAccelerometerUpdates]; } @@ -647,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 *)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; } @@ -764,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 *)messenger completion:(void (^)(FlutterError *))completion { [self startImageStreamWithMessenger:messenger @@ -894,10 +714,6 @@ - (void)stopImageStream { } } -- (void)receivedImageStreamData { - self.streamingPendingFramesCount--; -} - - (void)setZoomLevel:(CGFloat)zoom withCompletion:(void (^)(FlutterError *_Nullable))completion { if (_captureDevice.maxAvailableVideoZoomFactor < zoom || _captureDevice.minAvailableVideoZoomFactor > zoom) { diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam.h index 6c19d22f9a7..12b22d615f8 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation_objc/include/camera_avfoundation/FLTCam.h @@ -49,19 +49,17 @@ NS_ASSUME_NONNULL_BEGIN @property(strong, nonatomic, nullable) NSObject *videoWriterInput; @property(strong, nonatomic, nullable) NSObject *audioWriterInput; @property(nullable) NSObject *videoAdaptor; +@property(readonly, nonatomic) NSObject *videoCaptureSession; +@property(readonly, nonatomic) NSObject *audioCaptureSession; +@property(readonly, nonatomic) NSObject *deviceOrientationProvider; /// 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; -/// Informs the Dart side of the plugin of the current camera state and capabilities. -- (void)reportInitializationState; -- (void)start; -- (void)stop; - (void)setDeviceOrientation:(UIDeviceOrientation)orientation; - (void)captureToFileWithCompletion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; -- (void)close; - (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 @@ -79,44 +77,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)unlockCaptureOrientation; - (void)setFlashMode:(FCPPlatformFlashMode)mode withCompletion:(void (^)(FlutterError *_Nullable))completion; -- (void)setExposureMode:(FCPPlatformExposureMode)mode; -/// Sets FocusMode on the current AVCaptureDevice. -/// -/// If the @c focusMode is set to FocusModeAuto the AVCaptureDevice is configured to use -/// AVCaptureFocusModeContinuousModeAutoFocus when supported, otherwise it is set to -/// AVCaptureFocusModeAutoFocus. If neither AVCaptureFocusModeContinuousModeAutoFocus nor -/// AVCaptureFocusModeAutoFocus are supported focus mode will not be set. -/// If @c focusMode is set to FocusModeLocked the AVCaptureDevice is configured to use -/// AVCaptureFocusModeAutoFocus. If AVCaptureFocusModeAutoFocus is not supported focus mode will not -/// be set. -/// -/// @param mode The focus mode that should be applied. -- (void)setFocusMode:(FCPPlatformFocusMode)mode; - -/// Acknowledges the receipt of one image stream frame. -/// -/// This should be called each time a frame is received. Failing to call it may -/// cause later frames to be dropped instead of streamed. -- (void)receivedImageStreamData; - (void)pausePreview; - (void)resumePreview; - (void)setDescriptionWhileRecording:(NSString *)cameraName withCompletion:(void (^)(FlutterError *_Nullable))completion; -/// Sets the exposure point, in a (0,1) coordinate system. -/// -/// If @c point is nil, the exposure point will reset to the center. -- (void)setExposurePoint:(nullable FCPPlatformPoint *)point - withCompletion:(void (^)(FlutterError *_Nullable))completion; - -/// Sets the focus point, in a (0,1) coordinate system. -/// -/// If @c point is nil, the focus point will reset to the center. -- (void)setFocusPoint:(nullable FCPPlatformPoint *)point - withCompletion:(void (^)(FlutterError *_Nullable))completion - NS_SWIFT_NAME(setFocusPoint(_:completion:)); -- (void)setExposureOffset:(double)offset; - (void)startImageStreamWithMessenger:(NSObject *)messenger completion:(nonnull void (^)(FlutterError *_Nullable))completion; - (void)stopImageStream; diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index f16e4785dd3..c4a77a5a9a8 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 +version: 0.9.20+1 environment: sdk: ^3.6.0