Skip to content

Commit 11361d0

Browse files
authored
[camera] Use startVideoCapturing and expose concurrent stream/record (#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
1 parent e85e0f2 commit 11361d0

File tree

6 files changed

+84
-62
lines changed

6 files changed

+84
-62
lines changed

packages/camera/camera/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.10.2
2+
3+
* Implements option to also stream when recording a video.
4+
15
## 0.10.1
26

37
* Remove usage of deprecated quiver Optional type.

packages/camera/camera/lib/src/camera_controller.dart

+19-13
Original file line numberDiff line numberDiff line change
@@ -452,12 +452,6 @@ class CameraController extends ValueNotifier<CameraValue> {
452452
assert(defaultTargetPlatform == TargetPlatform.android ||
453453
defaultTargetPlatform == TargetPlatform.iOS);
454454
_throwIfNotInitialized('stopImageStream');
455-
if (value.isRecordingVideo) {
456-
throw CameraException(
457-
'A video recording is already started.',
458-
'stopImageStream was called while a video is being recorded.',
459-
);
460-
}
461455
if (!value.isStreamingImages) {
462456
throw CameraException(
463457
'No camera is streaming images',
@@ -476,28 +470,35 @@ class CameraController extends ValueNotifier<CameraValue> {
476470

477471
/// Start a video recording.
478472
///
473+
/// You may optionally pass an [onAvailable] callback to also have the
474+
/// video frames streamed to this callback.
475+
///
479476
/// The video is returned as a [XFile] after calling [stopVideoRecording].
480477
/// Throws a [CameraException] if the capture fails.
481-
Future<void> startVideoRecording() async {
478+
Future<void> startVideoRecording(
479+
{onLatestImageAvailable? onAvailable}) async {
482480
_throwIfNotInitialized('startVideoRecording');
483481
if (value.isRecordingVideo) {
484482
throw CameraException(
485483
'A video recording is already started.',
486484
'startVideoRecording was called when a recording is already started.',
487485
);
488486
}
489-
if (value.isStreamingImages) {
490-
throw CameraException(
491-
'A camera has started streaming images.',
492-
'startVideoRecording was called while a camera was streaming images.',
493-
);
487+
488+
Function(CameraImageData image)? streamCallback;
489+
if (onAvailable != null) {
490+
streamCallback = (CameraImageData imageData) {
491+
onAvailable(CameraImage.fromPlatformInterface(imageData));
492+
};
494493
}
495494

496495
try {
497-
await CameraPlatform.instance.startVideoRecording(_cameraId);
496+
await CameraPlatform.instance.startVideoCapturing(
497+
VideoCaptureOptions(_cameraId, streamCallback: streamCallback));
498498
value = value.copyWith(
499499
isRecordingVideo: true,
500500
isRecordingPaused: false,
501+
isStreamingImages: onAvailable != null,
501502
recordingOrientation:
502503
value.lockedCaptureOrientation ?? value.deviceOrientation);
503504
} on PlatformException catch (e) {
@@ -516,6 +517,11 @@ class CameraController extends ValueNotifier<CameraValue> {
516517
'stopVideoRecording was called when no video is recording.',
517518
);
518519
}
520+
521+
if (value.isStreamingImages) {
522+
stopImageStream();
523+
}
524+
519525
try {
520526
final XFile file =
521527
await CameraPlatform.instance.stopVideoRecording(_cameraId);

packages/camera/camera/pubspec.yaml

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
44
Dart.
55
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
7-
version: 0.10.1
7+
version: 0.10.2
88

99
environment:
1010
sdk: ">=2.14.0 <3.0.0"
@@ -21,10 +21,10 @@ flutter:
2121
default_package: camera_web
2222

2323
dependencies:
24-
camera_android: ^0.10.0
25-
camera_avfoundation: ^0.9.7+1
26-
camera_platform_interface: ^2.2.0
27-
camera_web: ^0.3.0
24+
camera_android: ^0.10.1
25+
camera_avfoundation: ^0.9.9
26+
camera_platform_interface: ^2.3.2
27+
camera_web: ^0.3.1
2828
flutter:
2929
sdk: flutter
3030
flutter_plugin_android_lifecycle: ^2.0.2

packages/camera/camera/test/camera_image_stream_test.dart

+48-19
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ void main() {
130130
);
131131
});
132132

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

143-
await cameraController.startImageStream((CameraImage image) => null);
144-
cameraController.value =
145-
cameraController.value.copyWith(isRecordingVideo: true);
146143
expect(
147144
cameraController.stopImageStream,
148145
throwsA(isA<CameraException>().having(
149146
(CameraException error) => error.description,
150-
'A video recording is already started.',
151-
'stopImageStream was called while a video is being recorded.',
147+
'No camera is streaming images',
148+
'stopImageStream was called when no camera is streaming images.',
152149
)));
153150
});
154151

155-
test('stopImageStream() throws $CameraException when not streaming images',
156-
() async {
152+
test('stopImageStream() intended behaviour', () async {
157153
final CameraController cameraController = CameraController(
158154
const CameraDescription(
159155
name: 'cam',
160156
lensDirection: CameraLensDirection.back,
161157
sensorOrientation: 90),
162158
ResolutionPreset.max);
163159
await cameraController.initialize();
160+
await cameraController.startImageStream((CameraImage image) => null);
161+
await cameraController.stopImageStream();
162+
163+
expect(mockPlatform.streamCallLog,
164+
<String>['onStreamedFrameAvailable', 'listen', 'cancel']);
165+
});
166+
167+
test('startVideoRecording() can stream images', () async {
168+
final CameraController cameraController = CameraController(
169+
const CameraDescription(
170+
name: 'cam',
171+
lensDirection: CameraLensDirection.back,
172+
sensorOrientation: 90),
173+
ResolutionPreset.max);
174+
175+
await cameraController.initialize();
176+
177+
cameraController.startVideoRecording(
178+
onAvailable: (CameraImage image) => null);
164179

165180
expect(
166-
cameraController.stopImageStream,
167-
throwsA(isA<CameraException>().having(
168-
(CameraException error) => error.description,
169-
'No camera is streaming images',
170-
'stopImageStream was called when no camera is streaming images.',
171-
)));
181+
mockPlatform.streamCallLog.contains('startVideoCapturing with stream'),
182+
isTrue);
172183
});
173184

174-
test('stopImageStream() intended behaviour', () async {
185+
test('startVideoRecording() by default does not stream', () async {
175186
final CameraController cameraController = CameraController(
176187
const CameraDescription(
177188
name: 'cam',
178189
lensDirection: CameraLensDirection.back,
179190
sensorOrientation: 90),
180191
ResolutionPreset.max);
192+
181193
await cameraController.initialize();
182-
await cameraController.startImageStream((CameraImage image) => null);
183-
await cameraController.stopImageStream();
184194

185-
expect(mockPlatform.streamCallLog,
186-
<String>['onStreamedFrameAvailable', 'listen', 'cancel']);
195+
cameraController.startVideoRecording();
196+
197+
expect(mockPlatform.streamCallLog.contains('startVideoCapturing'), isTrue);
187198
});
188199
}
189200

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

217+
@override
218+
Future<XFile> startVideoRecording(int cameraId,
219+
{Duration? maxVideoDuration}) {
220+
streamCallLog.add('startVideoRecording');
221+
return super
222+
.startVideoRecording(cameraId, maxVideoDuration: maxVideoDuration);
223+
}
224+
225+
@override
226+
Future<void> startVideoCapturing(VideoCaptureOptions options) {
227+
if (options.streamCallback == null) {
228+
streamCallLog.add('startVideoCapturing');
229+
} else {
230+
streamCallLog.add('startVideoCapturing with stream');
231+
}
232+
return super.startVideoCapturing(options);
233+
}
234+
206235
void _onFrameStreamListen() {
207236
streamCallLog.add('listen');
208237
}

packages/camera/camera/test/camera_preview_test.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ class FakeController extends ValueNotifier<CameraValue>
9797
Future<void> startImageStream(onLatestImageAvailable onAvailable) async {}
9898

9999
@override
100-
Future<void> startVideoRecording() async {}
100+
Future<void> startVideoRecording(
101+
{onLatestImageAvailable? onAvailable}) async {}
101102

102103
@override
103104
Future<void> stopImageStream() async {}

packages/camera/camera/test/camera_test.dart

+6-24
Original file line numberDiff line numberDiff line change
@@ -335,30 +335,6 @@ void main() {
335335
)));
336336
});
337337

338-
test(
339-
'startVideoRecording() throws $CameraException when already streaming images',
340-
() async {
341-
final CameraController cameraController = CameraController(
342-
const CameraDescription(
343-
name: 'cam',
344-
lensDirection: CameraLensDirection.back,
345-
sensorOrientation: 90),
346-
ResolutionPreset.max);
347-
348-
await cameraController.initialize();
349-
350-
cameraController.value =
351-
cameraController.value.copyWith(isStreamingImages: true);
352-
353-
expect(
354-
cameraController.startVideoRecording(),
355-
throwsA(isA<CameraException>().having(
356-
(CameraException error) => error.description,
357-
'A camera has started streaming images.',
358-
'startVideoRecording was called while a camera was streaming images.',
359-
)));
360-
});
361-
362338
test('getMaxZoomLevel() throws $CameraException when uninitialized',
363339
() async {
364340
final CameraController cameraController = CameraController(
@@ -1457,6 +1433,12 @@ class MockCameraPlatform extends Mock
14571433
{Duration? maxVideoDuration}) =>
14581434
Future<XFile>.value(mockVideoRecordingXFile);
14591435

1436+
@override
1437+
Future<void> startVideoCapturing(VideoCaptureOptions options) {
1438+
return startVideoRecording(options.cameraId,
1439+
maxVideoDuration: options.maxDuration);
1440+
}
1441+
14601442
@override
14611443
Future<void> lockCaptureOrientation(
14621444
int? cameraId, DeviceOrientation? orientation) async =>

0 commit comments

Comments
 (0)