diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index d4712a196760..f46478505b3a 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.26+4 + +* Removes outdated restrictions against concurrent camera use cases. + ## 0.6.26+3 * Bumps kotlin_version to 2.3.0. diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index 5cf163e19a35..b3abec3cd6de 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -18,22 +18,6 @@ should add it to your `pubspec.yaml` as usual. ## Limitations -### Concurrent preview display, video recording, image capture, and image streaming - -The CameraX plugin only supports the concurrent camera use cases supported by Camerax; see -[their documentation][6] for more information. To avoid the usage of unsupported concurrent -use cases, the plugin behaves according to the following: - -* If the preview is paused (via `pausePreview`), concurrent video recording and image capture - and/or image streaming (via `startVideoCapturing(cameraId, VideoCaptureOptions(streamCallback:...))`) - is supported. -* If the preview is not paused - * **and** the camera device is at least supported hardware [`LIMITED`][8], then concurrent - image capture and video recording is supported. - * **and** the camera device is at least supported hardware [`LEVEL_3`][7], then concurrent - video recording and image streaming is supported, but concurrent video recording, image - streaming, and image capture is not supported. - ### 240p resolution configuration for video recording 240p resolution configuration for video recording is unsupported by CameraX, and thus, diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 136f62084e06..e500308d8c9d 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -1122,47 +1122,12 @@ class AndroidCameraCameraX extends CameraPlatform { // There is currently an active recording, so do not start a new one. return; } - - dynamic Function(CameraImageData)? streamCallback = options.streamCallback; - if (!_previewIsPaused) { - // The plugin binds the preview use case to the camera lifecycle when - // createCamera is called, but camera use cases can become limited - // when video recording and displaying a preview concurrently. This logic - // will prioritize attempting to continue displaying the preview, - // stream images, and record video if specified and supported. Otherwise, - // the preview must be paused in order to allow those concurrently. See - // https://developer.android.com/media/camera/camerax/architecture#combine-use-cases - // for more information on supported concurrent camera use cases. - final camera2CameraInfo = Camera2CameraInfo.from(cameraInfo: cameraInfo!); - final cameraInfoSupportedHardwareLevel = - (await camera2CameraInfo.getCameraCharacteristic( - CameraCharacteristics.infoSupportedHardwareLevel, - ))! - as InfoSupportedHardwareLevel; - - // Handle limited level device restrictions: - final cameraSupportsConcurrentImageCapture = - cameraInfoSupportedHardwareLevel != InfoSupportedHardwareLevel.legacy; - if (!cameraSupportsConcurrentImageCapture) { - // Concurrent preview + video recording + image capture is not supported - // unless the camera device is cameraSupportsHardwareLevelLimited or - // better. - await _unbindUseCaseFromLifecycle(imageCapture!); - } - - // Handle level 3 device restrictions: - final cameraSupportsHardwareLevel3 = - cameraInfoSupportedHardwareLevel == InfoSupportedHardwareLevel.level3; - if (!cameraSupportsHardwareLevel3 || streamCallback == null) { - // Concurrent preview + video recording + image streaming is not supported - // unless the camera device is cameraSupportsHardwareLevel3 or better. - streamCallback = null; - await _unbindUseCaseFromLifecycle(imageAnalysis!); - } else { - // If image streaming concurrently with video recording, image capture - // is unsupported. - await _unbindUseCaseFromLifecycle(imageCapture!); - } + final dynamic Function(CameraImageData)? streamCallback = + options.streamCallback; + if (streamCallback == null) { + // ImageAnalysis is not universally supported with Preview + VideoCapture, + // so unbind ImageAnalysis if it is not in use. + await _unbindUseCaseFromLifecycle(imageAnalysis!); } await _bindUseCaseToLifecycle(videoCapture!, options.cameraId); diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 830322fdc33a..c2f80b425994 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.6.26+3 +version: 0.6.26+4 environment: sdk: ^3.9.0 diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 16ffabfd0e89..3ad7df68cd4f 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -6174,115 +6174,7 @@ void main() { ); test( - 'startVideoCapturing unbinds ImageAnalysis use case when camera device is not at least level 3, no image streaming callback is specified, and preview is not paused', - () async { - // Set up mocks and constants. - final camera = AndroidCameraCameraX(); - final mockPendingRecording = MockPendingRecording(); - final mockRecording = MockRecording(); - final mockCamera = MockCamera(); - final mockCameraInfo = MockCameraInfo(); - final mockCamera2CameraInfo = MockCamera2CameraInfo(); - - // Set directly for test versus calling createCamera. - camera.processCameraProvider = MockProcessCameraProvider(); - camera.recorder = MockRecorder(); - camera.videoCapture = MockVideoCapture(); - camera.cameraSelector = MockCameraSelector(); - camera.cameraInfo = MockCameraInfo(); - camera.imageAnalysis = MockImageAnalysis(); - camera.enableRecordingAudio = false; - - // Ignore setting target rotation for this test; tested seprately. - camera.captureOrientationLocked = true; - - // Tell plugin to create detached Observer when camera info updated. - const outputPath = '/temp/REC123.mp4'; - GenericsPigeonOverrides.observerNew = - ({required void Function(Observer, T) onChanged}) { - return Observer.detached(onChanged: onChanged); - }; - GenericsPigeonOverrides.observerNew = - ({required void Function(Observer, T) onChanged}) { - return Observer.detached(onChanged: onChanged); - }; - PigeonOverrides.camera2CameraInfo_from = - ({required dynamic cameraInfo}) => mockCamera2CameraInfo; - PigeonOverrides.systemServicesManager_new = - ({ - required void Function(SystemServicesManager, String) onCameraError, - }) { - final mockSystemServicesManager = MockSystemServicesManager(); - when( - mockSystemServicesManager.getTempFilePath( - camera.videoPrefix, - '.mp4', - ), - ).thenAnswer((_) async => outputPath); - return mockSystemServicesManager; - }; - PigeonOverrides.videoRecordEventListener_new = - ({ - required void Function(VideoRecordEventListener, VideoRecordEvent) - onEvent, - }) { - return VideoRecordEventListener.pigeon_detached(onEvent: onEvent); - }; - PigeonOverrides.cameraCharacteristics_infoSupportedHardwareLevel = - MockCameraCharacteristicsKey(); - - const cameraId = 7; - - // Mock method calls. - when( - camera.recorder!.prepareRecording(outputPath), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.withAudioEnabled(!camera.enableRecordingAudio), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.asPersistentRecording(), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.start(any), - ).thenAnswer((_) async => mockRecording); - when( - camera.processCameraProvider!.isBound(camera.videoCapture!), - ).thenAnswer((_) async => false); - when( - camera.processCameraProvider!.isBound(camera.imageAnalysis!), - ).thenAnswer((_) async => true); - when( - camera.processCameraProvider!.bindToLifecycle( - camera.cameraSelector!, - [camera.videoCapture!], - ), - ).thenAnswer((_) async => mockCamera); - when( - mockCamera.getCameraInfo(), - ).thenAnswer((_) => Future.value(mockCameraInfo)); - when( - mockCameraInfo.getCameraState(), - ).thenAnswer((_) async => MockLiveCameraState()); - when( - mockCamera2CameraInfo.getCameraCharacteristic(any), - ).thenAnswer((_) async => InfoSupportedHardwareLevel.full); - - // Simulate video recording being started so startVideoRecording completes. - AndroidCameraCameraX.videoRecordingEventStreamController.add( - VideoRecordEventStart.pigeon_detached(), - ); - - await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); - - verify( - camera.processCameraProvider!.unbind([camera.imageAnalysis!]), - ); - }, - ); - - test( - 'startVideoCapturing unbinds ImageAnalysis use case when image streaming callback not specified, camera device is level 3, and preview is not paused', + 'startVideoCapturing unbinds ImageAnalysis use case when image streaming callback not specified', () async { // Set up mocks and constants. final camera = AndroidCameraCameraX(); @@ -6385,455 +6277,6 @@ void main() { }, ); - test( - 'startVideoCapturing unbinds ImageAnalysis use case when image streaming callback is specified, camera device is not at least level 3, and preview is not paused', - () async { - // Set up mocks and constants. - final camera = AndroidCameraCameraX(); - final mockPendingRecording = MockPendingRecording(); - final mockRecording = MockRecording(); - final mockCamera = MockCamera(); - final mockCameraInfo = MockCameraInfo(); - final mockCamera2CameraInfo = MockCamera2CameraInfo(); - - // Set directly for test versus calling createCamera. - camera.processCameraProvider = MockProcessCameraProvider(); - camera.recorder = MockRecorder(); - camera.videoCapture = MockVideoCapture(); - camera.cameraSelector = MockCameraSelector(); - camera.cameraInfo = MockCameraInfo(); - camera.imageAnalysis = MockImageAnalysis(); - camera.enableRecordingAudio = false; - - // Ignore setting target rotation for this test; tested separately. - camera.captureOrientationLocked = true; - - // Tell plugin to create detached Observer when camera info updated. - const outputPath = '/temp/REC123.mp4'; - GenericsPigeonOverrides.observerNew = - ({required void Function(Observer, T) onChanged}) { - return Observer.detached(onChanged: onChanged); - }; - PigeonOverrides.camera2CameraInfo_from = - ({required dynamic cameraInfo}) => mockCamera2CameraInfo; - PigeonOverrides.systemServicesManager_new = - ({ - required void Function(SystemServicesManager, String) onCameraError, - }) { - final mockSystemServicesManager = MockSystemServicesManager(); - when( - mockSystemServicesManager.getTempFilePath( - camera.videoPrefix, - '.mp4', - ), - ).thenAnswer((_) async => outputPath); - return mockSystemServicesManager; - }; - PigeonOverrides.videoRecordEventListener_new = - ({ - required void Function(VideoRecordEventListener, VideoRecordEvent) - onEvent, - }) { - return VideoRecordEventListener.pigeon_detached(onEvent: onEvent); - }; - PigeonOverrides.cameraCharacteristics_infoSupportedHardwareLevel = - MockCameraCharacteristicsKey(); - - const cameraId = 87; - - // Mock method calls. - when( - camera.recorder!.prepareRecording(outputPath), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.withAudioEnabled(!camera.enableRecordingAudio), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.asPersistentRecording(), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.start(any), - ).thenAnswer((_) async => mockRecording); - when( - camera.processCameraProvider!.isBound(camera.videoCapture!), - ).thenAnswer((_) async => false); - when( - camera.processCameraProvider!.isBound(camera.imageAnalysis!), - ).thenAnswer((_) async => true); - when( - camera.processCameraProvider!.bindToLifecycle( - camera.cameraSelector!, - [camera.videoCapture!], - ), - ).thenAnswer((_) async => mockCamera); - when( - mockCamera.getCameraInfo(), - ).thenAnswer((_) => Future.value(mockCameraInfo)); - when( - mockCameraInfo.getCameraState(), - ).thenAnswer((_) async => MockLiveCameraState()); - when( - mockCamera2CameraInfo.getCameraCharacteristic(any), - ).thenAnswer((_) async => InfoSupportedHardwareLevel.external); - - // Simulate video recording being started so startVideoRecording completes. - AndroidCameraCameraX.videoRecordingEventStreamController.add( - VideoRecordEventStart.pigeon_detached(), - ); - - await camera.startVideoCapturing( - VideoCaptureOptions( - cameraId, - streamCallback: (CameraImageData image) {}, - ), - ); - verify( - camera.processCameraProvider!.unbind([camera.imageAnalysis!]), - ); - }, - ); - - test( - 'startVideoCapturing unbinds ImageCapture use case when image streaming callback is specified, camera device is at least level 3, and preview is not paused', - () async { - // Set up mocks and constants. - final camera = AndroidCameraCameraX(); - final mockPendingRecording = MockPendingRecording(); - final mockRecording = MockRecording(); - final mockCamera = MockCamera(); - final mockCameraInfo = MockCameraInfo(); - final mockCamera2CameraInfo = MockCamera2CameraInfo(); - - // Set directly for test versus calling createCamera. - camera.processCameraProvider = MockProcessCameraProvider(); - camera.recorder = MockRecorder(); - camera.videoCapture = MockVideoCapture(); - camera.cameraSelector = MockCameraSelector(); - camera.cameraInfo = MockCameraInfo(); - camera.imageAnalysis = MockImageAnalysis(); - camera.imageCapture = MockImageCapture(); - camera.enableRecordingAudio = true; - - // Ignore setting target rotation for this test; tested seprately. - camera.captureOrientationLocked = true; - - // Tell plugin to create detached Observer when camera info updated. - const outputPath = '/temp/REC123.mp4'; - PigeonOverrides.analyzer_new = - ({required void Function(Analyzer, ImageProxy) analyze}) { - return Analyzer.pigeon_detached(analyze: analyze); - }; - GenericsPigeonOverrides.observerNew = - ({required void Function(Observer, T) onChanged}) { - return Observer.detached(onChanged: onChanged); - }; - PigeonOverrides.camera2CameraInfo_from = - ({required dynamic cameraInfo}) => mockCamera2CameraInfo; - PigeonOverrides.systemServicesManager_new = - ({ - required void Function(SystemServicesManager, String) onCameraError, - }) { - final mockSystemServicesManager = MockSystemServicesManager(); - when( - mockSystemServicesManager.getTempFilePath( - camera.videoPrefix, - '.mp4', - ), - ).thenAnswer((_) async => outputPath); - return mockSystemServicesManager; - }; - PigeonOverrides.videoRecordEventListener_new = - ({ - required void Function(VideoRecordEventListener, VideoRecordEvent) - onEvent, - }) { - return VideoRecordEventListener.pigeon_detached(onEvent: onEvent); - }; - PigeonOverrides.cameraCharacteristics_infoSupportedHardwareLevel = - MockCameraCharacteristicsKey(); - - const cameraId = 107; - - // Mock method calls. - when( - camera.recorder!.prepareRecording(outputPath), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.withAudioEnabled(!camera.enableRecordingAudio), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.asPersistentRecording(), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.start(any), - ).thenAnswer((_) async => mockRecording); - when( - camera.processCameraProvider!.isBound(camera.videoCapture!), - ).thenAnswer((_) async => false); - when( - camera.processCameraProvider!.isBound(camera.imageCapture!), - ).thenAnswer((_) async => true); - when( - camera.processCameraProvider!.isBound(camera.imageAnalysis!), - ).thenAnswer((_) async => true); - when( - camera.processCameraProvider!.bindToLifecycle( - camera.cameraSelector!, - [camera.videoCapture!], - ), - ).thenAnswer((_) async => mockCamera); - when( - mockCamera.getCameraInfo(), - ).thenAnswer((_) => Future.value(mockCameraInfo)); - when( - mockCameraInfo.getCameraState(), - ).thenAnswer((_) async => MockLiveCameraState()); - when( - mockCamera2CameraInfo.getCameraCharacteristic(any), - ).thenAnswer((_) async => InfoSupportedHardwareLevel.level3); - - // Simulate video recording being started so startVideoRecording completes. - AndroidCameraCameraX.videoRecordingEventStreamController.add( - VideoRecordEventStart.pigeon_detached(), - ); - - await camera.startVideoCapturing( - VideoCaptureOptions( - cameraId, - streamCallback: (CameraImageData image) {}, - ), - ); - verify( - camera.processCameraProvider!.unbind([camera.imageCapture!]), - ); - }, - ); - - test( - 'startVideoCapturing does not unbind ImageCapture or ImageAnalysis use cases when preview is paused', - () async { - // Set up mocks and constants. - final camera = AndroidCameraCameraX(); - final mockPendingRecording = MockPendingRecording(); - final mockRecording = MockRecording(); - final mockCamera = MockCamera(); - final mockCameraInfo = MockCameraInfo(); - final mockCamera2CameraInfo = MockCamera2CameraInfo(); - - // Set directly for test versus calling createCamera. - camera.processCameraProvider = MockProcessCameraProvider(); - camera.recorder = MockRecorder(); - camera.videoCapture = MockVideoCapture(); - camera.cameraSelector = MockCameraSelector(); - camera.cameraInfo = MockCameraInfo(); - camera.imageAnalysis = MockImageAnalysis(); - camera.imageCapture = MockImageCapture(); - camera.preview = MockPreview(); - camera.enableRecordingAudio = false; - - // Ignore setting target rotation for this test; tested seprately. - camera.captureOrientationLocked = true; - - // Tell plugin to create detached Observer when camera info updated. - const outputPath = '/temp/REC123.mp4'; - PigeonOverrides.analyzer_new = - ({required void Function(Analyzer, ImageProxy) analyze}) { - return Analyzer.pigeon_detached(analyze: analyze); - }; - GenericsPigeonOverrides.observerNew = - ({required void Function(Observer, T) onChanged}) { - return Observer.detached(onChanged: onChanged); - }; - PigeonOverrides.camera2CameraInfo_from = - ({required dynamic cameraInfo}) => mockCamera2CameraInfo; - PigeonOverrides.systemServicesManager_new = - ({ - required void Function(SystemServicesManager, String) onCameraError, - }) { - final mockSystemServicesManager = MockSystemServicesManager(); - when( - mockSystemServicesManager.getTempFilePath( - camera.videoPrefix, - '.mp4', - ), - ).thenAnswer((_) async => outputPath); - return mockSystemServicesManager; - }; - PigeonOverrides.videoRecordEventListener_new = - ({ - required void Function(VideoRecordEventListener, VideoRecordEvent) - onEvent, - }) { - return VideoRecordEventListener.pigeon_detached(onEvent: onEvent); - }; - PigeonOverrides.cameraCharacteristics_infoSupportedHardwareLevel = - MockCameraCharacteristicsKey(); - - const cameraId = 97; - - // Mock method calls. - when( - camera.recorder!.prepareRecording(outputPath), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.withAudioEnabled(!camera.enableRecordingAudio), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.asPersistentRecording(), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.start(any), - ).thenAnswer((_) async => mockRecording); - when( - camera.processCameraProvider!.isBound(camera.videoCapture!), - ).thenAnswer((_) async => false); - when( - camera.processCameraProvider!.bindToLifecycle( - camera.cameraSelector!, - [camera.videoCapture!], - ), - ).thenAnswer((_) async => mockCamera); - when( - mockCamera.getCameraInfo(), - ).thenAnswer((_) => Future.value(mockCameraInfo)); - when( - mockCameraInfo.getCameraState(), - ).thenAnswer((_) async => MockLiveCameraState()); - - await camera.pausePreview(cameraId); - - // Simulate video recording being started so startVideoRecording completes. - AndroidCameraCameraX.videoRecordingEventStreamController.add( - VideoRecordEventStart.pigeon_detached(), - ); - - await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); - - verifyNever( - camera.processCameraProvider!.unbind([camera.imageCapture!]), - ); - verifyNever( - camera.processCameraProvider!.unbind([camera.imageAnalysis!]), - ); - }, - ); - - test( - 'startVideoCapturing unbinds ImageCapture and ImageAnalysis use cases when running on a legacy hardware device', - () async { - // Set up mocks and constants. - final camera = AndroidCameraCameraX(); - final mockPendingRecording = MockPendingRecording(); - final mockRecording = MockRecording(); - final mockCamera = MockCamera(); - final mockCameraInfo = MockCameraInfo(); - final mockCamera2CameraInfo = MockCamera2CameraInfo(); - - // Set directly for test versus calling createCamera. - camera.processCameraProvider = MockProcessCameraProvider(); - camera.recorder = MockRecorder(); - camera.videoCapture = MockVideoCapture(); - camera.cameraSelector = MockCameraSelector(); - camera.cameraInfo = MockCameraInfo(); - camera.imageAnalysis = MockImageAnalysis(); - camera.imageCapture = MockImageCapture(); - camera.preview = MockPreview(); - camera.enableRecordingAudio = true; - - // Ignore setting target rotation for this test; tested seprately. - camera.captureOrientationLocked = true; - - // Tell plugin to create detached Observer when camera info updated. - const outputPath = '/temp/REC123.mp4'; - PigeonOverrides.analyzer_new = - ({required void Function(Analyzer, ImageProxy) analyze}) { - return Analyzer.pigeon_detached(analyze: analyze); - }; - GenericsPigeonOverrides.observerNew = - ({required void Function(Observer, T) onChanged}) { - return Observer.detached(onChanged: onChanged); - }; - PigeonOverrides.camera2CameraInfo_from = - ({required dynamic cameraInfo}) => mockCamera2CameraInfo; - PigeonOverrides.systemServicesManager_new = - ({ - required void Function(SystemServicesManager, String) onCameraError, - }) { - final mockSystemServicesManager = MockSystemServicesManager(); - when( - mockSystemServicesManager.getTempFilePath( - camera.videoPrefix, - '.mp4', - ), - ).thenAnswer((_) async => outputPath); - return mockSystemServicesManager; - }; - PigeonOverrides.videoRecordEventListener_new = - ({ - required void Function(VideoRecordEventListener, VideoRecordEvent) - onEvent, - }) { - return VideoRecordEventListener.pigeon_detached(onEvent: onEvent); - }; - PigeonOverrides.cameraCharacteristics_infoSupportedHardwareLevel = - MockCameraCharacteristicsKey(); - - const cameraId = 44; - - // Mock method calls. - when( - camera.recorder!.prepareRecording(outputPath), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.withAudioEnabled(!camera.enableRecordingAudio), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.asPersistentRecording(), - ).thenAnswer((_) async => mockPendingRecording); - when( - mockPendingRecording.start(any), - ).thenAnswer((_) async => mockRecording); - when( - camera.processCameraProvider!.isBound(camera.videoCapture!), - ).thenAnswer((_) async => false); - when( - camera.processCameraProvider!.isBound(camera.imageCapture!), - ).thenAnswer((_) async => true); - when( - camera.processCameraProvider!.isBound(camera.imageAnalysis!), - ).thenAnswer((_) async => true); - when( - camera.processCameraProvider!.bindToLifecycle( - camera.cameraSelector!, - [camera.videoCapture!], - ), - ).thenAnswer((_) async => mockCamera); - when( - mockCamera.getCameraInfo(), - ).thenAnswer((_) => Future.value(mockCameraInfo)); - when( - mockCameraInfo.getCameraState(), - ).thenAnswer((_) async => MockLiveCameraState()); - when( - mockCamera2CameraInfo.getCameraCharacteristic(any), - ).thenAnswer((_) async => InfoSupportedHardwareLevel.legacy); - - // Simulate video recording being started so startVideoRecording completes. - AndroidCameraCameraX.videoRecordingEventStreamController.add( - VideoRecordEventStart.pigeon_detached(), - ); - - await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); - - verify( - camera.processCameraProvider!.unbind([camera.imageCapture!]), - ); - verify( - camera.processCameraProvider!.unbind([camera.imageAnalysis!]), - ); - }, - ); - test( 'prepareForVideoRecording does not make any calls involving starting video recording', () async {