Skip to content

Commit

Permalink
[camera] Use startVideoCapturing and expose concurrent stream/record (f…
Browse files Browse the repository at this point in the history
…lutter#6815)

* Use startVideoCapturing and expose concurrent stream/record

This uses the new startVideoCapturing implementation, that supports concurrent stream/record.

* Ran dart formatter

* retrigger checks

* Account for version bump
  • Loading branch information
adam-harwood authored Jan 18, 2023
1 parent e85e0f2 commit 11361d0
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 62 deletions.
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

0 comments on commit 11361d0

Please sign in to comment.