diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 9060d4175aa4..a04de5b7c642 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.11.3 + +* Adds support to configure persistent recording on Android. See `CameraController.startVideoRecording(enablePersistentRecording)`. +* Updates minimum supported SDK version to Flutter 3.35.0/Dart 3.9. + ## 0.11.2+1 * Updates examples to use the new RadioGroup API instead of deprecated Radio parameters. diff --git a/packages/camera/camera/example/integration_test/camera_test.dart b/packages/camera/camera/example/integration_test/camera_test.dart index 172319531de5..19909e55a89b 100644 --- a/packages/camera/camera/example/integration_test/camera_test.dart +++ b/packages/camera/camera/example/integration_test/camera_test.dart @@ -260,7 +260,9 @@ void main() { return completer.future; } - testWidgets('Set description while recording', (WidgetTester tester) async { + testWidgets('Set description while recording captures full video', ( + WidgetTester tester, + ) async { final List cameras = await availableCameras(); if (cameras.length < 2) { return; @@ -269,7 +271,6 @@ void main() { final CameraController controller = CameraController( cameras[0], ResolutionPreset.low, - enableAudio: false, ); await controller.initialize(); @@ -278,7 +279,27 @@ void main() { await controller.startVideoRecording(); await controller.setDescription(cameras[1]); - expect(controller.description, cameras[1]); + await tester.pumpAndSettle(const Duration(seconds: 4)); + + await controller.setDescription(cameras[0]); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + + final XFile file = await controller.stopVideoRecording(); + + final File videoFile = File(file.path); + final VideoPlayerController videoController = VideoPlayerController.file( + videoFile, + ); + await videoController.initialize(); + final int duration = videoController.value.duration.inMilliseconds; + await videoController.dispose(); + + expect( + duration, + greaterThanOrEqualTo(const Duration(seconds: 4).inMilliseconds), + ); + await controller.dispose(); }); testWidgets('Set description', (WidgetTester tester) async { diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 4ecc6aae971a..23494b4b27ff 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -187,20 +187,17 @@ class CameraValue { exposurePointSupported ?? this.exposurePointSupported, focusPointSupported: focusPointSupported ?? this.focusPointSupported, deviceOrientation: deviceOrientation ?? this.deviceOrientation, - lockedCaptureOrientation: - lockedCaptureOrientation == null - ? this.lockedCaptureOrientation - : lockedCaptureOrientation.orNull, - recordingOrientation: - recordingOrientation == null - ? this.recordingOrientation - : recordingOrientation.orNull, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, description: description ?? this.description, - previewPauseOrientation: - previewPauseOrientation == null - ? this.previewPauseOrientation - : previewPauseOrientation.orNull, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, ); } @@ -439,6 +436,10 @@ class CameraController extends ValueNotifier { /// Sets the description of the camera. /// + /// On Android, you must start the recording with [startVideoRecording] + /// with `enablePersistentRecording` set to `true` + /// to avoid cancelling any active recording. + /// /// Throws a [CameraException] if setting the description fails. Future setDescription(CameraDescription description) async { if (value.isRecordingVideo) { @@ -554,8 +555,15 @@ class CameraController extends ValueNotifier { /// /// The video is returned as a [XFile] after calling [stopVideoRecording]. /// Throws a [CameraException] if the capture fails. + /// + /// `enablePersistentRecording` parameter configures the recording to be a persistent recording. + /// A persistent recording can only be stopped by explicitly calling [stopVideoRecording] + /// and will ignore events that would normally cause recording to stop, + /// such as lifecycle events or explicit calls to [setDescription] while recording is in progress. + /// Currently a no-op on platforms other than Android. Future startVideoRecording({ onLatestImageAvailable? onAvailable, + bool enablePersistentRecording = true, }) async { _throwIfNotInitialized('startVideoRecording'); if (value.isRecordingVideo) { @@ -574,7 +582,11 @@ class CameraController extends ValueNotifier { try { await CameraPlatform.instance.startVideoCapturing( - VideoCaptureOptions(_cameraId, streamCallback: streamCallback), + VideoCaptureOptions( + _cameraId, + streamCallback: streamCallback, + enablePersistentRecording: enablePersistentRecording, + ), ); value = value.copyWith( isRecordingVideo: true, diff --git a/packages/camera/camera/lib/src/camera_preview.dart b/packages/camera/camera/lib/src/camera_preview.dart index c0a15fe56f07..ea0b601ac73a 100644 --- a/packages/camera/camera/lib/src/camera_preview.dart +++ b/packages/camera/camera/lib/src/camera_preview.dart @@ -23,24 +23,23 @@ class CameraPreview extends StatelessWidget { Widget build(BuildContext context) { return controller.value.isInitialized ? ValueListenableBuilder( - valueListenable: controller, - builder: (BuildContext context, Object? value, Widget? child) { - return AspectRatio( - aspectRatio: - _isLandscape() - ? controller.value.aspectRatio - : (1 / controller.value.aspectRatio), - child: Stack( - fit: StackFit.expand, - children: [ - _wrapInRotatedBox(child: controller.buildPreview()), - child ?? Container(), - ], - ), - ); - }, - child: child, - ) + valueListenable: controller, + builder: (BuildContext context, Object? value, Widget? child) { + return AspectRatio( + aspectRatio: _isLandscape() + ? controller.value.aspectRatio + : (1 / controller.value.aspectRatio), + child: Stack( + fit: StackFit.expand, + children: [ + _wrapInRotatedBox(child: controller.buildPreview()), + child ?? Container(), + ], + ), + ); + }, + child: child, + ) : Container(); } @@ -73,7 +72,7 @@ class CameraPreview extends StatelessWidget { return controller.value.isRecordingVideo ? controller.value.recordingOrientation! : (controller.value.previewPauseOrientation ?? - controller.value.lockedCaptureOrientation ?? - controller.value.deviceOrientation); + controller.value.lockedCaptureOrientation ?? + controller.value.deviceOrientation); } } diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 5207181aac9b..95b55be083db 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,11 +4,11 @@ description: A Flutter plugin for controlling the camera. Supports previewing Dart. repository: https://github.com/flutter/packages/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.11.2+1 +version: 0.11.3 environment: - sdk: ^3.7.0 - flutter: ">=3.29.0" + sdk: ^3.9.0 + flutter: ">=3.35.0" flutter: plugin: @@ -21,9 +21,9 @@ flutter: default_package: camera_web dependencies: - camera_android_camerax: ^0.6.13 + camera_android_camerax: ^0.6.22 camera_avfoundation: ^0.9.18 - camera_platform_interface: ^2.10.0 + camera_platform_interface: ^2.11.0 camera_web: ^0.3.3 flutter: sdk: flutter diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index be672d1bbb2e..bafc77831dcb 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -112,6 +112,7 @@ class FakeController extends ValueNotifier @override Future startVideoRecording({ onLatestImageAvailable? onAvailable, + bool enablePersistentRecording = true, }) async {} @override diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index cc7983d10a39..c3af92bb7579 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1838,10 +1838,9 @@ class MockCameraPlatform extends Mock Future createCameraWithSettings( CameraDescription cameraDescription, MediaSettings? mediaSettings, - ) => - mockPlatformException - ? throw PlatformException(code: 'foo', message: 'bar') - : Future.value(mockInitializeCamera); + ) => mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockInitializeCamera); @override Future createCamera( @@ -1869,10 +1868,9 @@ class MockCameraPlatform extends Mock ); @override - Future takePicture(int cameraId) => - mockPlatformException - ? throw PlatformException(code: 'foo', message: 'bar') - : Future.value(mockTakePicture); + Future takePicture(int cameraId) => mockPlatformException + ? throw PlatformException(code: 'foo', message: 'bar') + : Future.value(mockTakePicture); @override Future prepareForVideoRecording() async =>