[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
diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md
index 84c7559..d19a528 100644
--- a/packages/camera/camera/CHANGELOG.md
+++ b/packages/camera/camera/CHANGELOG.md
@@ -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.
diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart
index b05e61b..b201074 100644
--- a/packages/camera/camera/lib/src/camera_controller.dart
+++ b/packages/camera/camera/lib/src/camera_controller.dart
@@ -452,12 +452,6 @@
     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',
@@ -476,9 +470,13 @@
 
   /// 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(
@@ -486,18 +484,21 @@
         '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) {
@@ -516,6 +517,11 @@
         'stopVideoRecording was called when no video is recording.',
       );
     }
+
+    if (value.isStreamingImages) {
+      stopImageStream();
+    }
+
     try {
       final XFile file =
           await CameraPlatform.instance.stopVideoRecording(_cameraId);
diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml
index 33d704e..f8b23bf 100644
--- a/packages/camera/camera/pubspec.yaml
+++ b/packages/camera/camera/pubspec.yaml
@@ -4,7 +4,7 @@
   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"
@@ -21,10 +21,10 @@
         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
diff --git a/packages/camera/camera/test/camera_image_stream_test.dart b/packages/camera/camera/test/camera_image_stream_test.dart
index a9320e4..29b5cce 100644
--- a/packages/camera/camera/test/camera_image_stream_test.dart
+++ b/packages/camera/camera/test/camera_image_stream_test.dart
@@ -130,28 +130,6 @@
     );
   });
 
-  test('stopImageStream() throws $CameraException when recording videos',
-      () 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);
-    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.',
-        )));
-  });
-
   test('stopImageStream() throws $CameraException when not streaming images',
       () async {
     final CameraController cameraController = CameraController(
@@ -185,6 +163,39 @@
     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(
+        mockPlatform.streamCallLog.contains('startVideoCapturing with stream'),
+        isTrue);
+  });
+
+  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();
+
+    cameraController.startVideoRecording();
+
+    expect(mockPlatform.streamCallLog.contains('startVideoCapturing'), isTrue);
+  });
 }
 
 class MockStreamingCameraPlatform extends MockCameraPlatform {
@@ -203,6 +214,24 @@
     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');
   }
diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart
index 546f4e9..7c43787 100644
--- a/packages/camera/camera/test/camera_preview_test.dart
+++ b/packages/camera/camera/test/camera_preview_test.dart
@@ -97,7 +97,8 @@
   Future<void> startImageStream(onLatestImageAvailable onAvailable) async {}
 
   @override
-  Future<void> startVideoRecording() async {}
+  Future<void> startVideoRecording(
+      {onLatestImageAvailable? onAvailable}) async {}
 
   @override
   Future<void> stopImageStream() async {}
diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart
index 2138f2d..44a48d1 100644
--- a/packages/camera/camera/test/camera_test.dart
+++ b/packages/camera/camera/test/camera_test.dart
@@ -335,30 +335,6 @@
           )));
     });
 
-    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(
@@ -1458,6 +1434,12 @@
       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 =>
       super.noSuchMethod(Invocation.method(