Skip to content

Commit bec74e0

Browse files
authored
[camerax] Implement startVideoCapturing and onVideoRecordedEvent (flutter#4815)
Implements `startVideoCapturing` (with the image streaming option, others currently unsupported) and `onVideoRecordedEvent` (empty implementation; same as the other plugins). Fixes flutter#126477. Fixes flutter#127896.
1 parent e04ba88 commit bec74e0

File tree

5 files changed

+90
-14
lines changed

5 files changed

+90
-14
lines changed

packages/camera/camera_android_camerax/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.5.0+18
2+
3+
* Implements `startVideoCapturing`.
4+
15
## 0.5.0+17
26

37
* Implements resolution configuration for all camera use cases.

packages/camera/camera_android_camerax/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,12 @@ Calling `setFlashMode` with mode `FlashMode.torch` currently does nothing.
5151

5252
`setZoomLevel` is unimplemented.
5353

54-
### Some video capture functionality \[[Issue #127896][127896], [Issue #126477][126477]\]
54+
### Setting maximum duration and stream options for video capture
5555

56-
`startVideoCapturing` is unimplemented; use `startVideoRecording` instead.
57-
`onVideoRecordedEvent` is also unimplemented.
56+
Calling `startVideoCapturing` with `VideoCaptureOptions` configured with
57+
`maxVideoDuration` and `streamOptions` is currently unsupported do to the
58+
limitations of the CameraX library and the platform interface, respectively,
59+
and thus, those parameters will silently be ignored.
5860

5961
## Contributing
6062

packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,12 @@ class AndroidCameraCameraX extends CameraPlatform {
363363
]);
364364
}
365365

