diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 61aff809f22c..6db99566c86d 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.0+18 + +* Implements `startVideoCapturing`. + ## 0.5.0+17 * Implements resolution configuration for all camera use cases. diff --git a/packages/camera/camera_android_camerax/README.md b/packages/camera/camera_android_camerax/README.md index 1642b8156904..63c0bf5bfc2b 100644 --- a/packages/camera/camera_android_camerax/README.md +++ b/packages/camera/camera_android_camerax/README.md @@ -51,10 +51,12 @@ Calling `setFlashMode` with mode `FlashMode.torch` currently does nothing. `setZoomLevel` is unimplemented. -### Some video capture functionality \[[Issue #127896][127896], [Issue #126477][126477]\] +### Setting maximum duration and stream options for video capture -`startVideoCapturing` is unimplemented; use `startVideoRecording` instead. -`onVideoRecordedEvent` is also unimplemented. +Calling `startVideoCapturing` with `VideoCaptureOptions` configured with +`maxVideoDuration` and `streamOptions` is currently unsupported do to the +limitations of the CameraX library and the platform interface, respectively, +and thus, those parameters will silently be ignored. ## Contributing 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 fd42cc695665..c915961208db 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 @@ -363,6 +363,12 @@ class AndroidCameraCameraX extends CameraPlatform { ]); } + /// The camera finished recording a video. + @override + Stream onVideoRecordedEvent(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + /// Gets the minimum supported exposure offset for the selected camera in EV units. /// /// [cameraId] not used. @@ -507,12 +513,23 @@ class AndroidCameraCameraX extends CameraPlatform { /// Note that the preset resolution is used to configure the recording, but /// 240p ([ResolutionPreset.low]) is unsupported and will fallback to /// configure the recording as the next highest available quality. + /// + /// This method is deprecated in favour of [startVideoCapturing]. @override Future startVideoRecording(int cameraId, {Duration? maxVideoDuration}) async { - assert(cameraSelector != null); - assert(processCameraProvider != null); + return startVideoCapturing( + VideoCaptureOptions(cameraId, maxDuration: maxVideoDuration)); + } + /// Starts a video recording and/or streaming session. + /// + /// Please see [VideoCaptureOptions] for documentation on the + /// configuration options. Currently, maxVideoDuration and streamOptions + /// are unsupported due to the limitations of CameraX and the platform + /// interface, respectively. + @override + Future startVideoCapturing(VideoCaptureOptions options) async { if (recording != null) { // There is currently an active recording, so do not start a new one. return; @@ -527,6 +544,10 @@ class AndroidCameraCameraX extends CameraPlatform { await SystemServices.getTempFilePath(videoPrefix, '.temp'); pendingRecording = await recorder!.prepareRecording(videoOutputPath!); recording = await pendingRecording!.start(); + + if (options.streamCallback != null) { + onStreamedFrameAvailable(options.cameraId).listen(options.streamCallback); + } } /// Stops the video recording and returns the file where it was saved. diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 54c895b4fa93..75b3c0390019 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.5.0+17 +version: 0.5.0+18 environment: sdk: ">=2.19.0 <4.0.0" @@ -19,7 +19,7 @@ flutter: dependencies: async: ^2.5.0 - camera_platform_interface: ^2.2.0 + camera_platform_interface: ^2.3.2 flutter: sdk: flutter integration_test: 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 bece9281bb8c..b90aabdd28dd 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 @@ -785,9 +785,9 @@ void main() { group('video recording', () { test( - 'startVideoRecording binds video capture use case and starts the recording', + 'startVideoCapturing binds video capture use case and starts the recording', () async { - //Set up mocks and constants. + // Set up mocks and constants. final FakeAndroidCameraCameraX camera = FakeAndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); camera.cameraSelector = MockCameraSelector(); @@ -815,7 +815,7 @@ void main() { camera.cameraSelector!, [camera.videoCapture!])) .thenAnswer((_) async => camera.camera!); - await camera.startVideoRecording(cameraId); + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); verify(camera.processCameraProvider!.bindToLifecycle( camera.cameraSelector!, [camera.videoCapture!])); @@ -824,9 +824,9 @@ void main() { }); test( - 'startVideoRecording binds video capture use case and starts the recording' + 'startVideoCapturing binds video capture use case and starts the recording' ' on first call, and does nothing on second call', () async { - //Set up mocks and constants. + // Set up mocks and constants. final FakeAndroidCameraCameraX camera = FakeAndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); camera.cameraSelector = MockCameraSelector(); @@ -854,14 +854,14 @@ void main() { camera.cameraSelector!, [camera.videoCapture!])) .thenAnswer((_) async => camera.camera!); - await camera.startVideoRecording(cameraId); + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); verify(camera.processCameraProvider!.bindToLifecycle( camera.cameraSelector!, [camera.videoCapture!])); expect(camera.pendingRecording, equals(mockPendingRecording)); expect(camera.recording, mockRecording); - await camera.startVideoRecording(cameraId); + await camera.startVideoCapturing(const VideoCaptureOptions(cameraId)); // Verify that each of these calls happened only once. verify(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) .called(1); @@ -872,6 +872,55 @@ void main() { verifyNoMoreInteractions(mockPendingRecording); }); + test( + 'startVideoCapturing called with stream options starts image streaming', + () async { + // Set up mocks and constants. + final FakeAndroidCameraCameraX camera = + FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true); + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + camera.processCameraProvider = mockProcessCameraProvider; + camera.cameraSelector = MockCameraSelector(); + camera.recorder = camera.testRecorder; + camera.videoCapture = camera.testVideoCapture; + camera.imageAnalysis = camera.testImageAnalysis; + camera.camera = MockCamera(); + final MockPendingRecording mockPendingRecording = MockPendingRecording(); + final TestSystemServicesHostApi mockSystemServicesApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockSystemServicesApi); + + const int cameraId = 17; + const String outputPath = '/temp/MOV123.temp'; + final Completer imageDataCompleter = + Completer(); + final VideoCaptureOptions videoCaptureOptions = VideoCaptureOptions( + cameraId, + streamCallback: (CameraImageData imageData) => + imageDataCompleter.complete(imageData)); + + // Mock method calls. + when(camera.processCameraProvider!.isBound(camera.videoCapture!)) + .thenAnswer((_) async => true); + when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp')) + .thenReturn(outputPath); + when(camera.testRecorder.prepareRecording(outputPath)) + .thenAnswer((_) async => mockPendingRecording); + when(mockProcessCameraProvider.bindToLifecycle(any, any)) + .thenAnswer((_) => Future.value(camera.camera)); + when(camera.camera!.getCameraInfo()) + .thenAnswer((_) => Future.value(MockCameraInfo())); + + await camera.startVideoCapturing(videoCaptureOptions); + + final CameraImageData mockCameraImageData = MockCameraImageData(); + camera.cameraImageDataStreamController!.add(mockCameraImageData); + + expect(imageDataCompleter.future, isNotNull); + await camera.cameraImageDataStreamController!.close(); + }); + test('pauseVideoRecording pauses the recording', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); final MockRecording recording = MockRecording();