Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[camera] Use startVideoCapturing and expose concurrent stream/record #6815

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.10.2

* Implements option to also stream when recording a video.

## 0.10.1

* Remove usage of deprecated quiver Optional type.
Expand Down
32 changes: 19 additions & 13 deletions packages/camera/camera/lib/src/camera_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -452,12 +452,6 @@ class CameraController extends ValueNotifier<CameraValue> {
assert(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS);
_throwIfNotInitialized('stopImageStream');
if (value.isRecordingVideo) {
throw CameraException(
'A video recording is already started.',
'stopImageStream was called while a video is being recorded.',
);
}
if (!value.isStreamingImages) {
throw CameraException(
'No camera is streaming images',
Expand All @@ -476,28 +470,35 @@ class CameraController extends ValueNotifier<CameraValue> {

/// Start a video recording.
///
/// You may optionally pass an [onAvailable] callback to also have the
/// video frames streamed to this callback.
///
/// The video is returned as a [XFile] after calling [stopVideoRecording].
/// Throws a [CameraException] if the capture fails.
Future<void> startVideoRecording() async {
Future<void> startVideoRecording(
{onLatestImageAvailable? onAvailable}) async {
_throwIfNotInitialized('startVideoRecording');
if (value.isRecordingVideo) {
throw CameraException(
'A video recording is already started.',
'startVideoRecording was called when a recording is already started.',
);
}
if (value.isStreamingImages) {
throw CameraException(
'A camera has started streaming images.',
'startVideoRecording was called while a camera was streaming images.',
);

Function(CameraImageData image)? streamCallback;
if (onAvailable != null) {
streamCallback = (CameraImageData imageData) {
onAvailable(CameraImage.fromPlatformInterface(imageData));
};
}

try {
await CameraPlatform.instance.startVideoRecording(_cameraId);
await CameraPlatform.instance.startVideoCapturing(
VideoCaptureOptions(_cameraId, streamCallback: streamCallback));
value = value.copyWith(
isRecordingVideo: true,
isRecordingPaused: false,
isStreamingImages: onAvailable != null,
recordingOrientation:
value.lockedCaptureOrientation ?? value.deviceOrientation);
} on PlatformException catch (e) {
Expand All @@ -516,6 +517,11 @@ class CameraController extends ValueNotifier<CameraValue> {
'stopVideoRecording was called when no video is recording.',
);
}

if (value.isStreamingImages) {
stopImageStream();
}

try {
final XFile file =
await CameraPlatform.instance.stopVideoRecording(_cameraId);
Expand Down
10 changes: 5 additions & 5 deletions packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
Dart.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.10.1
version: 0.10.2

environment:
sdk: ">=2.14.0 <3.0.0"
Expand All @@ -21,10 +21,10 @@ flutter:
default_package: camera_web

dependencies:
camera_android: ^0.10.0
camera_avfoundation: ^0.9.7+1
camera_platform_interface: ^2.2.0
camera_web: ^0.3.0
camera_android: ^0.10.1
camera_avfoundation: ^0.9.9
camera_platform_interface: ^2.3.2
camera_web: ^0.3.1
flutter:
sdk: flutter
flutter_plugin_android_lifecycle: ^2.0.2
Expand Down
67 changes: 48 additions & 19 deletions packages/camera/camera/test/camera_image_stream_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ void main() {
);
});

test('stopImageStream() throws $CameraException when recording videos',
test('stopImageStream() throws $CameraException when not streaming images',
() async {
final CameraController cameraController = CameraController(
const CameraDescription(
Expand All @@ -140,50 +140,61 @@ void main() {
ResolutionPreset.max);
await cameraController.initialize();

await cameraController.startImageStream((CameraImage image) => null);
cameraController.value =
cameraController.value.copyWith(isRecordingVideo: true);
expect(
cameraController.stopImageStream,
throwsA(isA<CameraException>().having(
(CameraException error) => error.description,
'A video recording is already started.',
'stopImageStream was called while a video is being recorded.',
'No camera is streaming images',
'stopImageStream was called when no camera is streaming images.',
)));
});

test('stopImageStream() throws $CameraException when not streaming images',
() async {
test('stopImageStream() intended behaviour', () async {
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);
await cameraController.initialize();
await cameraController.startImageStream((CameraImage image) => null);
await cameraController.stopImageStream();

expect(mockPlatform.streamCallLog,
<String>['onStreamedFrameAvailable', 'listen', 'cancel']);
});

test('startVideoRecording() can stream images', () async {
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);

await cameraController.initialize();

cameraController.startVideoRecording(
onAvailable: (CameraImage image) => null);

expect(
cameraController.stopImageStream,
throwsA(isA<CameraException>().having(
(CameraException error) => error.description,
'No camera is streaming images',
'stopImageStream was called when no camera is streaming images.',
)));
mockPlatform.streamCallLog.contains('startVideoCapturing with stream'),
isTrue);
});

test('stopImageStream() intended behaviour', () async {
test('startVideoRecording() by default does not stream', () async {
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);

await cameraController.initialize();
await cameraController.startImageStream((CameraImage image) => null);
await cameraController.stopImageStream();

expect(mockPlatform.streamCallLog,
<String>['onStreamedFrameAvailable', 'listen', 'cancel']);
cameraController.startVideoRecording();

expect(mockPlatform.streamCallLog.contains('startVideoCapturing'), isTrue);
});
}

Expand All @@ -203,6 +214,24 @@ class MockStreamingCameraPlatform extends MockCameraPlatform {
return _streamController!.stream;
}

@override
Future<XFile> startVideoRecording(int cameraId,
{Duration? maxVideoDuration}) {
streamCallLog.add('startVideoRecording');
return super
.startVideoRecording(cameraId, maxVideoDuration: maxVideoDuration);
}

@override
Future<void> startVideoCapturing(VideoCaptureOptions options) {
if (options.streamCallback == null) {
streamCallLog.add('startVideoCapturing');
} else {
streamCallLog.add('startVideoCapturing with stream');
}
return super.startVideoCapturing(options);
}

void _onFrameStreamListen() {
streamCallLog.add('listen');
}
Expand Down
3 changes: 2 additions & 1 deletion packages/camera/camera/test/camera_preview_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ class FakeController extends ValueNotifier<CameraValue>
Future<void> startImageStream(onLatestImageAvailable onAvailable) async {}

@override
Future<void> startVideoRecording() async {}
Future<void> startVideoRecording(
{onLatestImageAvailable? onAvailable}) async {}

@override
Future<void> stopImageStream() async {}
Expand Down
30 changes: 6 additions & 24 deletions packages/camera/camera/test/camera_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -335,30 +335,6 @@ void main() {
)));
});

test(
'startVideoRecording() throws $CameraException when already streaming images',
() async {
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);

await cameraController.initialize();

cameraController.value =
cameraController.value.copyWith(isStreamingImages: true);

expect(
cameraController.startVideoRecording(),
throwsA(isA<CameraException>().having(
(CameraException error) => error.description,
'A camera has started streaming images.',
'startVideoRecording was called while a camera was streaming images.',
)));
});

test('getMaxZoomLevel() throws $CameraException when uninitialized',
() async {
final CameraController cameraController = CameraController(
Expand Down Expand Up @@ -1457,6 +1433,12 @@ class MockCameraPlatform extends Mock
{Duration? maxVideoDuration}) =>
Future<XFile>.value(mockVideoRecordingXFile);

@override
Future<void> startVideoCapturing(VideoCaptureOptions options) {
return startVideoRecording(options.cameraId,
maxVideoDuration: options.maxDuration);
}

@override
Future<void> lockCaptureOrientation(
int? cameraId, DeviceOrientation? orientation) async =>
Expand Down