366+
/// The camera finished recording a video.
367+
@override
368+
Stream<VideoRecordedEvent> onVideoRecordedEvent(int cameraId) {
369+
return _cameraEvents(cameraId).whereType<VideoRecordedEvent>();
370+
}
371+
366372
/// Gets the minimum supported exposure offset for the selected camera in EV units.
367373
///
368374
/// [cameraId] not used.
@@ -507,12 +513,23 @@ class AndroidCameraCameraX extends CameraPlatform {
507513
/// Note that the preset resolution is used to configure the recording, but
508514
/// 240p ([ResolutionPreset.low]) is unsupported and will fallback to
509515
/// configure the recording as the next highest available quality.
516+
///
517+
/// This method is deprecated in favour of [startVideoCapturing].
510518
@override
511519
Future<void> startVideoRecording(int cameraId,
512520
{Duration? maxVideoDuration}) async {
513-
assert(cameraSelector != null);
514-
assert(processCameraProvider != null);
521+
return startVideoCapturing(
522+
VideoCaptureOptions(cameraId, maxDuration: maxVideoDuration));
523+
}
515524

525+
/// Starts a video recording and/or streaming session.
526+
///
527+
/// Please see [VideoCaptureOptions] for documentation on the
528+
/// configuration options. Currently, maxVideoDuration and streamOptions
529+
/// are unsupported due to the limitations of CameraX and the platform
530+
/// interface, respectively.
531+
@override
532+
Future<void> startVideoCapturing(VideoCaptureOptions options) async {
516533
if (recording != null) {
517534
// There is currently an active recording, so do not start a new one.
518535
return;
@@ -527,6 +544,10 @@ class AndroidCameraCameraX extends CameraPlatform {
527544
await SystemServices.getTempFilePath(videoPrefix, '.temp');
528545
pendingRecording = await recorder!.prepareRecording(videoOutputPath!);
529546
recording = await pendingRecording!.start();
547+
548+
if (options.streamCallback != null) {
549+
onStreamedFrameAvailable(options.cameraId).listen(options.streamCallback);
550+
}
530551
}
531552

532553
/// Stops the video recording and returns the file where it was saved.

packages/camera/camera_android_camerax/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_android_camerax
22
description: Android implementation of the camera plugin using the CameraX library.
33
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.5.0+17
5+
version: 0.5.0+18
66

77
environment:
88
sdk: ">=2.19.0 <4.0.0"
@@ -19,7 +19,7 @@ flutter:
1919

2020
dependencies:
2121
async: ^2.5.0
22-
camera_platform_interface: ^2.2.0
22+
camera_platform_interface: ^2.3.2
2323
flutter:
2424
sdk: flutter
2525
integration_test:

packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -785,9 +785,9 @@ void main() {
785785

786786
group('video recording', () {
787787
test(
788-
'startVideoRecording binds video capture use case and starts the recording',
788+
'startVideoCapturing binds video capture use case and starts the recording',
789789
() async {
790-
//Set up mocks and constants.
790+
// Set up mocks and constants.
791791
final FakeAndroidCameraCameraX camera = FakeAndroidCameraCameraX();
792792
camera.processCameraProvider = MockProcessCameraProvider();
793793
camera.cameraSelector = MockCameraSelector();
@@ -815,7 +815,7 @@ void main() {
815815
camera.cameraSelector!, <UseCase>[camera.videoCapture!]))
816816
.thenAnswer((_) async => camera.camera!);
817817

818-
await camera.startVideoRecording(cameraId);
818+
await camera.startVideoCapturing(const VideoCaptureOptions(cameraId));
819819

820820
verify(camera.processCameraProvider!.bindToLifecycle(
821821
camera.cameraSelector!, <UseCase>[camera.videoCapture!]));
@@ -824,9 +824,9 @@ void main() {
824824
});
825825

826826
test(
827-
'startVideoRecording binds video capture use case and starts the recording'
827+
'startVideoCapturing binds video capture use case and starts the recording'
828828
' on first call, and does nothing on second call', () async {
829-
//Set up mocks and constants.
829+
// Set up mocks and constants.
830830
final FakeAndroidCameraCameraX camera = FakeAndroidCameraCameraX();
831831
camera.processCameraProvider = MockProcessCameraProvider();
832832
camera.cameraSelector = MockCameraSelector();
@@ -854,14 +854,14 @@ void main() {
854854
camera.cameraSelector!, <UseCase>[camera.videoCapture!]))
855855
.thenAnswer((_) async => camera.camera!);
856856

857-
await camera.startVideoRecording(cameraId);
857+
await camera.startVideoCapturing(const VideoCaptureOptions(cameraId));
858858

859859
verify(camera.processCameraProvider!.bindToLifecycle(
860860
camera.cameraSelector!, <UseCase>[camera.videoCapture!]));
861861
expect(camera.pendingRecording, equals(mockPendingRecording));
862862
expect(camera.recording, mockRecording);
863863

864-
await camera.startVideoRecording(cameraId);
864+
await camera.startVideoCapturing(const VideoCaptureOptions(cameraId));
865865
// Verify that each of these calls happened only once.
866866
verify(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp'))
867867
.called(1);
@@ -872,6 +872,55 @@ void main() {
872872
verifyNoMoreInteractions(mockPendingRecording);
873873
});
874874

875+
test(
876+
'startVideoCapturing called with stream options starts image streaming',
877+
() async {
878+
// Set up mocks and constants.
879+
final FakeAndroidCameraCameraX camera =
880+
FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
881+
final MockProcessCameraProvider mockProcessCameraProvider =
882+
MockProcessCameraProvider();
883+
camera.processCameraProvider = mockProcessCameraProvider;
884+
camera.cameraSelector = MockCameraSelector();
885+
camera.recorder = camera.testRecorder;
886+
camera.videoCapture = camera.testVideoCapture;
887+
camera.imageAnalysis = camera.testImageAnalysis;
888+
camera.camera = MockCamera();
889+
final MockPendingRecording mockPendingRecording = MockPendingRecording();
890+
final TestSystemServicesHostApi mockSystemServicesApi =
891+
MockTestSystemServicesHostApi();
892+
TestSystemServicesHostApi.setup(mockSystemServicesApi);
893+
894+
const int cameraId = 17;
895+
const String outputPath = '/temp/MOV123.temp';
896+
final Completer<CameraImageData> imageDataCompleter =
897+
Completer<CameraImageData>();
898+
final VideoCaptureOptions videoCaptureOptions = VideoCaptureOptions(
899+
cameraId,
900+
streamCallback: (CameraImageData imageData) =>
901+
imageDataCompleter.complete(imageData));
902+
903+
// Mock method calls.
904+
when(camera.processCameraProvider!.isBound(camera.videoCapture!))
905+
.thenAnswer((_) async => true);
906+
when(mockSystemServicesApi.getTempFilePath(camera.videoPrefix, '.temp'))
907+
.thenReturn(outputPath);
908+
when(camera.testRecorder.prepareRecording(outputPath))
909+
.thenAnswer((_) async => mockPendingRecording);
910+
when(mockProcessCameraProvider.bindToLifecycle(any, any))
911+
.thenAnswer((_) => Future<Camera>.value(camera.camera));
912+
when(camera.camera!.getCameraInfo())
913+
.thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
914+
915+
await camera.startVideoCapturing(videoCaptureOptions);
916+
917+
final CameraImageData mockCameraImageData = MockCameraImageData();
918+
camera.cameraImageDataStreamController!.add(mockCameraImageData);
919+
920+
expect(imageDataCompleter.future, isNotNull);
921+
await camera.cameraImageDataStreamController!.close();
922+
});
923+
875924
test('pauseVideoRecording pauses the recording', () async {
876925
final AndroidCameraCameraX camera = AndroidCameraCameraX();
877926
final MockRecording recording = MockRecording();

0 commit comments

Comments
 (0)