From 9bdf58effe8eb0f77557443a5fe892d87e886c6e Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Tue, 16 Jul 2024 11:48:56 +0100 Subject: [PATCH 01/24] Adds video stabilization to camera - Adds support for video stabilization to camera: Adds getVideoStabilizationSupportedModes() and setVideoStabilizationMode() methods to CameraController. - Adds support for video stabilization to camera_avfoundation - Adds support for video stabilization to camera_android_camerax - Adds support for video stabilization to camera_platform_interface: - Adds getVideoStabilizationSupportedModes() and setVideoStabilizationMode() methods to CameraPlatform. - Adds VideoStabilizationMode enum to represent an abstraction of the available video stabilization modes, meant for Android and iOS, mapped as follows: /// Video stabilization is disabled. off, /// Standard video stabilization is enabled. /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_ON on Android /// and throws CameraException on iOS. on, /// Standard video stabilization is enabled. /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android /// (camera_android_camerax) and to AVCaptureVideoStabilizationModeStandard /// on iOS. standard, /// Cinematic video stabilization is enabled. /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android /// (camera_android_camerax) and to AVCaptureVideoStabilizationModeCinematic /// on iOS. cinematic, /// Extended cinematic video stabilization is enabled. /// Maps to AVCaptureVideoStabilizationModeCinematicExtended on iOS and /// throws CameraException on Android. cinematicExtended, --- packages/camera/camera/AUTHORS | 1 + packages/camera/camera/CHANGELOG.md | 4 + packages/camera/camera/lib/camera.dart | 1 + .../camera/lib/src/camera_controller.dart | 43 ++++ packages/camera/camera/pubspec.yaml | 8 +- .../camera/test/camera_preview_test.dart | 7 + packages/camera/camera/test/camera_test.dart | 144 ++++++++++++ .../camera/camera/test/camera_value_test.dart | 8 +- packages/camera/camera_android/CHANGELOG.md | 4 + packages/camera/camera_android/pubspec.yaml | 4 +- .../camera/camera_android_camerax/AUTHORS | 1 + .../camera_android_camerax/CHANGELOG.md | 4 + .../camerax/Camera2CameraInfoHostApiImpl.java | 27 ++- .../CaptureRequestOptionsHostApiImpl.java | 7 + .../camerax/GeneratedCameraXLibrary.java | 33 ++- .../camerax/Camera2CameraInfoTest.java | 51 +++++ .../camerax/CaptureRequestOptionsTest.java | 8 + .../lib/src/android_camera_camerax.dart | 83 +++++++ .../lib/src/camera2_camera_info.dart | 17 ++ .../lib/src/camera_info.dart | 11 + .../lib/src/camerax_library.g.dart | 30 +++ .../lib/src/capture_request_options.dart | 6 + .../pigeons/camerax_library.dart | 3 + .../camera_android_camerax/pubspec.yaml | 4 +- .../test/android_camera_camerax_test.dart | 215 ++++++++++++++++++ .../android_camera_camerax_test.mocks.dart | 11 + .../test/camera2_camera_info_test.dart | 70 ++++++ .../test/camera2_camera_info_test.mocks.dart | 10 + .../test/test_camerax_library.g.dart | 26 +++ packages/camera/camera_avfoundation/AUTHORS | 1 + .../camera/camera_avfoundation/CHANGELOG.md | 4 + .../camera_avfoundation/CameraPlugin.m | 19 ++ .../Sources/camera_avfoundation/FLTCam.m | 47 ++++ .../include/camera_avfoundation/FLTCam.h | 3 + .../include/camera_avfoundation/messages.g.h | 24 +- .../Sources/camera_avfoundation/messages.g.m | 71 +++++- .../lib/src/avfoundation_camera.dart | 62 +++++ .../lib/src/messages.g.dart | 72 +++++- .../camera_avfoundation/pigeons/messages.dart | 19 +- .../camera/camera_avfoundation/pubspec.yaml | 4 +- .../test/avfoundation_camera_test.dart | 112 +++++++++ .../test/avfoundation_camera_test.mocks.dart | 24 ++ .../camera/camera_platform_interface/AUTHORS | 1 + .../camera_platform_interface/CHANGELOG.md | 5 + .../method_channel/method_channel_camera.dart | 38 ++++ .../platform_interface/camera_platform.dart | 24 ++ .../lib/src/types/types.dart | 1 + .../src/types/video_stabilization_mode.dart | 66 ++++++ .../camera_platform_interface/pubspec.yaml | 2 +- .../method_channel_camera_test.dart | 145 ++++++++++++ .../types/video_stabilization_mode_test.dart | 49 ++++ packages/camera/camera_web/CHANGELOG.md | 4 + packages/camera/camera_web/pubspec.yaml | 4 +- packages/camera/camera_windows/CHANGELOG.md | 4 + packages/camera/camera_windows/pubspec.yaml | 4 +- 55 files changed, 1621 insertions(+), 29 deletions(-) create mode 100644 packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart create mode 100644 packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart diff --git a/packages/camera/camera/AUTHORS b/packages/camera/camera/AUTHORS index 493a0b4ef9c..605414ab7dc 100644 --- a/packages/camera/camera/AUTHORS +++ b/packages/camera/camera/AUTHORS @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> +Rui Craveiro diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 53a6c4ca1ad..d94b79cffec 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.0 + +* Adds support for video stabilization. + ## 0.11.0+1 * Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart index 507d6c7cded..3778c870071 100644 --- a/packages/camera/camera/lib/camera.dart +++ b/packages/camera/camera/lib/camera.dart @@ -12,6 +12,7 @@ export 'package:camera_platform_interface/camera_platform_interface.dart' FocusMode, ImageFormatGroup, ResolutionPreset, + VideoStabilizationMode, XFile; export 'src/camera_controller.dart'; diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 5f90a0a740a..7e544323511 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -54,6 +54,7 @@ class CameraValue { this.recordingOrientation, this.isPreviewPaused = false, this.previewPauseOrientation, + this.videoStabilizationMode = VideoStabilizationMode.off, }) : _isRecordingPaused = isRecordingPaused; /// Creates a new camera controller state for an uninitialized controller. @@ -72,6 +73,7 @@ class CameraValue { deviceOrientation: DeviceOrientation.portraitUp, isPreviewPaused: false, description: description, + videoStabilizationMode: VideoStabilizationMode.off, ); /// True after [CameraController.initialize] has completed successfully. @@ -148,6 +150,9 @@ class CameraValue { /// The properties of the camera device controlled by this controller. final CameraDescription description; + /// The video stabilization mode in + final VideoStabilizationMode videoStabilizationMode; + /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get @@ -171,6 +176,7 @@ class CameraValue { bool? isPreviewPaused, CameraDescription? description, Optional? previewPauseOrientation, + VideoStabilizationMode? videoStabilizationMode, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -198,6 +204,8 @@ class CameraValue { previewPauseOrientation: previewPauseOrientation == null ? this.previewPauseOrientation : previewPauseOrientation.orNull, + videoStabilizationMode: + videoStabilizationMode ?? this.videoStabilizationMode, ); } @@ -219,6 +227,7 @@ class CameraValue { 'recordingOrientation: $recordingOrientation, ' 'isPreviewPaused: $isPreviewPaused, ' 'previewPausedOrientation: $previewPauseOrientation, ' + 'videoStabilizationMode: $videoStabilizationMode, ' 'description: $description)'; } } @@ -689,6 +698,40 @@ class CameraController extends ValueNotifier { } } + /// Set the video stabilization mode for the selected camera. + /// + /// On Android (when using camera_android_camerax) and on iOS + /// the supplied [mode] value should be a mode in the list returned + /// by [getVideoStabilizationSupportedModes]. + /// + /// Throws a [CameraException] when a not supported video stabilization + /// mode is supplied. + Future setVideoStabilizationMode(VideoStabilizationMode mode) async { + _throwIfNotInitialized('setVideoStabilizationMode'); + try { + await CameraPlatform.instance.setVideoStabilizationMode(_cameraId, mode); + value = value.copyWith(videoStabilizationMode: mode); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets a list of video stabilization modes that are supported for the selected camera. + /// + /// Will return the list of supported video stabilization modes + /// on Android (when using camera_android_camerax package) and + /// on iOS. Will return an empty list on all other platforms. + Future> + getVideoStabilizationSupportedModes() { + _throwIfNotInitialized('isVideoStabilizationModeSupported'); + try { + return CameraPlatform.instance + .getVideoStabilizationSupportedModes(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + /// Sets the flash mode for taking pictures. Future setFlashMode(FlashMode mode) async { try { diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index f00a7e798f0..7f2ec933fe9 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,7 +4,7 @@ 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.0+1 +version: 0.12.0 environment: sdk: ^3.2.3 @@ -21,9 +21,9 @@ flutter: default_package: camera_web dependencies: - camera_android_camerax: ^0.6.5 - camera_avfoundation: ^0.9.15 - camera_platform_interface: ^2.6.0 + camera_android_camerax: ^0.7.0 + camera_avfoundation: ^0.10.0 + camera_platform_interface: ^2.9.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 4b5a2a5b516..74d6c6a1fdd 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -132,6 +132,13 @@ class FakeController extends ValueNotifier @override CameraDescription get description => value.description; + + @override + Future setVideoStabilizationMode(VideoStabilizationMode mode) async {} + + @override + Future> + getVideoStabilizationSupportedModes() async => []; } void main() { diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 0c6a319397e..bc9bb76e8f3 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1190,6 +1190,133 @@ void main() { .called(4); }); + test('getVideoStabilizationSupportedModes() returns empty list', () async { + // arrange + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance + .getVideoStabilizationSupportedModes(mockInitializeCamera)) + .thenAnswer((_) async => []); + + // act + final Iterable modes = + await cameraController.getVideoStabilizationSupportedModes(); + + // assert + expect(modes, []); + }); + + test('getVideoStabilizationSupportedModes() returns off', () async { + // arrange + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance + .getVideoStabilizationSupportedModes(mockInitializeCamera)) + .thenAnswer((_) async => + [VideoStabilizationMode.off]); + + // act + final Iterable modes = + await cameraController.getVideoStabilizationSupportedModes(); + + // assert + expect(modes, [VideoStabilizationMode.off]); + }); + + test('getVideoStabilizationSupportedModes() returns all modes', () async { + // arrange + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + + await cameraController.initialize(); + when(CameraPlatform.instance + .getVideoStabilizationSupportedModes(mockInitializeCamera)) + .thenAnswer((_) async => [ + VideoStabilizationMode.off, + VideoStabilizationMode.on, + VideoStabilizationMode.standard, + VideoStabilizationMode.cinematic, + VideoStabilizationMode.cinematicExtended, + ]); + + // act + final Iterable modes = + await cameraController.getVideoStabilizationSupportedModes(); + + // assert + expect(modes, [ + VideoStabilizationMode.off, + VideoStabilizationMode.on, + VideoStabilizationMode.standard, + VideoStabilizationMode.cinematic, + VideoStabilizationMode.cinematicExtended, + ]); + }); + + test('setVideoStabilizationMode() calls $CameraPlatform', () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.off); + + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.off)) + .called(1); + }); + + test( + 'setVideoStabilizationMode() throws $CameraException on $PlatformException', + () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, + VideoStabilizationMode.cinematicExtended)) + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + ), + ); + + expect( + cameraController.setVideoStabilizationMode( + VideoStabilizationMode.cinematicExtended), + throwsA(isA().having( + (CameraException error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); + test('pausePreview() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( @@ -1602,6 +1729,23 @@ class MockCameraPlatform extends Mock Invocation.method(#setExposureOffset, [cameraId, offset]), returnValue: Future.value(1.0), ) as Future; + + @override + Future> getVideoStabilizationSupportedModes( + int cameraId) { + return super.noSuchMethod( + Invocation.method( + #getVideoStabilizationSupportedModes, [cameraId]), + returnValue: Future>.value( + []), + ) as Future>; + } + + @override + Future setVideoStabilizationMode( + int cameraId, VideoStabilizationMode mode) async => + super.noSuchMethod(Invocation.method( + #setVideoStabilizationMode, [cameraId, mode])); } class MockCameraDescription extends CameraDescription { diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index dbb1ddcbf78..835dd59b55b 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -28,6 +28,7 @@ void main() { focusPointSupported: true, previewPauseOrientation: DeviceOrientation.portraitUp, description: FakeController.fakeDescription, + videoStabilizationMode: VideoStabilizationMode.cinematic, ); expect(cameraValue, isA()); @@ -47,6 +48,8 @@ void main() { expect(cameraValue.recordingOrientation, DeviceOrientation.portraitUp); expect(cameraValue.isPreviewPaused, false); expect(cameraValue.previewPauseOrientation, DeviceOrientation.portraitUp); + expect( + cameraValue.videoStabilizationMode, VideoStabilizationMode.cinematic); }); test('Can be created as uninitialized', () { @@ -70,6 +73,7 @@ void main() { expect(cameraValue.recordingOrientation, null); expect(cameraValue.isPreviewPaused, isFalse); expect(cameraValue.previewPauseOrientation, null); + expect(cameraValue.videoStabilizationMode, VideoStabilizationMode.off); }); test('Can be copied with isInitialized', () { @@ -94,6 +98,7 @@ void main() { expect(cameraValue.recordingOrientation, null); expect(cameraValue.isPreviewPaused, isFalse); expect(cameraValue.previewPauseOrientation, null); + expect(cameraValue.videoStabilizationMode, VideoStabilizationMode.off); }); test('Has aspectRatio after setting size', () { @@ -144,10 +149,11 @@ void main() { isPreviewPaused: true, previewPauseOrientation: DeviceOrientation.portraitUp, description: FakeController.fakeDescription, + videoStabilizationMode: VideoStabilizationMode.cinematicExtended, ); expect(cameraValue.toString(), - 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, isPreviewPaused: true, previewPausedOrientation: DeviceOrientation.portraitUp, description: CameraDescription(, CameraLensDirection.back, 0))'); + 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, isPreviewPaused: true, previewPausedOrientation: DeviceOrientation.portraitUp, videoStabilizationMode: VideoStabilizationMode.cinematicExtended, description: CameraDescription(, CameraLensDirection.back, 0))'); }); }); } diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index fa7a742c118..050d4d46744 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.9+9 + +* Updates camera_platform_interface lib to 2.9.0. + ## 0.10.9+8 * Removes unused code related to `maxVideoDuration`. diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 1f157f0aefe..f29bdb21d8a 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -3,7 +3,7 @@ description: Android implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.9+8 +version: 0.10.9+9 environment: sdk: ^3.4.0 @@ -19,7 +19,7 @@ flutter: dartPluginClass: AndroidCamera dependencies: - camera_platform_interface: ^2.6.0 + camera_platform_interface: ^2.9.0 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.2 diff --git a/packages/camera/camera_android_camerax/AUTHORS b/packages/camera/camera_android_camerax/AUTHORS index 557dff97933..7fc8bd920ea 100644 --- a/packages/camera/camera_android_camerax/AUTHORS +++ b/packages/camera/camera_android_camerax/AUTHORS @@ -4,3 +4,4 @@ # Name/Organization Google Inc. +Rui Craveiro \ No newline at end of file diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 9e8e4fc17c5..3b3d24ece90 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.0 + +* Adds video stabilization. + ## 0.6.7+1 * Updates README to remove references to `maxVideoDuration`, as it was never diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java index 76606b43efc..917635fd9c6 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java @@ -4,7 +4,6 @@ package io.flutter.plugins.camerax; -import android.content.Context; import android.hardware.camera2.CameraCharacteristics; import androidx.annotation.NonNull; import androidx.annotation.OptIn; @@ -14,6 +13,8 @@ import androidx.camera.core.CameraInfo; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Camera2CameraInfoHostApi; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -53,6 +54,15 @@ public Long getSensorOrientation(@NonNull Camera2CameraInfo camera2CameraInfo) { return Long.valueOf( camera2CameraInfo.getCameraCharacteristic(CameraCharacteristics.SENSOR_ORIENTATION)); } + + @NonNull + @OptIn(markerClass = ExperimentalCamera2Interop.class) + public int[] getAvailableVideoStabilizationModes(@NonNull Camera2CameraInfo camera2CameraInfo) { + int[] modes = + camera2CameraInfo.getCameraCharacteristic( + CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES); + return modes == null ? new int[] {} : modes; + } } /** @@ -60,7 +70,6 @@ public Long getSensorOrientation(@NonNull Camera2CameraInfo camera2CameraInfo) { * * @param binaryMessenger used to communicate with Dart over asynchronous messages * @param instanceManager maintains instances stored to communicate with attached Dart objects - * @param context {@link Context} used to retrieve {@code Executor} */ public Camera2CameraInfoHostApiImpl( @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager) { @@ -86,6 +95,7 @@ public Camera2CameraInfoHostApiImpl( @Override @NonNull + @OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class) public Long createFrom(@NonNull Long cameraInfoIdentifier) { final CameraInfo cameraInfo = Objects.requireNonNull(instanceManager.getInstance(cameraInfoIdentifier)); @@ -115,6 +125,19 @@ public Long getSensorOrientation(@NonNull Long identifier) { return proxy.getSensorOrientation(getCamera2CameraInfoInstance(identifier)); } + @NonNull + @Override + public List getAvailableVideoStabilizationModes(@NonNull Long identifier) { + + int[] lst = proxy.getAvailableVideoStabilizationModes(getCamera2CameraInfoInstance(identifier)); + List ret = new ArrayList(lst.length); + for (int i : lst) { + ret.add((long) i); + } + return ret; + } + + @OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class) private Camera2CameraInfo getCamera2CameraInfoInstance(@NonNull Long identifier) { return Objects.requireNonNull(instanceManager.getInstance(identifier)); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java index f76dd5422e7..af05e045cce 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsHostApiImpl.java @@ -51,6 +51,10 @@ public static class CaptureRequestOptionsProxy { builder.setCaptureRequestOption( (CaptureRequest.Key) optionKey, (Boolean) optionValue); break; + case CONTROL_VIDEO_STABILIZATION_MODE: + builder.setCaptureRequestOption( + (CaptureRequest.Key) optionKey, (int) optionValue); + break; default: throw new IllegalArgumentException( "The capture request key " @@ -69,6 +73,9 @@ private CaptureRequest.Key getCaptureRequestKey( case CONTROL_AE_LOCK: key = CaptureRequest.CONTROL_AE_LOCK; break; + case CONTROL_VIDEO_STABILIZATION_MODE: + key = CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE; + break; default: throw new IllegalArgumentException( "The capture request key is not currently supported by the plugin."); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java index 7adf02595ad..ff0bd8290d8 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java @@ -181,7 +181,8 @@ private VideoRecordEvent(final int index) { * Camera2. */ public enum CaptureRequestKeySupportedType { - CONTROL_AE_LOCK(0); + CONTROL_AE_LOCK(0), + CONTROL_VIDEO_STABILIZATION_MODE(1); final int index; @@ -4444,6 +4445,9 @@ public interface Camera2CameraInfoHostApi { @NonNull Long getSensorOrientation(@NonNull Long identifier); + @NonNull + List getAvailableVideoStabilizationModes(@NonNull Long identifier); + /** The codec used by Camera2CameraInfoHostApi. */ static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); @@ -4563,6 +4567,33 @@ static void setup( channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.Camera2CameraInfoHostApi.getAvailableVideoStabilizationModes", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + try { + List output = + api.getAvailableVideoStabilizationModes( + (identifierArg == null) ? null : identifierArg.longValue()); + wrapped.add(0, output); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java index a5ab10ff79b..4d6aac853c3 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java @@ -4,6 +4,10 @@ package io.flutter.plugins.camerax; +import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF; +import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON; +import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -17,6 +21,7 @@ import androidx.camera.camera2.interop.Camera2CameraInfo; import androidx.camera.core.CameraInfo; import io.flutter.plugin.common.BinaryMessenger; +import java.util.List; import java.util.Objects; import org.junit.After; import org.junit.Before; @@ -86,6 +91,52 @@ public void getSupportedHardwareLevel_returnsExpectedLevel() { hostApi.getSupportedHardwareLevel(camera2CameraInfoIdentifier).intValue()); } + @Test + public void getAvailableVideoStabilizationModes_returnsNone() { + final Camera2CameraInfoHostApiImpl hostApi = + new Camera2CameraInfoHostApiImpl(mock(BinaryMessenger.class), testInstanceManager); + final long camera2CameraInfoIdentifier = 3; + + testInstanceManager.addDartCreatedInstance(mockCamera2CameraInfo, camera2CameraInfoIdentifier); + when(mockCamera2CameraInfo.getCameraCharacteristic( + CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES)) + .thenReturn(new int[] {}); + + List returned = hostApi.getAvailableVideoStabilizationModes(camera2CameraInfoIdentifier); + assertEquals(0, returned.size()); + } + + @Test + public void getAvailableVideoStabilizationModes_returnsAll() { + // arrange + final Camera2CameraInfoHostApiImpl hostApi = + new Camera2CameraInfoHostApiImpl(mock(BinaryMessenger.class), testInstanceManager); + final long camera2CameraInfoIdentifier = 3; + + int[] expected = + new int[] { + CONTROL_VIDEO_STABILIZATION_MODE_OFF, + CONTROL_VIDEO_STABILIZATION_MODE_ON, + CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION, + }; + + testInstanceManager.addDartCreatedInstance(mockCamera2CameraInfo, camera2CameraInfoIdentifier); + when(mockCamera2CameraInfo.getCameraCharacteristic( + CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES)) + .thenReturn(expected); + + // act + int[] returned = + hostApi + .getAvailableVideoStabilizationModes(camera2CameraInfoIdentifier) + .stream() + .mapToInt(value -> value.intValue()) + .toArray(); + + // assert + assertArrayEquals(expected, returned); + } + @Test public void getCameraId_returnsExpectedId() { final Camera2CameraInfoHostApiImpl hostApi = diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CaptureRequestOptionsTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CaptureRequestOptionsTest.java index 9c3329978d4..912207470fa 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CaptureRequestOptionsTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CaptureRequestOptionsTest.java @@ -55,6 +55,7 @@ public void create_buildsExpectedCaptureKeyRequestOptionsWhenOptionsNonNull() { new HashMap() { { put(0L, false); + put(1L, 1); } }; @@ -71,6 +72,13 @@ public void create_buildsExpectedCaptureKeyRequestOptionsWhenOptionsNonNull() { .setCaptureRequestOption( eq(CaptureRequest.CONTROL_AE_LOCK), eq((Boolean) testValueForSupportedType)); break; + case CONTROL_VIDEO_STABILIZATION_MODE: + verify(mockBuilder) + .setCaptureRequestOption( + eq(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE), + eq((Integer) testValueForSupportedType)); + break; + default: throw new IllegalArgumentException( "The capture request key is not currently supported by the plugin."); diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 1d068eb24f1..27abb5b55c5 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -816,6 +816,89 @@ class AndroidCameraCameraX extends CameraPlatform { await cameraControl.setZoomRatio(zoom); } + @override + Future> getVideoStabilizationSupportedModes( + int cameraId) async { + final List modeIndexes = + await _getAvailableVideoStabilizationModeIndexes(); + + final List list = []; + for (final int ix in modeIndexes) { + final VideoStabilizationMode? mode = switch (ix) { + 0 => VideoStabilizationMode.off, + 1 => VideoStabilizationMode.on, + 2 => VideoStabilizationMode.standard, + _ => null, + }; + if (mode != null) { + list.add(mode); + } + } + return list; + } + + /// Set the video stabilization mode for the selected camera. + @override + Future setVideoStabilizationMode( + int cameraId, VideoStabilizationMode mode) async { + final int? controlMode = _getControlVideoStabilizationMode(mode); + + final List availableModes = + await _getAvailableVideoStabilizationModeIndexes(); + if (controlMode == null || !availableModes.contains(controlMode)) { + // TODO(ruicraveiro): add to future possible error codes documentation + // https://github.com/flutter/flutter/issues/69298 + throw CameraException('VIDEO_STABILIIZATION_ERROR', + 'Unavailable video stabilization mode.'); + } + + final CaptureRequestOptions captureRequestOptions = proxy + .createCaptureRequestOptions(<( + CaptureRequestKeySupportedType, + Object? + )>[ + ( + CaptureRequestKeySupportedType.controlVideoStabilizationMode, + controlMode + ) + ]); + + final Camera2CameraControl camera2Control = + proxy.getCamera2CameraControl(cameraControl); + await camera2Control.addCaptureRequestOptions(captureRequestOptions); + } + + /// Gets from Camera2 API the available video stabilization + /// modes. + /// See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES + Future> _getAvailableVideoStabilizationModeIndexes() async { + final CameraInfo? camInfo = cameraInfo; + if (camInfo == null) { + return []; + } + final Camera2CameraInfo cam2Info = + await proxy.getCamera2CameraInfo(camInfo); + + final List modeIndexes = + await cam2Info.getAvailableVideoStabilizationModes(); + + return modeIndexes; + } + + int? _getControlVideoStabilizationMode(VideoStabilizationMode mode) { + final int? controlMode = switch (mode) { + // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_OFF + VideoStabilizationMode.off => 0, + // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_ON + VideoStabilizationMode.on => 1, + // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION + VideoStabilizationMode.standard => 2, + VideoStabilizationMode.cinematic => null, + VideoStabilizationMode.cinematicExtended => null, + }; + return controlMode; + } + /// The ui orientation changed. @override Stream onDeviceOrientationChanged() { diff --git a/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart index 88b44238f3b..6f8cb2c7d38 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart @@ -48,6 +48,14 @@ class Camera2CameraInfo extends JavaObject { Future getSupportedHardwareLevel() => _api.getSupportedHardwareLevelFromInstance(this); + /// Retrieves the value of `CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES` + /// for the device to which this instance pertains to. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES + /// for more information. + Future> getAvailableVideoStabilizationModes() => + _api.getAvailableVideoStabilizationModesFromInstance(this); + /// Gets the camera ID. /// /// The ID may change based on the internal configuration of the camera to which @@ -91,6 +99,15 @@ class _Camera2CameraInfoHostApiImpl extends Camera2CameraInfoHostApi { return getSupportedHardwareLevel(identifier!); } + Future> getAvailableVideoStabilizationModesFromInstance( + Camera2CameraInfo instance) async { + final int? identifier = instanceManager.getIdentifier(instance); + final List modes = + await getAvailableVideoStabilizationModes(identifier!); + + return modes.where((int? e) => e != null).map((int? e) => e!).toList(); + } + Future getCameraIdFromInstance(Camera2CameraInfo instance) { final int? identifier = instanceManager.getIdentifier(instance); return getCameraId(identifier!); diff --git a/packages/camera/camera_android_camerax/lib/src/camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera_info.dart index 6c0a62f9fd5..ea5d51f37b2 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_info.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_info.dart @@ -100,6 +100,17 @@ class _CameraInfoHostApiImpl extends CameraInfoHostApi { return instanceManager.getInstanceWithWeakReference>( zoomStateIdentifier)!; } + + // /// Returns a value indicating whether the given video + // /// stabilization mode is supported for [CameraInfo] instance. + // Future isVideoStabilizationModeSupportedFromInstance( + // CameraInfo instance, VideoStabilizationMode mode) async { + // final int identifier = instanceManager.getIdentifier(instance)!; + + // final bool? result = + // await isVideoStabilizationModeSupported(identifier, mode.index); + // return result; + // } } /// Flutter API implementation of [CameraInfo]. diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart index a9461eaaae0..7919dc4e1e1 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart @@ -96,6 +96,7 @@ enum VideoRecordEvent { /// interoperability with Camera2. enum CaptureRequestKeySupportedType { controlAeLock, + controlVideoStabilizationMode, } class ResolutionInfo { @@ -3657,6 +3658,35 @@ class Camera2CameraInfoHostApi { return (replyList[0] as int?)!; } } + + Future> getAvailableVideoStabilizationModes( + int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getAvailableVideoStabilizationModes', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as List?)!.cast(); + } + } } abstract class Camera2CameraInfoFlutterApi { diff --git a/packages/camera/camera_android_camerax/lib/src/capture_request_options.dart b/packages/camera/camera_android_camerax/lib/src/capture_request_options.dart index 777d4a43370..00a13c28ccc 100644 --- a/packages/camera/camera_android_camerax/lib/src/capture_request_options.dart +++ b/packages/camera/camera_android_camerax/lib/src/capture_request_options.dart @@ -112,6 +112,12 @@ class _CaptureRequestOptionsHostApiImpl extends CaptureRequestOptionsHostApi { throw ArgumentError( 'A controlAeLock value must be specified as a bool, but a $valueRuntimeType was specified.'); } + case CaptureRequestKeySupportedType.controlVideoStabilizationMode: + if (valueRuntimeType != int) { + throw ArgumentError( + 'A controlVideoStabilizationMode value must be specified as an int, but a $valueRuntimeType was specified.'); + } + // This ignore statement is safe beause this error will be useful when // a new CaptureRequestKeySupportedType is being added, but the logic in // this method has not yet been updated. diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 949830db3e8..7107a4f9aca 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -172,6 +172,7 @@ class MeteringPointInfo { /// interoperability with Camera2. enum CaptureRequestKeySupportedType { controlAeLock, + controlVideoStabilizationMode, } @HostApi(dartHostTestHandler: 'TestInstanceManagerHostApi') @@ -568,6 +569,8 @@ abstract class Camera2CameraInfoHostApi { String getCameraId(int identifier); int getSensorOrientation(int identifier); + + List getAvailableVideoStabilizationModes(int identifier); } @FlutterApi() diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index da774124a3a..b2907329c76 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.6.7+1 +version: 0.7.0 environment: sdk: ^3.4.0 @@ -19,7 +19,7 @@ flutter: dependencies: async: ^2.5.0 - camera_platform_interface: ^2.6.0 + camera_platform_interface: ^2.9.0 flutter: sdk: flutter meta: ^1.7.0 diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 1f9ea762ce2..380bfcbe22e 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -2317,6 +2317,221 @@ void main() { verify(mockCameraControl.setZoomRatio(zoomRatio)); }); + test('setVideoStabilizationMode sets mode expected', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 78; + + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + + final MockCameraControl mockCameraControl = MockCameraControl(); + final MockCamera2CameraControl mockCamera2CameraControl = + MockCamera2CameraControl(); + + // Set directly for test versus calling createCamera. + camera.camera = MockCamera(); + camera.cameraControl = mockCameraControl; + camera.cameraInfo = mockCameraInfo; + + when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) + .thenAnswer( + (_) async { + return [0]; + }, + ); + + camera.proxy = CameraXProxy( + getCamera2CameraControl: (CameraControl cameraControl) => + cameraControl == mockCameraControl + ? mockCamera2CameraControl + : Camera2CameraControl.detached(cameraControl: cameraControl), + getCamera2CameraInfo: (CameraInfo cameraInfo) async { + return cameraInfo == mockCameraInfo + ? mockCamera2CameraInfo + : Camera2CameraInfo.detached(); + }, + createCaptureRequestOptions: + (List<(CaptureRequestKeySupportedType, Object?)> options) => + CaptureRequestOptions.detached(requestedOptions: options), + ); + + // Test off. + await camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.off); + + final VerificationResult verificationResult = + verify(mockCamera2CameraControl.addCaptureRequestOptions(captureAny)); + final CaptureRequestOptions capturedCaptureRequestOptions = + verificationResult.captured.single as CaptureRequestOptions; + final List<(CaptureRequestKeySupportedType, Object?)> requestedOptions = + capturedCaptureRequestOptions.requestedOptions; + expect(requestedOptions.length, equals(1)); + expect(requestedOptions.first.$1, + equals(CaptureRequestKeySupportedType.controlVideoStabilizationMode)); + expect(requestedOptions.first.$2, equals(0)); + }); + + test( + 'setVideoStabilizationMode throws CameraException when mode not available', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 78; + + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + + final MockCameraControl mockCameraControl = MockCameraControl(); + + // Set directly for test versus calling createCamera. + camera.camera = MockCamera(); + camera.cameraInfo = mockCameraInfo; + + when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) + .thenAnswer( + (_) async { + return [0]; + }, + ); + + camera.cameraControl = mockCameraControl; + + camera.proxy = CameraXProxy( + getCamera2CameraInfo: (CameraInfo cameraInfo) async { + return cameraInfo == mockCameraInfo + ? mockCamera2CameraInfo + : Camera2CameraInfo.detached(); + }, + ); + + expect( + () => camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.standard), + throwsA(isA())); + }); + + test('setVideoStabilizationMode throws CameraException when mode not mapped', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 78; + + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + + final MockCameraControl mockCameraControl = MockCameraControl(); + + // Set directly for test versus calling createCamera. + camera.camera = MockCamera(); + camera.cameraInfo = mockCameraInfo; + + when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) + .thenAnswer( + (_) async { + return [0, 1, 2]; + }, + ); + + camera.cameraControl = mockCameraControl; + + camera.proxy = CameraXProxy( + getCamera2CameraInfo: (CameraInfo cameraInfo) async { + return cameraInfo == mockCameraInfo + ? mockCamera2CameraInfo + : Camera2CameraInfo.detached(); + }, + ); + + expect( + () => camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.cinematic), + throwsA(isA())); + expect( + () => camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.cinematicExtended), + throwsA(isA())); + }); + + test('getVideoStabilizationMode returns no available mode', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 78; + + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + + final MockCameraControl mockCameraControl = MockCameraControl(); + + // Set directly for test versus calling createCamera. + camera.camera = MockCamera(); + camera.cameraInfo = mockCameraInfo; + + when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) + .thenAnswer( + (_) async { + return []; + }, + ); + + camera.cameraControl = mockCameraControl; + + // Tell plugin to create detached Camera2CameraControl and + // CaptureRequestOptions instances for testing. + camera.proxy = CameraXProxy( + getCamera2CameraInfo: (CameraInfo cameraInfo) async { + return cameraInfo == mockCameraInfo + ? mockCamera2CameraInfo + : Camera2CameraInfo.detached(); + }, + ); + + final Iterable modes = + await camera.getVideoStabilizationSupportedModes(cameraId); + + expect(modes, isEmpty); + }); + + test('getVideoStabilizationMode returns all available modes', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 78; + + final MockCameraInfo mockCameraInfo = MockCameraInfo(); + final MockCamera2CameraInfo mockCamera2CameraInfo = MockCamera2CameraInfo(); + + final MockCameraControl mockCameraControl = MockCameraControl(); + + // Set directly for test versus calling createCamera. + camera.camera = MockCamera(); + camera.cameraInfo = mockCameraInfo; + + when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) + .thenAnswer( + (_) async { + return [0, 1, 2]; + }, + ); + + camera.cameraControl = mockCameraControl; + + // Tell plugin to create detached Camera2CameraControl and + // CaptureRequestOptions instances for testing. + camera.proxy = CameraXProxy( + getCamera2CameraInfo: (CameraInfo cameraInfo) async { + return cameraInfo == mockCameraInfo + ? mockCamera2CameraInfo + : Camera2CameraInfo.detached(); + }, + ); + + final Iterable modes = + await camera.getVideoStabilizationSupportedModes(cameraId); + + expect( + modes, + orderedEquals([ + VideoStabilizationMode.off, + VideoStabilizationMode.on, + VideoStabilizationMode.standard, + ])); + }); + test( 'onStreamedFrameAvailable emits CameraImageData when picked up from CameraImageData stream controller', () async { diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart index 316b1275969..36936c30413 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -707,6 +707,17 @@ class MockCamera2CameraInfo extends _i1.Mock implements _i26.Camera2CameraInfo { returnValueForMissingStub: _i17.Future.value(0), ) as _i17.Future); + @override + _i17.Future> getAvailableVideoStabilizationModes() => + (super.noSuchMethod( + Invocation.method( + #getAvailableVideoStabilizationModes, + [], + ), + returnValue: _i17.Future>.value([]), + returnValueForMissingStub: _i17.Future>.value([]), + ) as _i17.Future>); + @override _i17.Future getCameraId() => (super.noSuchMethod( Invocation.method( diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart index 0766eee37e8..0d5c0224a81 100644 --- a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart +++ b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart @@ -110,6 +110,76 @@ void main() { verify(mockApi.getSupportedHardwareLevel(camera2CameraInfoId)); }); + test( + 'getAvailableVideoStabilizationModes makes call to retrieve no available video stabilization modes', + () async { + // Arrange + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + const int camera2CameraInfoId = 9; + + instanceManager.addHostCreatedInstance( + camera2CameraInfo, + camera2CameraInfoId, + onCopy: (_) => Camera2CameraInfo.detached(), + ); + + const List expectedModes = []; + when(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)) + .thenReturn(expectedModes); + + // Act + final List returnedModes = + await camera2CameraInfo.getAvailableVideoStabilizationModes(); + + // Assert + expect(returnedModes, equals(expectedModes)); + verify(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)); + }); + + test( + 'getAvailableVideoStabilizationModes makes call to retrieve all available video stabilization modes', + () async { + // Arrange + final MockTestCamera2CameraInfoHostApi mockApi = + MockTestCamera2CameraInfoHostApi(); + TestCamera2CameraInfoHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.detached( + instanceManager: instanceManager, + ); + const int camera2CameraInfoId = 9; + + instanceManager.addHostCreatedInstance( + camera2CameraInfo, + camera2CameraInfoId, + onCopy: (_) => Camera2CameraInfo.detached(), + ); + + const List expectedModes = [0, 1, 2]; + when(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)) + .thenReturn(expectedModes); + + // Act + final List returnedModes = + await camera2CameraInfo.getAvailableVideoStabilizationModes(); + + // Assert + expect(returnedModes, equals(expectedModes)); + verify(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)); + }); + test('getCameraId makes call to retrieve camera ID', () async { final MockTestCamera2CameraInfoHostApi mockApi = MockTestCamera2CameraInfoHostApi(); diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart index 88b219f28cf..97a5140d0f9 100644 --- a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.mocks.dart @@ -99,6 +99,16 @@ class MockTestCamera2CameraInfoHostApi extends _i1.Mock ), returnValue: 0, ) as int); + + @override + List getAvailableVideoStabilizationModes(int? identifier) => + (super.noSuchMethod( + Invocation.method( + #getAvailableVideoStabilizationModes, + [identifier], + ), + returnValue: [], + ) as List); } /// A class which mocks [TestInstanceManagerHostApi]. diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart index 32235b878f3..6d7f7f1b94f 100644 --- a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart @@ -2487,6 +2487,8 @@ abstract class TestCamera2CameraInfoHostApi { int getSensorOrientation(int identifier); + List getAvailableVideoStabilizationModes(int identifier); + static void setup(TestCamera2CameraInfoHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -2579,5 +2581,29 @@ abstract class TestCamera2CameraInfoHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Camera2CameraInfoHostApi.getAvailableVideoStabilizationModes', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoHostApi.getAvailableVideoStabilizationModes was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.Camera2CameraInfoHostApi.getAvailableVideoStabilizationModes was null, expected non-null int.'); + final List output = + api.getAvailableVideoStabilizationModes(arg_identifier!); + return [output]; + }); + } + } } } diff --git a/packages/camera/camera_avfoundation/AUTHORS b/packages/camera/camera_avfoundation/AUTHORS index 493a0b4ef9c..1d9825e2e03 100644 --- a/packages/camera/camera_avfoundation/AUTHORS +++ b/packages/camera/camera_avfoundation/AUTHORS @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> +Rui Craveiro \ No newline at end of file diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 767f9a75e0a..2d35928d2b8 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.0 + +* Adds video stabilization. + ## 0.9.17 * Adds Swift Package Manager compatibility. diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m index 151883c71e9..cfd5fd1497b 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m @@ -420,6 +420,25 @@ - (void)disposeCamera:(NSInteger)cameraId }); } +- (void)setVideoStabilizationMode:(FCPPlatformVideoStabilizationMode)mode + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + dispatch_async(self.captureSessionQueue, ^{ + [weakSelf.camera setVideoStabilizationMode:mode withCompletion:completion]; + }); +} + +- (void)isVideoStabilizationModeSupported:(FCPPlatformVideoStabilizationMode)mode + completion:(nonnull void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion { + __weak typeof(self) weakSelf = self; + + dispatch_async(self.captureSessionQueue, ^{ + bool isSupported = [weakSelf.camera isVideoStabilizationModeSupported:mode]; + completion(@(isSupported), nil); + }); +} + #pragma mark Private // This must be called on captureSessionQueue. It is extracted from diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m index 6cfe47b86fb..3eba183f9ad 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m @@ -1199,6 +1199,53 @@ - (void)setZoomLevel:(CGFloat)zoom withCompletion:(void (^)(FlutterError *_Nulla completion(nil); } +- (void)setVideoStabilizationMode:(FCPPlatformVideoStabilizationMode)mode + withCompletion:(void (^)(FlutterError *_Nullable))completion { + AVCaptureVideoStabilizationMode stabilizationMode = getAvCaptureVideoStabilizationMode(mode); + + if (![_captureDevice.activeFormat isVideoStabilizationModeSupported:stabilizationMode]) { + completion([FlutterError errorWithCode:@"VIDEO_STABILIIZATION_ERROR" + message:@"Unavailable video stabilization mode." + details:nil]); + return; + } + + AVCaptureConnection *connection = [_captureVideoOutput connectionWithMediaType:AVMediaTypeVideo]; + + connection.preferredVideoStabilizationMode = stabilizationMode; + + completion(nil); +} + +- (BOOL)isVideoStabilizationModeSupported:(FCPPlatformVideoStabilizationMode)mode { + AVCaptureVideoStabilizationMode stabilizationMode = getAvCaptureVideoStabilizationMode(mode); + return [_captureDevice.activeFormat isVideoStabilizationModeSupported:stabilizationMode]; +} + + +AVCaptureVideoStabilizationMode getAvCaptureVideoStabilizationMode( + FCPPlatformVideoStabilizationMode videoStabilizationMode) { + switch (videoStabilizationMode) { + case FCPPlatformVideoStabilizationModeOff: + return AVCaptureVideoStabilizationModeOff; + case FCPPlatformVideoStabilizationModeStandard: + return AVCaptureVideoStabilizationModeStandard; + + case FCPPlatformVideoStabilizationModeCinematic: + return AVCaptureVideoStabilizationModeCinematic; + + case FCPPlatformVideoStabilizationModeCinematicExtended: + if (@available(iOS 13.0, *)) { + return AVCaptureVideoStabilizationModeCinematicExtended; + } else { + return AVCaptureVideoStabilizationModeCinematic; + } + + default: + return AVCaptureVideoStabilizationModeOff; + } +} + - (CGFloat)minimumAvailableZoomFactor { return _captureDevice.minAvailableVideoZoomFactor; } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h index d8f97926b77..4fb685725af 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h @@ -113,6 +113,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)startImageStreamWithMessenger:(NSObject *)messenger; - (void)stopImageStream; - (void)setZoomLevel:(CGFloat)zoom withCompletion:(void (^)(FlutterError *_Nullable))completion; +- (void)setVideoStabilizationMode:(FCPPlatformVideoStabilizationMode)mode + withCompletion:(void (^)(FlutterError *_Nullable))completion; +- (bool)isVideoStabilizationModeSupported:(FCPPlatformVideoStabilizationMode)mode; - (void)setUpCaptureSessionForAudio; @end diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/messages.g.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/messages.g.h index 8e3dd431443..7939423c423 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/messages.g.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/messages.g.h @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v18.0.0), do not edit directly. +// Autogenerated from Pigeon (v18.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -114,6 +114,19 @@ typedef NS_ENUM(NSUInteger, FCPPlatformResolutionPreset) { - (instancetype)initWithValue:(FCPPlatformResolutionPreset)value; @end +typedef NS_ENUM(NSUInteger, FCPPlatformVideoStabilizationMode) { + FCPPlatformVideoStabilizationModeOff = 0, + FCPPlatformVideoStabilizationModeStandard = 1, + FCPPlatformVideoStabilizationModeCinematic = 2, + FCPPlatformVideoStabilizationModeCinematicExtended = 3, +}; + +/// Wrapper for FCPPlatformVideoStabilizationMode to allow for nullability. +@interface FCPPlatformVideoStabilizationModeBox : NSObject +@property(nonatomic, assign) FCPPlatformVideoStabilizationMode value; +- (instancetype)initWithValue:(FCPPlatformVideoStabilizationMode)value; +@end + @class FCPPlatformCameraDescription; @class FCPPlatformCameraState; @class FCPPlatformMediaSettings; @@ -263,9 +276,16 @@ NSObject *FCPCameraApiGetCodec(void); - (void)getMaximumZoomLevel:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; /// Sets the zoom factor. - (void)setZoomLevel:(double)zoom completion:(void (^)(FlutterError *_Nullable))completion; +/// Sets the video stabilization mode. +- (void)setVideoStabilizationMode:(FCPPlatformVideoStabilizationMode)mode + completion:(void (^)(FlutterError *_Nullable))completion; +/// Sets the video stabilization mode. +- (void)isVideoStabilizationModeSupported:(FCPPlatformVideoStabilizationMode)mode + completion:(void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion; /// Pauses streaming of preview frames. - (void)pausePreviewWithCompletion:(void (^)(FlutterError *_Nullable))completion; -/// Resumes a previously paused preview stream. +/// Resumes a previously paused preview stream.Æ’ - (void)resumePreviewWithCompletion:(void (^)(FlutterError *_Nullable))completion; /// Changes the camera used while recording video. /// diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/messages.g.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/messages.g.m index 3b4355f0709..e0b092fae7f 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/messages.g.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/messages.g.m @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v18.0.0), do not edit directly. +// Autogenerated from Pigeon (v18.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "./include/camera_avfoundation/messages.g.h" @@ -120,6 +120,16 @@ - (instancetype)initWithValue:(FCPPlatformResolutionPreset)value { } @end +@implementation FCPPlatformVideoStabilizationModeBox +- (instancetype)initWithValue:(FCPPlatformVideoStabilizationMode)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + @interface FCPPlatformCameraDescription () + (FCPPlatformCameraDescription *)fromList:(NSArray *)list; + (nullable FCPPlatformCameraDescription *)nullableFromList:(NSArray *)list; @@ -191,7 +201,7 @@ + (instancetype)makeWithPreviewSize:(FCPPlatformSize *)previewSize } + (FCPPlatformCameraState *)fromList:(NSArray *)list { FCPPlatformCameraState *pigeonResult = [[FCPPlatformCameraState alloc] init]; - pigeonResult.previewSize = [FCPPlatformSize nullableFromList:(GetNullableObjectAtIndex(list, 0))]; + pigeonResult.previewSize = GetNullableObjectAtIndex(list, 0); pigeonResult.exposureMode = [GetNullableObjectAtIndex(list, 1) integerValue]; pigeonResult.focusMode = [GetNullableObjectAtIndex(list, 2) integerValue]; pigeonResult.exposurePointSupported = [GetNullableObjectAtIndex(list, 3) boolValue]; @@ -203,7 +213,7 @@ + (nullable FCPPlatformCameraState *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - (self.previewSize ? [self.previewSize toList] : [NSNull null]), + self.previewSize ?: [NSNull null], @(self.exposureMode), @(self.focusMode), @(self.exposurePointSupported), @@ -1014,6 +1024,61 @@ void SetUpFCPCameraApiWithSuffix(id binaryMessenger, [channel setMessageHandler:nil]; } } + /// Sets the video stabilization mode. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.setVideoStabilizationMode", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(setVideoStabilizationMode:completion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(setVideoStabilizationMode:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FCPPlatformVideoStabilizationMode arg_mode = + [GetNullableObjectAtIndex(args, 0) integerValue]; + [api setVideoStabilizationMode:arg_mode + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Sets the video stabilization mode. + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.camera_avfoundation." + @"CameraApi.isVideoStabilizationModeSupported", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FCPCameraApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(isVideoStabilizationModeSupported:completion:)], + @"FCPCameraApi api (%@) doesn't respond to " + @"@selector(isVideoStabilizationModeSupported:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + FCPPlatformVideoStabilizationMode arg_mode = + [GetNullableObjectAtIndex(args, 0) integerValue]; + [api isVideoStabilizationModeSupported:arg_mode + completion:^(NSNumber *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } /// Pauses streaming of preview frames. { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index d3d5bd8fe4f..5b04ddbb652 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -368,6 +368,44 @@ class AVFoundationCamera extends CameraPlatform { } } + @override + Future setVideoStabilizationMode( + int cameraId, VideoStabilizationMode mode) async { + try { + final PlatformVideoStabilizationMode? platformMode = + _pigeonVideoStabilizationMode(mode); + if (platformMode == null) { + // TODO(ruicraveiro): add to future possible error codes documentation + // https://github.com/flutter/flutter/issues/69298 + throw CameraException('VIDEO_STABILIIZATION_ERROR', + 'Unavailable video stabilization mode.'); + } + await _hostApi.setVideoStabilizationMode(platformMode); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + @override + Future> getVideoStabilizationSupportedModes( + int cameraId) async { + final Set ret = {}; + + for (final VideoStabilizationMode mode in VideoStabilizationMode.values) { + final PlatformVideoStabilizationMode? platformMode = + _pigeonVideoStabilizationMode(mode); + if (platformMode != null) { + final bool isSupported = + await _hostApi.isVideoStabilizationModeSupported(platformMode); + if (isSupported) { + ret.add(mode); + } + } + } + + return ret; + } + @override Future pausePreview(int cameraId) async { await _hostApi.pausePreview(); @@ -480,6 +518,30 @@ class AVFoundationCamera extends CameraPlatform { return PlatformResolutionPreset.max; } + /// Returns a [ResolutionPreset]'s Pigeon representation. + PlatformVideoStabilizationMode? _pigeonVideoStabilizationMode( + VideoStabilizationMode videoStabilizationMode) { + switch (videoStabilizationMode) { + case VideoStabilizationMode.off: + return PlatformVideoStabilizationMode.off; + case VideoStabilizationMode.on: + return null; + case VideoStabilizationMode.standard: + return PlatformVideoStabilizationMode.standard; + case VideoStabilizationMode.cinematic: + return PlatformVideoStabilizationMode.cinematic; + case VideoStabilizationMode.cinematicExtended: + return PlatformVideoStabilizationMode.cinematicExtended; + } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return PlatformVideoStabilizationMode.cinematic; + } + /// Returns an [ImageFormatGroup]'s Pigeon representation. PlatformImageFormatGroup _pigeonImageFormat(ImageFormatGroup format) { switch (format) { diff --git a/packages/camera/camera_avfoundation/lib/src/messages.g.dart b/packages/camera/camera_avfoundation/lib/src/messages.g.dart index 4290eb02ed2..860a5f08680 100644 --- a/packages/camera/camera_avfoundation/lib/src/messages.g.dart +++ b/packages/camera/camera_avfoundation/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v18.0.0), do not edit directly. +// Autogenerated from Pigeon (v18.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -84,6 +84,13 @@ enum PlatformResolutionPreset { max, } +enum PlatformVideoStabilizationMode { + off, + standard, + cinematic, + cinematicExtended, +} + class PlatformCameraDescription { PlatformCameraDescription({ required this.name, @@ -138,7 +145,7 @@ class PlatformCameraState { Object encode() { return [ - previewSize.encode(), + previewSize, exposureMode.index, focusMode.index, exposurePointSupported, @@ -149,7 +156,7 @@ class PlatformCameraState { static PlatformCameraState decode(Object result) { result as List; return PlatformCameraState( - previewSize: PlatformSize.decode(result[0]! as List), + previewSize: result[0]! as PlatformSize, exposureMode: PlatformExposureMode.values[result[1]! as int], focusMode: PlatformFocusMode.values[result[2]! as int], exposurePointSupported: result[3]! as bool, @@ -1003,6 +1010,63 @@ class CameraApi { } } + /// Sets the video stabilization mode. + Future setVideoStabilizationMode( + PlatformVideoStabilizationMode mode) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.setVideoStabilizationMode$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([mode.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + /// Sets the video stabilization mode. + Future isVideoStabilizationModeSupported( + PlatformVideoStabilizationMode mode) async { + final String __pigeon_channelName = + 'dev.flutter.pigeon.camera_avfoundation.CameraApi.isVideoStabilizationModeSupported$__pigeon_messageChannelSuffix'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([mode.index]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + /// Pauses streaming of preview frames. Future pausePreview() async { final String __pigeon_channelName = @@ -1028,7 +1092,7 @@ class CameraApi { } } - /// Resumes a previously paused preview stream. + /// Resumes a previously paused preview stream.Æ’ Future resumePreview() async { final String __pigeon_channelName = 'dev.flutter.pigeon.camera_avfoundation.CameraApi.resumePreview$__pigeon_messageChannelSuffix'; diff --git a/packages/camera/camera_avfoundation/pigeons/messages.dart b/packages/camera/camera_avfoundation/pigeons/messages.dart index c50ecc33e40..fba8b2266ac 100644 --- a/packages/camera/camera_avfoundation/pigeons/messages.dart +++ b/packages/camera/camera_avfoundation/pigeons/messages.dart @@ -79,6 +79,13 @@ enum PlatformResolutionPreset { max, } +enum PlatformVideoStabilizationMode { + off, + standard, + cinematic, + cinematicExtended, +} + // Pigeon version of CameraDescription. class PlatformCameraDescription { PlatformCameraDescription({ @@ -289,11 +296,21 @@ abstract class CameraApi { @ObjCSelector('setZoomLevel:') void setZoomLevel(double zoom); + /// Sets the video stabilization mode. + @async + @ObjCSelector('setVideoStabilizationMode:') + void setVideoStabilizationMode(PlatformVideoStabilizationMode mode); + + /// Sets the video stabilization mode. + @async + @ObjCSelector('isVideoStabilizationModeSupported:') + bool isVideoStabilizationModeSupported(PlatformVideoStabilizationMode mode); + /// Pauses streaming of preview frames. @async void pausePreview(); - /// Resumes a previously paused preview stream. + /// Resumes a previously paused preview stream.Æ’ @async void resumePreview(); diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index c00b8d68df0..47b9aa08a4b 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.17 +version: 0.10.0 environment: sdk: ^3.2.3 @@ -17,7 +17,7 @@ flutter: dartPluginClass: AVFoundationCamera dependencies: - camera_platform_interface: ^2.7.0 + camera_platform_interface: ^2.9.0 flutter: sdk: flutter stream_transform: ^2.0.0 diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index df04b1a8e8e..b7018cdac4d 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -578,6 +578,118 @@ void main() { verify(mockApi.setFocusMode(PlatformFocusMode.locked)); }); + test('Should set video stabilization mode to off', () async { + await camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.off); + + verify(mockApi + .setVideoStabilizationMode(PlatformVideoStabilizationMode.off)); + }); + + test( + 'Should throw CameraException when off video stabilization mode is set', + () async { + const String code = 'VIDEO_STABILIIZATION_ERROR'; + const String message = 'Unavailable video stabilization mode.'; + + expect( + () => camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.on), + throwsA(isA() + .having((CameraException e) => e.code, 'code', code) + .having((CameraException e) => e.description, 'description', + message))); + }); + + test('Should set video stabilization mode to standard', () async { + await camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.standard); + + verify(mockApi + .setVideoStabilizationMode(PlatformVideoStabilizationMode.standard)); + }); + + test('Should set video stabilization mode to cinematic', () async { + await camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.cinematic); + + verify(mockApi + .setVideoStabilizationMode(PlatformVideoStabilizationMode.cinematic)); + }); + + test('Should set video stabilization mode to cinematicExtended', () async { + await camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.cinematicExtended); + + verify(mockApi.setVideoStabilizationMode( + PlatformVideoStabilizationMode.cinematicExtended)); + }); + + test('Should get no video stabilization mode', () async { + when(mockApi.isVideoStabilizationModeSupported(any)) + .thenAnswer((_) async => false); + + final Iterable modes = + await camera.getVideoStabilizationSupportedModes(cameraId); + + expect(modes, isEmpty); + }); + + test('Should get off and standard video stabilization modes', () async { + when(mockApi.isVideoStabilizationModeSupported( + PlatformVideoStabilizationMode.off)) + .thenAnswer((_) async => true); + when(mockApi.isVideoStabilizationModeSupported( + PlatformVideoStabilizationMode.standard)) + .thenAnswer((_) async => true); + when(mockApi.isVideoStabilizationModeSupported( + PlatformVideoStabilizationMode.cinematic)) + .thenAnswer((_) async => false); + when(mockApi.isVideoStabilizationModeSupported( + PlatformVideoStabilizationMode.cinematicExtended)) + .thenAnswer((_) async => false); + + final List modes = + (await camera.getVideoStabilizationSupportedModes(cameraId)).toList(); + + expect(modes, [ + VideoStabilizationMode.off, + VideoStabilizationMode.standard, + ]); + }); + + test('Should get all video stabilization modes', () async { + when(mockApi.isVideoStabilizationModeSupported(any)) + .thenAnswer((_) async => true); + + final List modes = + (await camera.getVideoStabilizationSupportedModes(cameraId)).toList(); + + expect(modes, [ + VideoStabilizationMode.off, + VideoStabilizationMode.standard, + VideoStabilizationMode.cinematic, + VideoStabilizationMode.cinematicExtended, + ]); + }); + + test( + 'Should throw CameraException when unavailable video stabilization mode is set', + () async { + const String code = 'VIDEO_STABILIIZATION_ERROR'; + const String message = 'Unavailable video stabilization mode error'; + when(mockApi.setVideoStabilizationMode(any)).thenAnswer( + (_) async => throw PlatformException(code: code, message: message)); + + expect( + () => camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.cinematic), + throwsA(isA() + .having((CameraException e) => e.code, 'code', code) + .having((CameraException e) => e.description, 'description', + message))); + }); + test('Should set the focus point to a value', () async { const Point point = Point(0.4, 0.6); await camera.setFocusPoint(cameraId, point); diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart index 729b39f7f28..8a5d4bf2c60 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.mocks.dart @@ -338,6 +338,30 @@ class MockCameraApi extends _i1.Mock implements _i2.CameraApi { returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); + @override + _i3.Future setVideoStabilizationMode( + _i2.PlatformVideoStabilizationMode? mode) => + (super.noSuchMethod( + Invocation.method( + #setVideoStabilizationMode, + [mode], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future isVideoStabilizationModeSupported( + _i2.PlatformVideoStabilizationMode? mode) => + (super.noSuchMethod( + Invocation.method( + #isVideoStabilizationModeSupported, + [mode], + ), + returnValue: _i3.Future.value(false), + returnValueForMissingStub: _i3.Future.value(false), + ) as _i3.Future); + @override _i3.Future pausePreview() => (super.noSuchMethod( Invocation.method( diff --git a/packages/camera/camera_platform_interface/AUTHORS b/packages/camera/camera_platform_interface/AUTHORS index 0d1bfa6a90c..d21929d105f 100644 --- a/packages/camera/camera_platform_interface/AUTHORS +++ b/packages/camera/camera_platform_interface/AUTHORS @@ -65,3 +65,4 @@ Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> Mairramer +Rui Craveiro \ No newline at end of file diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 43d3e63c42b..4fe3dbf32f1 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,9 +1,14 @@ +## 2.9.0 + +* Adds support for video stabilization. + ## 2.8.0 * Deprecates `maxVideoDuration`/`maxDuration`, as it was never implemented on most platforms, and there is no plan to implement it in the future. * Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 2.7.4 * Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index e38df486533..229d36222c6 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -502,6 +502,44 @@ class MethodChannelCamera extends CameraPlatform { } } + @override + Future> getVideoStabilizationSupportedModes( + int cameraId) async { + try { + final List? modes = await _channel.invokeMethod>( + 'getVideoStabilizationSupportedModes', + { + 'cameraId': cameraId, + }, + ); + + if (modes == null) { + return []; + } + return modes + .map((Object? e) => deserializeVideoStabilizationMode(e! as String)); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the video stabilization mode for the selected camera + @override + Future setVideoStabilizationMode( + int cameraId, VideoStabilizationMode mode) async { + try { + await _channel.invokeMethod( + 'setVideoStabilizationMode', + { + 'cameraId': cameraId, + 'mode': mode.index, + }, + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + @override Future pausePreview(int cameraId) async { await _channel.invokeMethod( diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 218de02c635..c991b323bb6 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -272,6 +272,30 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('setZoomLevel() is not implemented.'); } + /// Gets a list of video stabilization modes that are supported for the selected camera. + /// + /// Will return the list of supported video stabilization modes + /// on Android (when using camera_android_camerax package) and + /// on iOS. Will return an empty list on all other platforms. + Future> getVideoStabilizationSupportedModes( + int cameraId) async { + return []; + } + + /// Sets the video stabilization mode for the selected camera. + /// + /// On Android (when using camera_android_camerax) and on iOS + /// the supplied [mode] value should be a mode in the list returned + /// by [getVideoStabilizationSupportedModes]. + /// + /// Throws a [CameraException] when a not supported video stabilization + /// mode is supplied. + Future setVideoStabilizationMode( + int cameraId, VideoStabilizationMode mode) async { + throw CameraException( + 'not_supported', 'setVideoStabilizationMode() is not implemented.'); + } + /// Pause the active preview on the current frame for the selected camera. Future pausePreview(int cameraId) { throw UnimplementedError('pausePreview() is not implemented.'); diff --git a/packages/camera/camera_platform_interface/lib/src/types/types.dart b/packages/camera/camera_platform_interface/lib/src/types/types.dart index e56b9b4c49a..34e906a2808 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/types.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/types.dart @@ -13,3 +13,4 @@ export 'image_format_group.dart'; export 'media_settings.dart'; export 'resolution_preset.dart'; export 'video_capture_options.dart'; +export 'video_stabilization_mode.dart'; diff --git a/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart new file mode 100644 index 00000000000..01018acac44 --- /dev/null +++ b/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart @@ -0,0 +1,66 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// The possible video stabilization modes that can be capturing video. +enum VideoStabilizationMode { + /// Video stabilization is disabled. + off, + + /// Basic video stabilization is enabled. + /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_ON on Android + /// and throws CameraException on iOS. + on, + + /// Standard video stabilization is enabled. + /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android + /// (camera_android_camerax) and to AVCaptureVideoStabilizationModeStandard + /// on iOS. + standard, + + /// Cinematic video stabilization is enabled. + /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android + /// (camera_android_camerax) and to AVCaptureVideoStabilizationModeCinematic + /// on iOS. + cinematic, + + /// Extended cinematic video stabilization is enabled. + /// Maps to AVCaptureVideoStabilizationModeCinematicExtended on iOS and + /// throws CameraException on Android. + cinematicExtended, +} + +/// Returns the video stabilization mode as a String. +String serializeVideoStabilizationMode( + VideoStabilizationMode videoStabilizationMode) { + switch (videoStabilizationMode) { + case VideoStabilizationMode.off: + return 'off'; + case VideoStabilizationMode.on: + return 'on'; + case VideoStabilizationMode.standard: + return 'standard'; + case VideoStabilizationMode.cinematic: + return 'cinematic'; + case VideoStabilizationMode.cinematicExtended: + return 'cinematicExtended'; + } +} + +/// Returns the video stabilization mode for a given String. +VideoStabilizationMode deserializeVideoStabilizationMode(String str) { + switch (str) { + case 'off': + return VideoStabilizationMode.off; + case 'on': + return VideoStabilizationMode.on; + case 'standard': + return VideoStabilizationMode.standard; + case 'cinematic': + return VideoStabilizationMode.cinematic; + case 'cinematicExtended': + return VideoStabilizationMode.cinematicExtended; + default: + throw ArgumentError('"$str" is not a valid VideoStabilizationMode value'); + } +} diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 6691da0cb3a..71993e0e18e 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ 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 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.8.0 +version: 2.9.0 environment: sdk: ^3.2.0 diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 063633255c9..e93d525e112 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -1014,6 +1014,151 @@ void main() { 'Illegal zoom error'))); }); + test('Should get empty list from getVideoStabilizationSupportedModes', + () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'getVideoStabilizationSupportedModes': [], + }, + ); + + // Act + final Iterable modes = + await camera.getVideoStabilizationSupportedModes( + cameraId, + ); + + // Assert + expect(modes, []); + + expect(channel.log, [ + isMethodCall('getVideoStabilizationSupportedModes', + arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test( + 'Should get list containing off from getVideoStabilizationSupportedModes', + () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'getVideoStabilizationSupportedModes': ['off'], + }, + ); + + // Act + final Iterable modes = + await camera.getVideoStabilizationSupportedModes( + cameraId, + ); + + // Assert + expect(modes, [VideoStabilizationMode.off]); + + expect(channel.log, [ + isMethodCall('getVideoStabilizationSupportedModes', + arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test( + 'Should get list containing all from getVideoStabilizationSupportedModes', + () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: { + 'getVideoStabilizationSupportedModes': [ + 'off', + 'on', + 'standard', + 'cinematic', + 'cinematicExtended', + ], + }, + ); + + // Act + final Iterable modes = + await camera.getVideoStabilizationSupportedModes( + cameraId, + ); + + // Assert + expect(modes, [ + VideoStabilizationMode.off, + VideoStabilizationMode.on, + VideoStabilizationMode.standard, + VideoStabilizationMode.cinematic, + VideoStabilizationMode.cinematicExtended, + ]); + + expect(channel.log, [ + isMethodCall('getVideoStabilizationSupportedModes', + arguments: { + 'cameraId': cameraId, + }), + ]); + }); + + test('Should set the video stabilization mode', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setVideoStabilizationMode': null}, + ); + + // Act + await camera.setVideoStabilizationMode( + cameraId, + VideoStabilizationMode.off, + ); + await camera.setVideoStabilizationMode( + cameraId, + VideoStabilizationMode.standard, + ); + await camera.setVideoStabilizationMode( + cameraId, + VideoStabilizationMode.cinematic, + ); + await camera.setVideoStabilizationMode( + cameraId, + VideoStabilizationMode.cinematicExtended, + ); + + // Assert + expect(channel.log, [ + isMethodCall('setVideoStabilizationMode', + arguments: { + 'cameraId': cameraId, + 'mode': VideoStabilizationMode.off.index, + }), + isMethodCall('setVideoStabilizationMode', + arguments: { + 'cameraId': cameraId, + 'mode': VideoStabilizationMode.standard.index, + }), + isMethodCall('setVideoStabilizationMode', + arguments: { + 'cameraId': cameraId, + 'mode': VideoStabilizationMode.cinematic.index, + }), + isMethodCall('setVideoStabilizationMode', + arguments: { + 'cameraId': cameraId, + 'mode': VideoStabilizationMode.cinematicExtended.index, + }), + ]); + }); + test('Should lock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( diff --git a/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart b/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart new file mode 100644 index 00000000000..1eac8a93fb5 --- /dev/null +++ b/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart @@ -0,0 +1,49 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_platform_interface/src/types/video_stabilization_mode.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('VideoStabilizationMode should contain 3 options', () { + const List values = VideoStabilizationMode.values; + + expect(values.length, 5); + }); + + test('VideoStabilizationMode enum should have items in correct index', () { + const List values = VideoStabilizationMode.values; + + expect(values[0], VideoStabilizationMode.off); + expect(values[1], VideoStabilizationMode.on); + expect(values[2], VideoStabilizationMode.standard); + expect(values[3], VideoStabilizationMode.cinematic); + expect(values[4], VideoStabilizationMode.cinematicExtended); + }); + + test('serializeVideoStabilizationMode() should serialize correctly', () { + expect(serializeVideoStabilizationMode(VideoStabilizationMode.off), 'off'); + expect(serializeVideoStabilizationMode(VideoStabilizationMode.on), 'on'); + expect(serializeVideoStabilizationMode(VideoStabilizationMode.standard), + 'standard'); + expect(serializeVideoStabilizationMode(VideoStabilizationMode.cinematic), + 'cinematic'); + expect( + serializeVideoStabilizationMode( + VideoStabilizationMode.cinematicExtended), + 'cinematicExtended'); + }); + + test('deserializeVideoStabilizationMode() should deserialize correctly', () { + expect( + deserializeVideoStabilizationMode('off'), VideoStabilizationMode.off); + expect(deserializeVideoStabilizationMode('on'), VideoStabilizationMode.on); + expect(deserializeVideoStabilizationMode('standard'), + VideoStabilizationMode.standard); + expect(deserializeVideoStabilizationMode('cinematic'), + VideoStabilizationMode.cinematic); + expect(deserializeVideoStabilizationMode('cinematicExtended'), + VideoStabilizationMode.cinematicExtended); + }); +} diff --git a/packages/camera/camera_web/CHANGELOG.md b/packages/camera/camera_web/CHANGELOG.md index c9e8e661da4..fa485ed85f6 100644 --- a/packages/camera/camera_web/CHANGELOG.md +++ b/packages/camera/camera_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.4+1 + +* Updates camera_platform_interface lib to 2.9.0. + ## 0.3.4 * Removes `maxVideoDuration`/`maxDuration`, as the feature was never exposed at diff --git a/packages/camera/camera_web/pubspec.yaml b/packages/camera/camera_web/pubspec.yaml index e33a2e0fd28..afa94af13da 100644 --- a/packages/camera/camera_web/pubspec.yaml +++ b/packages/camera/camera_web/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_web description: A Flutter plugin for getting information about and controlling the camera on Web. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.3.4 +version: 0.3.4+1 environment: sdk: ^3.2.0 @@ -17,7 +17,7 @@ flutter: fileName: camera_web.dart dependencies: - camera_platform_interface: ^2.6.0 + camera_platform_interface: ^2.9.0 flutter: sdk: flutter flutter_web_plugins: diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md index 4b164343ecd..630d40d51d5 100644 --- a/packages/camera/camera_windows/CHANGELOG.md +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.4+1 + +* Updates camera_platform_interface lib to 2.9.0. + ## 0.2.4 * Removes `maxVideoDuration`/`maxDuration`, as the feature was never exposed at diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index 7e3f7381b4b..9bc006d7df8 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_windows description: A Flutter plugin for getting information about and controlling the camera on Windows. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.2.4 +version: 0.2.4+1 environment: sdk: ^3.2.0 @@ -17,7 +17,7 @@ flutter: dartPluginClass: CameraWindows dependencies: - camera_platform_interface: ^2.6.0 + camera_platform_interface: ^2.9.0 cross_file: ^0.3.1 flutter: sdk: flutter From 39e2adfdfb98c3b60f0d945ec7889f535825c396 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Tue, 16 Jul 2024 11:50:03 +0100 Subject: [PATCH 02/24] do not merge! dependency overrides --- packages/camera/camera/example/pubspec.yaml | 5 +++++ packages/camera/camera/pubspec.yaml | 5 +++++ packages/camera/camera_android/example/pubspec.yaml | 5 +++++ packages/camera/camera_android/pubspec.yaml | 5 +++++ packages/camera/camera_android_camerax/example/pubspec.yaml | 5 +++++ packages/camera/camera_android_camerax/pubspec.yaml | 5 +++++ packages/camera/camera_avfoundation/example/pubspec.yaml | 5 +++++ packages/camera/camera_avfoundation/pubspec.yaml | 5 +++++ packages/camera/camera_web/example/pubspec.yaml | 5 +++++ packages/camera/camera_web/pubspec.yaml | 5 +++++ packages/camera/camera_windows/example/pubspec.yaml | 5 +++++ packages/camera/camera_windows/pubspec.yaml | 5 +++++ 12 files changed, 60 insertions(+) diff --git a/packages/camera/camera/example/pubspec.yaml b/packages/camera/camera/example/pubspec.yaml index f3b240b56c2..99c49f8f364 100644 --- a/packages/camera/camera/example/pubspec.yaml +++ b/packages/camera/camera/example/pubspec.yaml @@ -30,3 +30,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera: {path: ../../../camera/camera}, camera_android_camerax: {path: ../../../camera/camera_android_camerax}, camera_avfoundation: {path: ../../../camera/camera_avfoundation}, camera_platform_interface: {path: ../../../camera/camera_platform_interface}, camera_web: {path: ../../../camera/camera_web}} diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 7f2ec933fe9..0624dbc7d73 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -37,3 +37,8 @@ dev_dependencies: topics: - camera + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_android_camerax: {path: ../../camera/camera_android_camerax}, camera_avfoundation: {path: ../../camera/camera_avfoundation}, camera_platform_interface: {path: ../../camera/camera_platform_interface}, camera_web: {path: ../../camera/camera_web}} diff --git a/packages/camera/camera_android/example/pubspec.yaml b/packages/camera/camera_android/example/pubspec.yaml index cd212825d74..510b2440b7d 100644 --- a/packages/camera/camera_android/example/pubspec.yaml +++ b/packages/camera/camera_android/example/pubspec.yaml @@ -31,3 +31,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_android: {path: ../../../camera/camera_android}, camera_platform_interface: {path: ../../../camera/camera_platform_interface}} diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index f29bdb21d8a..512a5a8721e 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -32,3 +32,8 @@ dev_dependencies: topics: - camera + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_platform_interface: {path: ../../camera/camera_platform_interface}} diff --git a/packages/camera/camera_android_camerax/example/pubspec.yaml b/packages/camera/camera_android_camerax/example/pubspec.yaml index c47771b1a12..42763e56e58 100644 --- a/packages/camera/camera_android_camerax/example/pubspec.yaml +++ b/packages/camera/camera_android_camerax/example/pubspec.yaml @@ -29,3 +29,8 @@ dev_dependencies: flutter: uses-material-design: true +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_android_camerax: {path: ../../../camera/camera_android_camerax}, camera_platform_interface: {path: ../../../camera/camera_platform_interface}} + diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index b2907329c76..d436df866ff 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -36,3 +36,8 @@ dev_dependencies: topics: - camera + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_platform_interface: {path: ../../camera/camera_platform_interface}} diff --git a/packages/camera/camera_avfoundation/example/pubspec.yaml b/packages/camera/camera_avfoundation/example/pubspec.yaml index 6cdd9cbf72f..70454067a2f 100644 --- a/packages/camera/camera_avfoundation/example/pubspec.yaml +++ b/packages/camera/camera_avfoundation/example/pubspec.yaml @@ -29,3 +29,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_avfoundation: {path: ../../../camera/camera_avfoundation}, camera_platform_interface: {path: ../../../camera/camera_platform_interface}} diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 47b9aa08a4b..e274a4a7df9 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -32,3 +32,8 @@ dev_dependencies: topics: - camera + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_platform_interface: {path: ../../camera/camera_platform_interface}} diff --git a/packages/camera/camera_web/example/pubspec.yaml b/packages/camera/camera_web/example/pubspec.yaml index 5cf64e1c5ce..6471f724091 100644 --- a/packages/camera/camera_web/example/pubspec.yaml +++ b/packages/camera/camera_web/example/pubspec.yaml @@ -26,3 +26,8 @@ dev_dependencies: sdk: flutter mocktail: 0.3.0 +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_platform_interface: {path: ../../../camera/camera_platform_interface}, camera_web: {path: ../../../camera/camera_web}} + diff --git a/packages/camera/camera_web/pubspec.yaml b/packages/camera/camera_web/pubspec.yaml index afa94af13da..51eb16fefc4 100644 --- a/packages/camera/camera_web/pubspec.yaml +++ b/packages/camera/camera_web/pubspec.yaml @@ -30,3 +30,8 @@ dev_dependencies: topics: - camera + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_platform_interface: {path: ../../camera/camera_platform_interface}} diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index 6e94a03e4ae..6b8778d3c31 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -27,3 +27,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_platform_interface: {path: ../../../camera/camera_platform_interface}, camera_windows: {path: ../../../camera/camera_windows}} diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index 9bc006d7df8..c5fe53252b3 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -33,3 +33,8 @@ dev_dependencies: topics: - camera + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + {camera_platform_interface: {path: ../../camera/camera_platform_interface}} From edf33b05a92efee96b5a5d1e099719ece9db3b83 Mon Sep 17 00:00:00 2001 From: Rui Craveiro <8490712+ruicraveiro@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:13:31 +0100 Subject: [PATCH 03/24] Update camera_info.dart Removes dead out-commented code --- .../camera_android_camerax/lib/src/camera_info.dart | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/camera/camera_android_camerax/lib/src/camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera_info.dart index ea5d51f37b2..6c0a62f9fd5 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_info.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_info.dart @@ -100,17 +100,6 @@ class _CameraInfoHostApiImpl extends CameraInfoHostApi { return instanceManager.getInstanceWithWeakReference>( zoomStateIdentifier)!; } - - // /// Returns a value indicating whether the given video - // /// stabilization mode is supported for [CameraInfo] instance. - // Future isVideoStabilizationModeSupportedFromInstance( - // CameraInfo instance, VideoStabilizationMode mode) async { - // final int identifier = instanceManager.getIdentifier(instance)!; - - // final bool? result = - // await isVideoStabilizationModeSupported(identifier, mode.index); - // return result; - // } } /// Flutter API implementation of [CameraInfo]. From 741e032d5ea992638f90f5ed4603a6b842d78ad1 Mon Sep 17 00:00:00 2001 From: Rui Craveiro Date: Tue, 16 Jul 2024 22:07:11 +0100 Subject: [PATCH 04/24] Moved getAvCaptureVideoStabilizationMode from FLTCam to CameraProperties --- .../camera_avfoundation/CameraProperties.m | 23 ++++++++++++++++++ .../Sources/camera_avfoundation/FLTCam.m | 24 ------------------- .../camera_avfoundation/CameraProperties.h | 2 ++ 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraProperties.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraProperties.m index 8ef61b43fc8..af8a6df4e9d 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraProperties.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraProperties.m @@ -55,3 +55,26 @@ OSType FCPGetPixelFormatForPigeonFormat(FCPPlatformImageFormatGroup imageFormat) return kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; } } + +AVCaptureVideoStabilizationMode getAvCaptureVideoStabilizationMode( + FCPPlatformVideoStabilizationMode videoStabilizationMode) { + switch (videoStabilizationMode) { + case FCPPlatformVideoStabilizationModeOff: + return AVCaptureVideoStabilizationModeOff; + case FCPPlatformVideoStabilizationModeStandard: + return AVCaptureVideoStabilizationModeStandard; + + case FCPPlatformVideoStabilizationModeCinematic: + return AVCaptureVideoStabilizationModeCinematic; + + case FCPPlatformVideoStabilizationModeCinematicExtended: + if (@available(iOS 13.0, *)) { + return AVCaptureVideoStabilizationModeCinematicExtended; + } else { + return AVCaptureVideoStabilizationModeCinematic; + } + + default: + return AVCaptureVideoStabilizationModeOff; + } +} diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m index 3eba183f9ad..30c47b922f1 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m @@ -1222,30 +1222,6 @@ - (BOOL)isVideoStabilizationModeSupported:(FCPPlatformVideoStabilizationMode)mod return [_captureDevice.activeFormat isVideoStabilizationModeSupported:stabilizationMode]; } - -AVCaptureVideoStabilizationMode getAvCaptureVideoStabilizationMode( - FCPPlatformVideoStabilizationMode videoStabilizationMode) { - switch (videoStabilizationMode) { - case FCPPlatformVideoStabilizationModeOff: - return AVCaptureVideoStabilizationModeOff; - case FCPPlatformVideoStabilizationModeStandard: - return AVCaptureVideoStabilizationModeStandard; - - case FCPPlatformVideoStabilizationModeCinematic: - return AVCaptureVideoStabilizationModeCinematic; - - case FCPPlatformVideoStabilizationModeCinematicExtended: - if (@available(iOS 13.0, *)) { - return AVCaptureVideoStabilizationModeCinematicExtended; - } else { - return AVCaptureVideoStabilizationModeCinematic; - } - - default: - return AVCaptureVideoStabilizationModeOff; - } -} - - (CGFloat)minimumAvailableZoomFactor { return _captureDevice.minAvailableVideoZoomFactor; } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraProperties.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraProperties.h index a46e4f04b65..27c582b6d69 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraProperties.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraProperties.h @@ -25,4 +25,6 @@ extern FCPPlatformDeviceOrientation FCPGetPigeonDeviceOrientationForOrientation( /// Gets VideoFormat from its Pigeon representation. extern OSType FCPGetPixelFormatForPigeonFormat(FCPPlatformImageFormatGroup imageFormat); +extern AVCaptureVideoStabilizationMode getAvCaptureVideoStabilizationMode( + FCPPlatformVideoStabilizationMode videoStabilizationMode); NS_ASSUME_NONNULL_END From d2200781b731f594b2443db2b4dd7d61ef6c7c64 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Mon, 22 Jul 2024 21:52:34 +0100 Subject: [PATCH 05/24] Refactored Camera2CameraInfoHostApiImpl with single @OptIn annotation --- .../plugins/camerax/Camera2CameraInfoHostApiImpl.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java index 917635fd9c6..882f6920867 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java @@ -23,6 +23,7 @@ *

This class handles instantiating and adding native object instances that are attached to a * Dart instance or handle method calls on the associated native class or an instance of the class. */ +@OptIn(markerClass = ExperimentalCamera2Interop.class) public class Camera2CameraInfoHostApiImpl implements Camera2CameraInfoHostApi { private final BinaryMessenger binaryMessenger; private final InstanceManager instanceManager; @@ -30,7 +31,6 @@ public class Camera2CameraInfoHostApiImpl implements Camera2CameraInfoHostApi { /** Proxy for methods of {@link Camera2CameraInfo}. */ @VisibleForTesting - @OptIn(markerClass = ExperimentalCamera2Interop.class) public static class Camera2CameraInfoProxy { @NonNull @@ -56,7 +56,6 @@ public Long getSensorOrientation(@NonNull Camera2CameraInfo camera2CameraInfo) { } @NonNull - @OptIn(markerClass = ExperimentalCamera2Interop.class) public int[] getAvailableVideoStabilizationModes(@NonNull Camera2CameraInfo camera2CameraInfo) { int[] modes = camera2CameraInfo.getCameraCharacteristic( @@ -95,7 +94,6 @@ public Camera2CameraInfoHostApiImpl( @Override @NonNull - @OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class) public Long createFrom(@NonNull Long cameraInfoIdentifier) { final CameraInfo cameraInfo = Objects.requireNonNull(instanceManager.getInstance(cameraInfoIdentifier)); @@ -137,7 +135,6 @@ public List getAvailableVideoStabilizationModes(@NonNull Long identifier) return ret; } - @OptIn(markerClass = androidx.camera.camera2.interop.ExperimentalCamera2Interop.class) private Camera2CameraInfo getCamera2CameraInfoInstance(@NonNull Long identifier) { return Objects.requireNonNull(instanceManager.getInstance(identifier)); } From 887c56539f5a45524a67b258ff36f41110cdd42a Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Mon, 22 Jul 2024 22:06:37 +0100 Subject: [PATCH 06/24] Moved Android video stabilization mapping to Camera2CameraInfo --- .../lib/src/android_camera_camerax.dart | 65 +++++-------------- .../lib/src/camera2_camera_info.dart | 46 +++++++++++-- 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 27abb5b55c5..8602ddee778 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -819,39 +819,35 @@ class AndroidCameraCameraX extends CameraPlatform { @override Future> getVideoStabilizationSupportedModes( int cameraId) async { - final List modeIndexes = - await _getAvailableVideoStabilizationModeIndexes(); - - final List list = []; - for (final int ix in modeIndexes) { - final VideoStabilizationMode? mode = switch (ix) { - 0 => VideoStabilizationMode.off, - 1 => VideoStabilizationMode.on, - 2 => VideoStabilizationMode.standard, - _ => null, - }; - if (mode != null) { - list.add(mode); - } + final CameraInfo? camInfo = cameraInfo; + if (camInfo == null) { + return []; } - return list; + final Camera2CameraInfo cam2Info = + await proxy.getCamera2CameraInfo(camInfo); + + final List modes = + await cam2Info.getAvailableVideoStabilizationModes(); + return modes; } /// Set the video stabilization mode for the selected camera. @override Future setVideoStabilizationMode( int cameraId, VideoStabilizationMode mode) async { - final int? controlMode = _getControlVideoStabilizationMode(mode); + final Iterable availableModes = + await getVideoStabilizationSupportedModes(cameraId); - final List availableModes = - await _getAvailableVideoStabilizationModeIndexes(); - if (controlMode == null || !availableModes.contains(controlMode)) { + if (!availableModes.contains(mode)) { // TODO(ruicraveiro): add to future possible error codes documentation // https://github.com/flutter/flutter/issues/69298 throw CameraException('VIDEO_STABILIIZATION_ERROR', 'Unavailable video stabilization mode.'); } + final int controlMode = + Camera2CameraInfo.getControlVideoStabilizationMode(mode); + final CaptureRequestOptions captureRequestOptions = proxy .createCaptureRequestOptions(<( CaptureRequestKeySupportedType, @@ -868,37 +864,6 @@ class AndroidCameraCameraX extends CameraPlatform { await camera2Control.addCaptureRequestOptions(captureRequestOptions); } - /// Gets from Camera2 API the available video stabilization - /// modes. - /// See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES - Future> _getAvailableVideoStabilizationModeIndexes() async { - final CameraInfo? camInfo = cameraInfo; - if (camInfo == null) { - return []; - } - final Camera2CameraInfo cam2Info = - await proxy.getCamera2CameraInfo(camInfo); - - final List modeIndexes = - await cam2Info.getAvailableVideoStabilizationModes(); - - return modeIndexes; - } - - int? _getControlVideoStabilizationMode(VideoStabilizationMode mode) { - final int? controlMode = switch (mode) { - // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_OFF - VideoStabilizationMode.off => 0, - // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_ON - VideoStabilizationMode.on => 1, - // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION - VideoStabilizationMode.standard => 2, - VideoStabilizationMode.cinematic => null, - VideoStabilizationMode.cinematicExtended => null, - }; - return controlMode; - } - /// The ui orientation changed. @override Stream onDeviceOrientationChanged() { diff --git a/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart index 6f8cb2c7d38..108870a94f1 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart' show BinaryMessenger; import 'package:meta/meta.dart' show immutable; @@ -48,13 +49,50 @@ class Camera2CameraInfo extends JavaObject { Future getSupportedHardwareLevel() => _api.getSupportedHardwareLevelFromInstance(this); - /// Retrieves the value of `CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES` - /// for the device to which this instance pertains to. + /// Retrieves de list of available common platform video stabilization modes + /// the device to which this instance pertains to supports. + /// + /// Works by retrieving the value of `CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES` + /// and mapping each of those values to the corresponding common platform + /// video stabilization mode. /// /// See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES /// for more information. - Future> getAvailableVideoStabilizationModes() => - _api.getAvailableVideoStabilizationModesFromInstance(this); + Future> + getAvailableVideoStabilizationModes() async { + final List controlModes = + await _api.getAvailableVideoStabilizationModesFromInstance(this); + + final List modes = [ + for (final int controlMode in controlModes) + if (controlMode == 0) + VideoStabilizationMode.off + else if (controlMode == 1) + VideoStabilizationMode.on + else if (controlMode == 2) + VideoStabilizationMode.standard + ]; + return modes; + } + + /// Maps the common platform VideoStabilizationMode + /// to the Android specific control video stabilization mode + static int getControlVideoStabilizationMode(VideoStabilizationMode mode) { + final int controlMode = switch (mode) { + // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_OFF + VideoStabilizationMode.off => 0, + // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_ON + VideoStabilizationMode.on => 1, + // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION + VideoStabilizationMode.standard => 2, + + // TODO(ruicraveiro): add to future possible error codes documentation + // https://github.com/flutter/flutter/issues/69298 + _ => throw CameraException( + 'VIDEO_STABILIIZATION_ERROR', 'Unavailable video stabilization mode.') + }; + return controlMode; + } /// Gets the camera ID. /// From 8d986ed60acfa607b9ca96e52b315d45e76ffe42 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Mon, 22 Jul 2024 23:07:21 +0100 Subject: [PATCH 07/24] fixed android camerax tests --- .../test/android_camera_camerax_test.dart | 27 ++++++++++--------- .../android_camera_camerax_test.mocks.dart | 21 ++++++++------- .../test/camera2_camera_info_test.dart | 21 +++++++++------ .../test/capture_request_options_test.dart | 13 ++++++--- 4 files changed, 49 insertions(+), 33 deletions(-) diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 380bfcbe22e..0638f741d72 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -2336,7 +2336,7 @@ void main() { when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return [0]; + return [VideoStabilizationMode.off]; }, ); @@ -2389,7 +2389,7 @@ void main() { when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return [0]; + return [VideoStabilizationMode.off]; }, ); @@ -2426,7 +2426,11 @@ void main() { when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return [0, 1, 2]; + return [ + VideoStabilizationMode.off, + VideoStabilizationMode.on, + VideoStabilizationMode.standard, + ]; }, ); @@ -2466,7 +2470,7 @@ void main() { when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return []; + return []; }, ); @@ -2501,10 +2505,15 @@ void main() { camera.camera = MockCamera(); camera.cameraInfo = mockCameraInfo; + final List expectedModes = [ + VideoStabilizationMode.off, + VideoStabilizationMode.on, + VideoStabilizationMode.standard, + ]; when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return [0, 1, 2]; + return expectedModes; }, ); @@ -2523,13 +2532,7 @@ void main() { final Iterable modes = await camera.getVideoStabilizationSupportedModes(cameraId); - expect( - modes, - orderedEquals([ - VideoStabilizationMode.off, - VideoStabilizationMode.on, - VideoStabilizationMode.standard, - ])); + expect(modes, orderedEquals(expectedModes)); }); test( diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart index 36936c30413..b245fc8e40a 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -708,15 +708,18 @@ class MockCamera2CameraInfo extends _i1.Mock implements _i26.Camera2CameraInfo { ) as _i17.Future); @override - _i17.Future> getAvailableVideoStabilizationModes() => - (super.noSuchMethod( - Invocation.method( - #getAvailableVideoStabilizationModes, - [], - ), - returnValue: _i17.Future>.value([]), - returnValueForMissingStub: _i17.Future>.value([]), - ) as _i17.Future>); + _i17.Future> + getAvailableVideoStabilizationModes() => (super.noSuchMethod( + Invocation.method( + #getAvailableVideoStabilizationModes, + [], + ), + returnValue: _i17.Future>.value( + <_i9.VideoStabilizationMode>[]), + returnValueForMissingStub: + _i17.Future>.value( + <_i9.VideoStabilizationMode>[]), + ) as _i17.Future>); @override _i17.Future getCameraId() => (super.noSuchMethod( diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart index 0d5c0224a81..53be192506d 100644 --- a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart +++ b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart @@ -7,6 +7,7 @@ import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camera_metadata.dart'; import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_platform_interface/src/types/video_stabilization_mode.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -132,16 +133,15 @@ void main() { onCopy: (_) => Camera2CameraInfo.detached(), ); - const List expectedModes = []; when(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)) - .thenReturn(expectedModes); + .thenReturn([]); // Act - final List returnedModes = + final List returnedModes = await camera2CameraInfo.getAvailableVideoStabilizationModes(); // Assert - expect(returnedModes, equals(expectedModes)); + expect(returnedModes, equals([])); verify(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)); }); @@ -167,16 +167,21 @@ void main() { onCopy: (_) => Camera2CameraInfo.detached(), ); - const List expectedModes = [0, 1, 2]; when(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)) - .thenReturn(expectedModes); + .thenReturn([0, 1, 2]); // Act - final List returnedModes = + final List returnedModes = await camera2CameraInfo.getAvailableVideoStabilizationModes(); // Assert - expect(returnedModes, equals(expectedModes)); + expect( + returnedModes, + equals([ + VideoStabilizationMode.off, + VideoStabilizationMode.on, + VideoStabilizationMode.standard, + ])); verify(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)); }); diff --git a/packages/camera/camera_android_camerax/test/capture_request_options_test.dart b/packages/camera/camera_android_camerax/test/capture_request_options_test.dart index 3649369c934..966f3eee41a 100644 --- a/packages/camera/camera_android_camerax/test/capture_request_options_test.dart +++ b/packages/camera/camera_android_camerax/test/capture_request_options_test.dart @@ -101,10 +101,11 @@ void main() { ); final List<(CaptureRequestKeySupportedType key, Object? value)> - supportedOptionsForTesting = <( - CaptureRequestKeySupportedType key, - Object? value - )>[(CaptureRequestKeySupportedType.controlAeLock, false)]; + supportedOptionsForTesting = + <(CaptureRequestKeySupportedType key, Object? value)>[ + (CaptureRequestKeySupportedType.controlAeLock, false), + (CaptureRequestKeySupportedType.controlVideoStabilizationMode, 0), + ]; final CaptureRequestOptions instance = CaptureRequestOptions( requestedOptions: supportedOptionsForTesting, @@ -129,6 +130,10 @@ void main() { case CaptureRequestKeySupportedType.controlAeLock: expect(captureRequestOptions[optionKey.index], equals(optionValue! as bool)); + + case CaptureRequestKeySupportedType.controlVideoStabilizationMode: + expect(captureRequestOptions[optionKey.index], + equals(optionValue! as int)); // This ignore statement is safe beause this will test when // a new CaptureRequestKeySupportedType is being added, but the logic in // in the CaptureRequestOptions class has not yet been updated. From 680097f1464455291f1a0e7d2bf16677a7ceb2ed Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Mon, 29 Jul 2024 19:39:28 +0100 Subject: [PATCH 08/24] Better naming for local variable in Camera2CameraInfoHostApi.getAvailableVideoStabilizationModes --- .../plugins/camerax/Camera2CameraInfoHostApiImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java index 882f6920867..f6345a9c496 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoHostApiImpl.java @@ -127,9 +127,9 @@ public Long getSensorOrientation(@NonNull Long identifier) { @Override public List getAvailableVideoStabilizationModes(@NonNull Long identifier) { - int[] lst = proxy.getAvailableVideoStabilizationModes(getCamera2CameraInfoInstance(identifier)); - List ret = new ArrayList(lst.length); - for (int i : lst) { + int[] availableModes = proxy.getAvailableVideoStabilizationModes(getCamera2CameraInfoInstance(identifier)); + List ret = new ArrayList(availableModes.length); + for (int i : availableModes) { ret.add((long) i); } return ret; From a8e6544d06b12daab6d696b9a15afd2f402b64d5 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Mon, 29 Jul 2024 20:46:18 +0100 Subject: [PATCH 09/24] video stabilization mapping back to android_camera_camerax --- .../lib/src/android_camera_camerax.dart | 49 +++++++++++++++++-- .../lib/src/camera2_camera_info.dart | 34 +------------ .../lib/src/camera_metadata.dart | 17 ++++++- .../test/android_camera_camerax_test.dart | 27 +++++----- .../android_camera_camerax_test.mocks.dart | 21 ++++---- .../test/camera2_camera_info_test.dart | 21 +++----- 6 files changed, 93 insertions(+), 76 deletions(-) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 8602ddee778..80cca51661d 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -816,6 +816,8 @@ class AndroidCameraCameraX extends CameraPlatform { await cameraControl.setZoomRatio(zoom); } + /// Gets a list of video stabilization modes that are supported for the + /// selected camera. @override Future> getVideoStabilizationSupportedModes( int cameraId) async { @@ -826,8 +828,24 @@ class AndroidCameraCameraX extends CameraPlatform { final Camera2CameraInfo cam2Info = await proxy.getCamera2CameraInfo(camInfo); - final List modes = + final List controlModes = await cam2Info.getAvailableVideoStabilizationModes(); + + /// if new modes need to be supported, the opposite of this mapping + /// code is in [_getControlVideoStabilizationMode(...)] in this class, + /// so don't forget to review that method as well. + + final List modes = [ + for (final int controlMode in controlModes) + if (controlMode == CameraMetadata.controlVideoStabilizationModeOff) + VideoStabilizationMode.off + else if (controlMode == CameraMetadata.controlVideoStabilizationModeOn) + VideoStabilizationMode.on + else if (controlMode == + CameraMetadata.controlVideoStabilizationModePreviewStabilization) + VideoStabilizationMode.standard + ]; + return modes; } @@ -845,8 +863,7 @@ class AndroidCameraCameraX extends CameraPlatform { 'Unavailable video stabilization mode.'); } - final int controlMode = - Camera2CameraInfo.getControlVideoStabilizationMode(mode); + final int controlMode = _getControlVideoStabilizationMode(mode); final CaptureRequestOptions captureRequestOptions = proxy .createCaptureRequestOptions(<( @@ -864,6 +881,32 @@ class AndroidCameraCameraX extends CameraPlatform { await camera2Control.addCaptureRequestOptions(captureRequestOptions); } + /// Maps the common platform VideoStabilizationMode + /// to the Android specific control video stabilization mode + static int _getControlVideoStabilizationMode(VideoStabilizationMode mode) { + // if new modes need to be supported, the opposite of this mapping + // code is in [getVideoStabilizationSupportedModes(...)] in this class, + // so don't forget to review that method as well. + + final int controlMode = switch (mode) { + // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_OFF + VideoStabilizationMode.off => + CameraMetadata.controlVideoStabilizationModeOff, + // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_ON + VideoStabilizationMode.on => + CameraMetadata.controlVideoStabilizationModeOn, + // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION + VideoStabilizationMode.standard => + CameraMetadata.controlVideoStabilizationModePreviewStabilization, + + // TODO(ruicraveiro): add to future possible error codes documentation + // https://github.com/flutter/flutter/issues/69298 + _ => throw CameraException( + 'VIDEO_STABILIIZATION_ERROR', 'Unavailable video stabilization mode.') + }; + return controlMode; + } + /// The ui orientation changed. @override Stream onDeviceOrientationChanged() { diff --git a/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart index 108870a94f1..b1b90b44046 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera2_camera_info.dart @@ -4,7 +4,6 @@ import 'dart:async'; -import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart' show BinaryMessenger; import 'package:meta/meta.dart' show immutable; @@ -58,42 +57,13 @@ class Camera2CameraInfo extends JavaObject { /// /// See https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES /// for more information. - Future> - getAvailableVideoStabilizationModes() async { - final List controlModes = + Future> getAvailableVideoStabilizationModes() async { + final List modes = await _api.getAvailableVideoStabilizationModesFromInstance(this); - final List modes = [ - for (final int controlMode in controlModes) - if (controlMode == 0) - VideoStabilizationMode.off - else if (controlMode == 1) - VideoStabilizationMode.on - else if (controlMode == 2) - VideoStabilizationMode.standard - ]; return modes; } - /// Maps the common platform VideoStabilizationMode - /// to the Android specific control video stabilization mode - static int getControlVideoStabilizationMode(VideoStabilizationMode mode) { - final int controlMode = switch (mode) { - // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_OFF - VideoStabilizationMode.off => 0, - // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_ON - VideoStabilizationMode.on => 1, - // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION - VideoStabilizationMode.standard => 2, - - // TODO(ruicraveiro): add to future possible error codes documentation - // https://github.com/flutter/flutter/issues/69298 - _ => throw CameraException( - 'VIDEO_STABILIIZATION_ERROR', 'Unavailable video stabilization mode.') - }; - return controlMode; - } - /// Gets the camera ID. /// /// The ID may change based on the internal configuration of the camera to which diff --git a/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart b/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart index 65098b747c6..36f3f0dc40a 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_metadata.dart @@ -34,9 +34,24 @@ class CameraMetadata { /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_3. static const int infoSupportedHardwareLevel3 = 3; - /// Constant taht specifies a camera device is backed by an external camera + /// Constant that specifies a camera device is backed by an external camera /// connected to this Android device. /// /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL. static const int infoSupportedHardwareLevelExternal = 4; + + /// Constant that specifies that video stabilization is disabled. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_OFF. + static const int controlVideoStabilizationModeOff = 0; + + /// Constant that specifies that video stabilization is enabled. + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_ON. + static const int controlVideoStabilizationModeOn = 1; + + /// Constant that specifies that video stabilization mode is preview stabilization, + /// + /// See https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION. + static const int controlVideoStabilizationModePreviewStabilization = 2; } diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 0638f741d72..380bfcbe22e 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -2336,7 +2336,7 @@ void main() { when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return [VideoStabilizationMode.off]; + return [0]; }, ); @@ -2389,7 +2389,7 @@ void main() { when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return [VideoStabilizationMode.off]; + return [0]; }, ); @@ -2426,11 +2426,7 @@ void main() { when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return [ - VideoStabilizationMode.off, - VideoStabilizationMode.on, - VideoStabilizationMode.standard, - ]; + return [0, 1, 2]; }, ); @@ -2470,7 +2466,7 @@ void main() { when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return []; + return []; }, ); @@ -2505,15 +2501,10 @@ void main() { camera.camera = MockCamera(); camera.cameraInfo = mockCameraInfo; - final List expectedModes = [ - VideoStabilizationMode.off, - VideoStabilizationMode.on, - VideoStabilizationMode.standard, - ]; when(mockCamera2CameraInfo.getAvailableVideoStabilizationModes()) .thenAnswer( (_) async { - return expectedModes; + return [0, 1, 2]; }, ); @@ -2532,7 +2523,13 @@ void main() { final Iterable modes = await camera.getVideoStabilizationSupportedModes(cameraId); - expect(modes, orderedEquals(expectedModes)); + expect( + modes, + orderedEquals([ + VideoStabilizationMode.off, + VideoStabilizationMode.on, + VideoStabilizationMode.standard, + ])); }); test( diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart index b245fc8e40a..36936c30413 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -708,18 +708,15 @@ class MockCamera2CameraInfo extends _i1.Mock implements _i26.Camera2CameraInfo { ) as _i17.Future); @override - _i17.Future> - getAvailableVideoStabilizationModes() => (super.noSuchMethod( - Invocation.method( - #getAvailableVideoStabilizationModes, - [], - ), - returnValue: _i17.Future>.value( - <_i9.VideoStabilizationMode>[]), - returnValueForMissingStub: - _i17.Future>.value( - <_i9.VideoStabilizationMode>[]), - ) as _i17.Future>); + _i17.Future> getAvailableVideoStabilizationModes() => + (super.noSuchMethod( + Invocation.method( + #getAvailableVideoStabilizationModes, + [], + ), + returnValue: _i17.Future>.value([]), + returnValueForMissingStub: _i17.Future>.value([]), + ) as _i17.Future>); @override _i17.Future getCameraId() => (super.noSuchMethod( diff --git a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart index 53be192506d..0d5c0224a81 100644 --- a/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart +++ b/packages/camera/camera_android_camerax/test/camera2_camera_info_test.dart @@ -7,7 +7,6 @@ import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camera_metadata.dart'; import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; -import 'package:camera_platform_interface/src/types/video_stabilization_mode.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -133,15 +132,16 @@ void main() { onCopy: (_) => Camera2CameraInfo.detached(), ); + const List expectedModes = []; when(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)) - .thenReturn([]); + .thenReturn(expectedModes); // Act - final List returnedModes = + final List returnedModes = await camera2CameraInfo.getAvailableVideoStabilizationModes(); // Assert - expect(returnedModes, equals([])); + expect(returnedModes, equals(expectedModes)); verify(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)); }); @@ -167,21 +167,16 @@ void main() { onCopy: (_) => Camera2CameraInfo.detached(), ); + const List expectedModes = [0, 1, 2]; when(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)) - .thenReturn([0, 1, 2]); + .thenReturn(expectedModes); // Act - final List returnedModes = + final List returnedModes = await camera2CameraInfo.getAvailableVideoStabilizationModes(); // Assert - expect( - returnedModes, - equals([ - VideoStabilizationMode.off, - VideoStabilizationMode.on, - VideoStabilizationMode.standard, - ])); + expect(returnedModes, equals(expectedModes)); verify(mockApi.getAvailableVideoStabilizationModes(camera2CameraInfoId)); }); From e6b483f9d96a158cc010ccfad2764061b280cd47 Mon Sep 17 00:00:00 2001 From: Rui Craveiro <8490712+ruicraveiro@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:54:45 +0100 Subject: [PATCH 10/24] Update pubspec.yaml Removed duplicate dependency_overrides --- packages/camera/camera/example/pubspec.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/camera/camera/example/pubspec.yaml b/packages/camera/camera/example/pubspec.yaml index 70b24c46c07..99c49f8f364 100644 --- a/packages/camera/camera/example/pubspec.yaml +++ b/packages/camera/camera/example/pubspec.yaml @@ -28,10 +28,6 @@ dev_dependencies: integration_test: sdk: flutter -dependency_overrides: - camera_web: - path: ../../camera_web - flutter: uses-material-design: true From 45fa745e20847462a43b4f038cbcab277a53ef00 Mon Sep 17 00:00:00 2001 From: Rui Craveiro <8490712+ruicraveiro@users.noreply.github.com> Date: Tue, 6 Aug 2024 18:45:51 +0100 Subject: [PATCH 11/24] Update packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com> --- .../camera_android_camerax/lib/src/android_camera_camerax.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 80cca51661d..d046a66436f 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -831,7 +831,7 @@ class AndroidCameraCameraX extends CameraPlatform { final List controlModes = await cam2Info.getAvailableVideoStabilizationModes(); - /// if new modes need to be supported, the opposite of this mapping + /// If new modes need to be supported, the opposite of this mapping /// code is in [_getControlVideoStabilizationMode(...)] in this class, /// so don't forget to review that method as well. From c33376871ff5505237d7fa843b7860a28509cc84 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Wed, 21 Aug 2024 23:31:27 +0100 Subject: [PATCH 12/24] video stabilizaton methods throw unimplemented --- .../lib/src/platform_interface/camera_platform.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index c991b323bb6..4a1b112dace 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -279,7 +279,7 @@ abstract class CameraPlatform extends PlatformInterface { /// on iOS. Will return an empty list on all other platforms. Future> getVideoStabilizationSupportedModes( int cameraId) async { - return []; + throw UnimplementedError(); } /// Sets the video stabilization mode for the selected camera. @@ -292,8 +292,7 @@ abstract class CameraPlatform extends PlatformInterface { /// mode is supplied. Future setVideoStabilizationMode( int cameraId, VideoStabilizationMode mode) async { - throw CameraException( - 'not_supported', 'setVideoStabilizationMode() is not implemented.'); + throw UnimplementedError(); } /// Pause the active preview on the current frame for the selected camera. From 94651bc50e3f62db8c22b637d852b3d14b44e4c1 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Wed, 21 Aug 2024 23:49:19 +0100 Subject: [PATCH 13/24] Using enhanced enum features with VideoStabilizationMode --- .../method_channel/method_channel_camera.dart | 4 +- .../src/types/video_stabilization_mode.dart | 43 ++++--------------- .../method_channel_camera_test.dart | 8 ++-- .../types/video_stabilization_mode_test.dart | 23 +++------- 4 files changed, 20 insertions(+), 58 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 229d36222c6..c96ce84ebef 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -517,7 +517,7 @@ class MethodChannelCamera extends CameraPlatform { return []; } return modes - .map((Object? e) => deserializeVideoStabilizationMode(e! as String)); + .map((Object? e) => VideoStabilizationMode.fromString(e! as String)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -532,7 +532,7 @@ class MethodChannelCamera extends CameraPlatform { 'setVideoStabilizationMode', { 'cameraId': cameraId, - 'mode': mode.index, + 'mode': mode.name, }, ); } on PlatformException catch (e) { diff --git a/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart index 01018acac44..06f6b4e15f9 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart @@ -8,59 +8,34 @@ enum VideoStabilizationMode { off, /// Basic video stabilization is enabled. + /// /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_ON on Android /// and throws CameraException on iOS. on, /// Standard video stabilization is enabled. + /// /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android /// (camera_android_camerax) and to AVCaptureVideoStabilizationModeStandard /// on iOS. standard, /// Cinematic video stabilization is enabled. + /// /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android /// (camera_android_camerax) and to AVCaptureVideoStabilizationModeCinematic /// on iOS. cinematic, /// Extended cinematic video stabilization is enabled. + /// /// Maps to AVCaptureVideoStabilizationModeCinematicExtended on iOS and /// throws CameraException on Android. - cinematicExtended, -} - -/// Returns the video stabilization mode as a String. -String serializeVideoStabilizationMode( - VideoStabilizationMode videoStabilizationMode) { - switch (videoStabilizationMode) { - case VideoStabilizationMode.off: - return 'off'; - case VideoStabilizationMode.on: - return 'on'; - case VideoStabilizationMode.standard: - return 'standard'; - case VideoStabilizationMode.cinematic: - return 'cinematic'; - case VideoStabilizationMode.cinematicExtended: - return 'cinematicExtended'; - } -} + cinematicExtended; -/// Returns the video stabilization mode for a given String. -VideoStabilizationMode deserializeVideoStabilizationMode(String str) { - switch (str) { - case 'off': - return VideoStabilizationMode.off; - case 'on': - return VideoStabilizationMode.on; - case 'standard': - return VideoStabilizationMode.standard; - case 'cinematic': - return VideoStabilizationMode.cinematic; - case 'cinematicExtended': - return VideoStabilizationMode.cinematicExtended; - default: - throw ArgumentError('"$str" is not a valid VideoStabilizationMode value'); + /// Returns the video stabilization mode for a given String. + factory VideoStabilizationMode.fromString(String str) { + return VideoStabilizationMode.values + .firstWhere((VideoStabilizationMode mode) => mode.name == str); } } diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index e93d525e112..3791a782fbc 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -1139,22 +1139,22 @@ void main() { isMethodCall('setVideoStabilizationMode', arguments: { 'cameraId': cameraId, - 'mode': VideoStabilizationMode.off.index, + 'mode': VideoStabilizationMode.off.name, }), isMethodCall('setVideoStabilizationMode', arguments: { 'cameraId': cameraId, - 'mode': VideoStabilizationMode.standard.index, + 'mode': VideoStabilizationMode.standard.name, }), isMethodCall('setVideoStabilizationMode', arguments: { 'cameraId': cameraId, - 'mode': VideoStabilizationMode.cinematic.index, + 'mode': VideoStabilizationMode.cinematic.name, }), isMethodCall('setVideoStabilizationMode', arguments: { 'cameraId': cameraId, - 'mode': VideoStabilizationMode.cinematicExtended.index, + 'mode': VideoStabilizationMode.cinematicExtended.name, }), ]); }); diff --git a/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart b/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart index 1eac8a93fb5..b92bc5f8d77 100644 --- a/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart +++ b/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart @@ -22,28 +22,15 @@ void main() { expect(values[4], VideoStabilizationMode.cinematicExtended); }); - test('serializeVideoStabilizationMode() should serialize correctly', () { - expect(serializeVideoStabilizationMode(VideoStabilizationMode.off), 'off'); - expect(serializeVideoStabilizationMode(VideoStabilizationMode.on), 'on'); - expect(serializeVideoStabilizationMode(VideoStabilizationMode.standard), - 'standard'); - expect(serializeVideoStabilizationMode(VideoStabilizationMode.cinematic), - 'cinematic'); - expect( - serializeVideoStabilizationMode( - VideoStabilizationMode.cinematicExtended), - 'cinematicExtended'); - }); - test('deserializeVideoStabilizationMode() should deserialize correctly', () { expect( - deserializeVideoStabilizationMode('off'), VideoStabilizationMode.off); - expect(deserializeVideoStabilizationMode('on'), VideoStabilizationMode.on); - expect(deserializeVideoStabilizationMode('standard'), + VideoStabilizationMode.fromString('off'), VideoStabilizationMode.off); + expect(VideoStabilizationMode.fromString('on'), VideoStabilizationMode.on); + expect(VideoStabilizationMode.fromString('standard'), VideoStabilizationMode.standard); - expect(deserializeVideoStabilizationMode('cinematic'), + expect(VideoStabilizationMode.fromString('cinematic'), VideoStabilizationMode.cinematic); - expect(deserializeVideoStabilizationMode('cinematicExtended'), + expect(VideoStabilizationMode.fromString('cinematicExtended'), VideoStabilizationMode.cinematicExtended); }); } From 1ec3f5425c925d871ed70f5ce2fa6bbff9dfb010 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Thu, 22 Aug 2024 00:26:22 +0100 Subject: [PATCH 14/24] Removed $ in name of test --- packages/camera/camera/test/camera_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index bc9bb76e8f3..303c29c44d5 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1269,7 +1269,7 @@ void main() { ]); }); - test('setVideoStabilizationMode() calls $CameraPlatform', () async { + test('setVideoStabilizationMode() calls CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', From c99c7274a2ecf3d8da6a36399232d2ba5254c738 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Fri, 30 Aug 2024 02:17:37 +0100 Subject: [PATCH 15/24] Added message when throwing UnimplementedError --- .../lib/src/platform_interface/camera_platform.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 4a1b112dace..649923e92ec 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -279,7 +279,8 @@ abstract class CameraPlatform extends PlatformInterface { /// on iOS. Will return an empty list on all other platforms. Future> getVideoStabilizationSupportedModes( int cameraId) async { - throw UnimplementedError(); + throw UnimplementedError( + 'getVideoStabilizationSupportedModes() is not implemented.'); } /// Sets the video stabilization mode for the selected camera. @@ -292,7 +293,7 @@ abstract class CameraPlatform extends PlatformInterface { /// mode is supplied. Future setVideoStabilizationMode( int cameraId, VideoStabilizationMode mode) async { - throw UnimplementedError(); + throw UnimplementedError('setVideoStabilizationMode() is not implemented.'); } /// Pause the active preview on the current frame for the selected camera. From a77bea18215fbd7ad4020cac4b070111c22fd51f Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Fri, 30 Aug 2024 02:19:14 +0100 Subject: [PATCH 16/24] minor improvements to CameraController method comments --- packages/camera/camera/lib/src/camera_controller.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 7e544323511..22410141766 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -704,7 +704,7 @@ class CameraController extends ValueNotifier { /// the supplied [mode] value should be a mode in the list returned /// by [getVideoStabilizationSupportedModes]. /// - /// Throws a [CameraException] when a not supported video stabilization + /// Throws a [CameraException] when an unsupported video stabilization /// mode is supplied. Future setVideoStabilizationMode(VideoStabilizationMode mode) async { _throwIfNotInitialized('setVideoStabilizationMode'); @@ -720,7 +720,7 @@ class CameraController extends ValueNotifier { /// /// Will return the list of supported video stabilization modes /// on Android (when using camera_android_camerax package) and - /// on iOS. Will return an empty list on all other platforms. + /// on iOS. Throws an [UnimplementedError] on all other platforms. Future> getVideoStabilizationSupportedModes() { _throwIfNotInitialized('isVideoStabilizationModeSupported'); From 5bb83794ef8d291c0859e47aef70781a77201e98 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Wed, 11 Sep 2024 22:47:41 +0100 Subject: [PATCH 17/24] Restructured video stabilization levels New levels are: off, level1, level2, level3 --- .../lib/src/android_camera_camerax.dart | 10 ++---- .../test/android_camera_camerax_test.dart | 9 +++-- .../lib/src/avfoundation_camera.dart | 8 ++--- .../test/avfoundation_camera_test.dart | 33 +++++-------------- .../src/types/video_stabilization_mode.dart | 29 ++++------------ .../method_channel_camera_test.dart | 26 +++++++-------- .../types/video_stabilization_mode_test.dart | 24 +++++++------- 7 files changed, 47 insertions(+), 92 deletions(-) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index d046a66436f..475f8f25db5 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -840,10 +840,7 @@ class AndroidCameraCameraX extends CameraPlatform { if (controlMode == CameraMetadata.controlVideoStabilizationModeOff) VideoStabilizationMode.off else if (controlMode == CameraMetadata.controlVideoStabilizationModeOn) - VideoStabilizationMode.on - else if (controlMode == - CameraMetadata.controlVideoStabilizationModePreviewStabilization) - VideoStabilizationMode.standard + VideoStabilizationMode.level1 ]; return modes; @@ -893,11 +890,8 @@ class AndroidCameraCameraX extends CameraPlatform { VideoStabilizationMode.off => CameraMetadata.controlVideoStabilizationModeOff, // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_ON - VideoStabilizationMode.on => + VideoStabilizationMode.level1 => CameraMetadata.controlVideoStabilizationModeOn, - // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION - VideoStabilizationMode.standard => - CameraMetadata.controlVideoStabilizationModePreviewStabilization, // TODO(ruicraveiro): add to future possible error codes documentation // https://github.com/flutter/flutter/issues/69298 diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 380bfcbe22e..1c012358757 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -2405,7 +2405,7 @@ void main() { expect( () => camera.setVideoStabilizationMode( - cameraId, VideoStabilizationMode.standard), + cameraId, VideoStabilizationMode.level2), throwsA(isA())); }); @@ -2442,11 +2442,11 @@ void main() { expect( () => camera.setVideoStabilizationMode( - cameraId, VideoStabilizationMode.cinematic), + cameraId, VideoStabilizationMode.level2), throwsA(isA())); expect( () => camera.setVideoStabilizationMode( - cameraId, VideoStabilizationMode.cinematicExtended), + cameraId, VideoStabilizationMode.level3), throwsA(isA())); }); @@ -2527,8 +2527,7 @@ void main() { modes, orderedEquals([ VideoStabilizationMode.off, - VideoStabilizationMode.on, - VideoStabilizationMode.standard, + VideoStabilizationMode.level1, ])); }); diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index 5b04ddbb652..3c6aa322db6 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -524,13 +524,11 @@ class AVFoundationCamera extends CameraPlatform { switch (videoStabilizationMode) { case VideoStabilizationMode.off: return PlatformVideoStabilizationMode.off; - case VideoStabilizationMode.on: - return null; - case VideoStabilizationMode.standard: + case VideoStabilizationMode.level1: return PlatformVideoStabilizationMode.standard; - case VideoStabilizationMode.cinematic: + case VideoStabilizationMode.level2: return PlatformVideoStabilizationMode.cinematic; - case VideoStabilizationMode.cinematicExtended: + case VideoStabilizationMode.level3: return PlatformVideoStabilizationMode.cinematicExtended; } // The enum comes from a different package, which could get a new value at diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index b7018cdac4d..a9de36d8174 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -586,24 +586,9 @@ void main() { .setVideoStabilizationMode(PlatformVideoStabilizationMode.off)); }); - test( - 'Should throw CameraException when off video stabilization mode is set', - () async { - const String code = 'VIDEO_STABILIIZATION_ERROR'; - const String message = 'Unavailable video stabilization mode.'; - - expect( - () => camera.setVideoStabilizationMode( - cameraId, VideoStabilizationMode.on), - throwsA(isA() - .having((CameraException e) => e.code, 'code', code) - .having((CameraException e) => e.description, 'description', - message))); - }); - - test('Should set video stabilization mode to standard', () async { + test('Should set video stabilization mode to level1', () async { await camera.setVideoStabilizationMode( - cameraId, VideoStabilizationMode.standard); + cameraId, VideoStabilizationMode.level1); verify(mockApi .setVideoStabilizationMode(PlatformVideoStabilizationMode.standard)); @@ -611,7 +596,7 @@ void main() { test('Should set video stabilization mode to cinematic', () async { await camera.setVideoStabilizationMode( - cameraId, VideoStabilizationMode.cinematic); + cameraId, VideoStabilizationMode.level2); verify(mockApi .setVideoStabilizationMode(PlatformVideoStabilizationMode.cinematic)); @@ -619,7 +604,7 @@ void main() { test('Should set video stabilization mode to cinematicExtended', () async { await camera.setVideoStabilizationMode( - cameraId, VideoStabilizationMode.cinematicExtended); + cameraId, VideoStabilizationMode.level3); verify(mockApi.setVideoStabilizationMode( PlatformVideoStabilizationMode.cinematicExtended)); @@ -654,7 +639,7 @@ void main() { expect(modes, [ VideoStabilizationMode.off, - VideoStabilizationMode.standard, + VideoStabilizationMode.level1, ]); }); @@ -667,9 +652,9 @@ void main() { expect(modes, [ VideoStabilizationMode.off, - VideoStabilizationMode.standard, - VideoStabilizationMode.cinematic, - VideoStabilizationMode.cinematicExtended, + VideoStabilizationMode.level1, + VideoStabilizationMode.level2, + VideoStabilizationMode.level3, ]); }); @@ -683,7 +668,7 @@ void main() { expect( () => camera.setVideoStabilizationMode( - cameraId, VideoStabilizationMode.cinematic), + cameraId, VideoStabilizationMode.level2), throwsA(isA() .having((CameraException e) => e.code, 'code', code) .having((CameraException e) => e.description, 'description', diff --git a/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart index 06f6b4e15f9..9868d899c96 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/video_stabilization_mode.dart @@ -7,31 +7,14 @@ enum VideoStabilizationMode { /// Video stabilization is disabled. off, - /// Basic video stabilization is enabled. - /// - /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_ON on Android - /// and throws CameraException on iOS. - on, + /// Least stabilized video stabilization mode with the least latency. + level1, - /// Standard video stabilization is enabled. - /// - /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android - /// (camera_android_camerax) and to AVCaptureVideoStabilizationModeStandard - /// on iOS. - standard, + /// More stabilized video with more latency. + level2, - /// Cinematic video stabilization is enabled. - /// - /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android - /// (camera_android_camerax) and to AVCaptureVideoStabilizationModeCinematic - /// on iOS. - cinematic, - - /// Extended cinematic video stabilization is enabled. - /// - /// Maps to AVCaptureVideoStabilizationModeCinematicExtended on iOS and - /// throws CameraException on Android. - cinematicExtended; + /// Most stabilized video with the most latency. + level3; /// Returns the video stabilization mode for a given String. factory VideoStabilizationMode.fromString(String str) { diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index 3791a782fbc..bcd984e65d0 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -1078,10 +1078,9 @@ void main() { methods: { 'getVideoStabilizationSupportedModes': [ 'off', - 'on', - 'standard', - 'cinematic', - 'cinematicExtended', + 'level1', + 'level2', + 'level3', ], }, ); @@ -1095,10 +1094,9 @@ void main() { // Assert expect(modes, [ VideoStabilizationMode.off, - VideoStabilizationMode.on, - VideoStabilizationMode.standard, - VideoStabilizationMode.cinematic, - VideoStabilizationMode.cinematicExtended, + VideoStabilizationMode.level1, + VideoStabilizationMode.level2, + VideoStabilizationMode.level3, ]); expect(channel.log, [ @@ -1123,15 +1121,15 @@ void main() { ); await camera.setVideoStabilizationMode( cameraId, - VideoStabilizationMode.standard, + VideoStabilizationMode.level1, ); await camera.setVideoStabilizationMode( cameraId, - VideoStabilizationMode.cinematic, + VideoStabilizationMode.level2, ); await camera.setVideoStabilizationMode( cameraId, - VideoStabilizationMode.cinematicExtended, + VideoStabilizationMode.level3, ); // Assert @@ -1144,17 +1142,17 @@ void main() { isMethodCall('setVideoStabilizationMode', arguments: { 'cameraId': cameraId, - 'mode': VideoStabilizationMode.standard.name, + 'mode': VideoStabilizationMode.level1.name, }), isMethodCall('setVideoStabilizationMode', arguments: { 'cameraId': cameraId, - 'mode': VideoStabilizationMode.cinematic.name, + 'mode': VideoStabilizationMode.level2.name, }), isMethodCall('setVideoStabilizationMode', arguments: { 'cameraId': cameraId, - 'mode': VideoStabilizationMode.cinematicExtended.name, + 'mode': VideoStabilizationMode.level3.name, }), ]); }); diff --git a/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart b/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart index b92bc5f8d77..8491d6d05d2 100644 --- a/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart +++ b/packages/camera/camera_platform_interface/test/types/video_stabilization_mode_test.dart @@ -6,31 +6,29 @@ import 'package:camera_platform_interface/src/types/video_stabilization_mode.dar import 'package:flutter_test/flutter_test.dart'; void main() { - test('VideoStabilizationMode should contain 3 options', () { + test('VideoStabilizationMode should contain 4 options', () { const List values = VideoStabilizationMode.values; - expect(values.length, 5); + expect(values.length, 4); }); test('VideoStabilizationMode enum should have items in correct index', () { const List values = VideoStabilizationMode.values; expect(values[0], VideoStabilizationMode.off); - expect(values[1], VideoStabilizationMode.on); - expect(values[2], VideoStabilizationMode.standard); - expect(values[3], VideoStabilizationMode.cinematic); - expect(values[4], VideoStabilizationMode.cinematicExtended); + expect(values[1], VideoStabilizationMode.level1); + expect(values[2], VideoStabilizationMode.level2); + expect(values[3], VideoStabilizationMode.level3); }); test('deserializeVideoStabilizationMode() should deserialize correctly', () { expect( VideoStabilizationMode.fromString('off'), VideoStabilizationMode.off); - expect(VideoStabilizationMode.fromString('on'), VideoStabilizationMode.on); - expect(VideoStabilizationMode.fromString('standard'), - VideoStabilizationMode.standard); - expect(VideoStabilizationMode.fromString('cinematic'), - VideoStabilizationMode.cinematic); - expect(VideoStabilizationMode.fromString('cinematicExtended'), - VideoStabilizationMode.cinematicExtended); + expect(VideoStabilizationMode.fromString('level1'), + VideoStabilizationMode.level1); + expect(VideoStabilizationMode.fromString('level2'), + VideoStabilizationMode.level2); + expect(VideoStabilizationMode.fromString('level3'), + VideoStabilizationMode.level3); }); } From 4fe99bb0eb6a7dfdaa8232d4766bdfa3c843c692 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Thu, 12 Sep 2024 21:51:54 +0100 Subject: [PATCH 18/24] Adjusted camera/camera unit tests for new VideoStabilizationMode enum values --- packages/camera/camera/test/camera_test.dart | 21 ++++++++----------- .../camera/camera/test/camera_value_test.dart | 9 ++++---- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 303c29c44d5..b4fc010714e 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1249,10 +1249,9 @@ void main() { .getVideoStabilizationSupportedModes(mockInitializeCamera)) .thenAnswer((_) async => [ VideoStabilizationMode.off, - VideoStabilizationMode.on, - VideoStabilizationMode.standard, - VideoStabilizationMode.cinematic, - VideoStabilizationMode.cinematicExtended, + VideoStabilizationMode.level1, + VideoStabilizationMode.level2, + VideoStabilizationMode.level3, ]); // act @@ -1262,10 +1261,9 @@ void main() { // assert expect(modes, [ VideoStabilizationMode.off, - VideoStabilizationMode.on, - VideoStabilizationMode.standard, - VideoStabilizationMode.cinematic, - VideoStabilizationMode.cinematicExtended, + VideoStabilizationMode.level1, + VideoStabilizationMode.level2, + VideoStabilizationMode.level3, ]); }); @@ -1298,8 +1296,7 @@ void main() { await cameraController.initialize(); when(CameraPlatform.instance.setVideoStabilizationMode( - cameraController.cameraId, - VideoStabilizationMode.cinematicExtended)) + cameraController.cameraId, VideoStabilizationMode.level3)) .thenThrow( PlatformException( code: 'TEST_ERROR', @@ -1308,8 +1305,8 @@ void main() { ); expect( - cameraController.setVideoStabilizationMode( - VideoStabilizationMode.cinematicExtended), + cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level3), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart index 835dd59b55b..5cbd83075b1 100644 --- a/packages/camera/camera/test/camera_value_test.dart +++ b/packages/camera/camera/test/camera_value_test.dart @@ -28,7 +28,7 @@ void main() { focusPointSupported: true, previewPauseOrientation: DeviceOrientation.portraitUp, description: FakeController.fakeDescription, - videoStabilizationMode: VideoStabilizationMode.cinematic, + videoStabilizationMode: VideoStabilizationMode.level2, ); expect(cameraValue, isA()); @@ -48,8 +48,7 @@ void main() { expect(cameraValue.recordingOrientation, DeviceOrientation.portraitUp); expect(cameraValue.isPreviewPaused, false); expect(cameraValue.previewPauseOrientation, DeviceOrientation.portraitUp); - expect( - cameraValue.videoStabilizationMode, VideoStabilizationMode.cinematic); + expect(cameraValue.videoStabilizationMode, VideoStabilizationMode.level2); }); test('Can be created as uninitialized', () { @@ -149,11 +148,11 @@ void main() { isPreviewPaused: true, previewPauseOrientation: DeviceOrientation.portraitUp, description: FakeController.fakeDescription, - videoStabilizationMode: VideoStabilizationMode.cinematicExtended, + videoStabilizationMode: VideoStabilizationMode.level3, ); expect(cameraValue.toString(), - 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, isPreviewPaused: true, previewPausedOrientation: DeviceOrientation.portraitUp, videoStabilizationMode: VideoStabilizationMode.cinematicExtended, description: CameraDescription(, CameraLensDirection.back, 0))'); + 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, isPreviewPaused: true, previewPausedOrientation: DeviceOrientation.portraitUp, videoStabilizationMode: VideoStabilizationMode.level3, description: CameraDescription(, CameraLensDirection.back, 0))'); }); }); } From 39bf897955aeaaa8c730b5cc4f99f450133dea2f Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Fri, 13 Sep 2024 23:16:53 +0100 Subject: [PATCH 19/24] Removed platform-specific comments --- .../lib/src/platform_interface/camera_platform.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 649923e92ec..00d5dfdc4fa 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -273,10 +273,6 @@ abstract class CameraPlatform extends PlatformInterface { } /// Gets a list of video stabilization modes that are supported for the selected camera. - /// - /// Will return the list of supported video stabilization modes - /// on Android (when using camera_android_camerax package) and - /// on iOS. Will return an empty list on all other platforms. Future> getVideoStabilizationSupportedModes( int cameraId) async { throw UnimplementedError( @@ -285,10 +281,6 @@ abstract class CameraPlatform extends PlatformInterface { /// Sets the video stabilization mode for the selected camera. /// - /// On Android (when using camera_android_camerax) and on iOS - /// the supplied [mode] value should be a mode in the list returned - /// by [getVideoStabilizationSupportedModes]. - /// /// Throws a [CameraException] when a not supported video stabilization /// mode is supplied. Future setVideoStabilizationMode( From f60a5ff8e726701ba264c17b626707968c4210af Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Wed, 12 Mar 2025 00:29:50 +0000 Subject: [PATCH 20/24] renamed getVideoStabilizationSupportedModes to getSupportedVideoStabilizationModes --- .../camera/lib/src/camera_controller.dart | 6 ++--- .../camera/test/camera_preview_test.dart | 2 +- packages/camera/camera/test/camera_test.dart | 22 ++++++++--------- .../lib/src/android_camera_camerax.dart | 6 ++--- .../test/android_camera_camerax_test.dart | 4 ++-- .../lib/src/avfoundation_camera.dart | 2 +- .../test/avfoundation_camera_test.dart | 6 ++--- .../method_channel/method_channel_camera.dart | 4 ++-- .../platform_interface/camera_platform.dart | 4 ++-- .../method_channel_camera_test.dart | 24 +++++++++---------- 10 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 2673606d60b..4ccb0ff3266 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -700,7 +700,7 @@ class CameraController extends ValueNotifier { /// /// On Android (when using camera_android_camerax) and on iOS /// the supplied [mode] value should be a mode in the list returned - /// by [getVideoStabilizationSupportedModes]. + /// by [getSupportedVideoStabilizationModes]. /// /// Throws a [CameraException] when an unsupported video stabilization /// mode is supplied. @@ -720,11 +720,11 @@ class CameraController extends ValueNotifier { /// on Android (when using camera_android_camerax package) and /// on iOS. Throws an [UnimplementedError] on all other platforms. Future> - getVideoStabilizationSupportedModes() { + getSupportedVideoStabilizationModes() { _throwIfNotInitialized('isVideoStabilizationModeSupported'); try { return CameraPlatform.instance - .getVideoStabilizationSupportedModes(_cameraId); + .getSupportedVideoStabilizationModes(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index 801310fae3a..ee67c338365 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -138,7 +138,7 @@ class FakeController extends ValueNotifier @override Future> - getVideoStabilizationSupportedModes() async => []; + getSupportedVideoStabilizationModes() async => []; @override bool supportsImageStreaming() => true; diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index b4fc010714e..090f14be216 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1190,7 +1190,7 @@ void main() { .called(4); }); - test('getVideoStabilizationSupportedModes() returns empty list', () async { + test('getSupportedVideoStabilizationModes() returns empty list', () async { // arrange final CameraController cameraController = CameraController( const CameraDescription( @@ -1201,18 +1201,18 @@ void main() { await cameraController.initialize(); when(CameraPlatform.instance - .getVideoStabilizationSupportedModes(mockInitializeCamera)) + .getSupportedVideoStabilizationModes(mockInitializeCamera)) .thenAnswer((_) async => []); // act final Iterable modes = - await cameraController.getVideoStabilizationSupportedModes(); + await cameraController.getSupportedVideoStabilizationModes(); // assert expect(modes, []); }); - test('getVideoStabilizationSupportedModes() returns off', () async { + test('getSupportedVideoStabilizationModes() returns off', () async { // arrange final CameraController cameraController = CameraController( const CameraDescription( @@ -1223,19 +1223,19 @@ void main() { await cameraController.initialize(); when(CameraPlatform.instance - .getVideoStabilizationSupportedModes(mockInitializeCamera)) + .getSupportedVideoStabilizationModes(mockInitializeCamera)) .thenAnswer((_) async => [VideoStabilizationMode.off]); // act final Iterable modes = - await cameraController.getVideoStabilizationSupportedModes(); + await cameraController.getSupportedVideoStabilizationModes(); // assert expect(modes, [VideoStabilizationMode.off]); }); - test('getVideoStabilizationSupportedModes() returns all modes', () async { + test('getSupportedVideoStabilizationModes() returns all modes', () async { // arrange final CameraController cameraController = CameraController( const CameraDescription( @@ -1246,7 +1246,7 @@ void main() { await cameraController.initialize(); when(CameraPlatform.instance - .getVideoStabilizationSupportedModes(mockInitializeCamera)) + .getSupportedVideoStabilizationModes(mockInitializeCamera)) .thenAnswer((_) async => [ VideoStabilizationMode.off, VideoStabilizationMode.level1, @@ -1256,7 +1256,7 @@ void main() { // act final Iterable modes = - await cameraController.getVideoStabilizationSupportedModes(); + await cameraController.getSupportedVideoStabilizationModes(); // assert expect(modes, [ @@ -1728,11 +1728,11 @@ class MockCameraPlatform extends Mock ) as Future; @override - Future> getVideoStabilizationSupportedModes( + Future> getSupportedVideoStabilizationModes( int cameraId) { return super.noSuchMethod( Invocation.method( - #getVideoStabilizationSupportedModes, [cameraId]), + #getSupportedVideoStabilizationModes, [cameraId]), returnValue: Future>.value( []), ) as Future>; diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 7c80ad8beab..9d02f416e0e 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -796,7 +796,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// Gets a list of video stabilization modes that are supported for the /// selected camera. @override - Future> getVideoStabilizationSupportedModes( + Future> getSupportedVideoStabilizationModes( int cameraId) async { final CameraInfo? camInfo = cameraInfo; if (camInfo == null) { @@ -828,7 +828,7 @@ class AndroidCameraCameraX extends CameraPlatform { Future setVideoStabilizationMode( int cameraId, VideoStabilizationMode mode) async { final Iterable availableModes = - await getVideoStabilizationSupportedModes(cameraId); + await getSupportedVideoStabilizationModes(cameraId); if (!availableModes.contains(mode)) { // TODO(ruicraveiro): add to future possible error codes documentation @@ -859,7 +859,7 @@ class AndroidCameraCameraX extends CameraPlatform { /// to the Android specific control video stabilization mode static int _getControlVideoStabilizationMode(VideoStabilizationMode mode) { // if new modes need to be supported, the opposite of this mapping - // code is in [getVideoStabilizationSupportedModes(...)] in this class, + // code is in [getSupportedVideoStabilizationModes(...)] in this class, // so don't forget to review that method as well. final int controlMode = switch (mode) { diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index eccb4bc38ee..d162d4d377a 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -2324,7 +2324,7 @@ void main() { ); final Iterable modes = - await camera.getVideoStabilizationSupportedModes(cameraId); + await camera.getSupportedVideoStabilizationModes(cameraId); expect(modes, isEmpty); }); @@ -2362,7 +2362,7 @@ void main() { ); final Iterable modes = - await camera.getVideoStabilizationSupportedModes(cameraId); + await camera.getSupportedVideoStabilizationModes(cameraId); expect( modes, diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index 741b08be448..078df1a58c7 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -388,7 +388,7 @@ class AVFoundationCamera extends CameraPlatform { } @override - Future> getVideoStabilizationSupportedModes( + Future> getSupportedVideoStabilizationModes( int cameraId) async { final Set ret = {}; diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 2d7706b7a6e..4ff3f04d29a 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -615,7 +615,7 @@ void main() { .thenAnswer((_) async => false); final Iterable modes = - await camera.getVideoStabilizationSupportedModes(cameraId); + await camera.getSupportedVideoStabilizationModes(cameraId); expect(modes, isEmpty); }); @@ -635,7 +635,7 @@ void main() { .thenAnswer((_) async => false); final List modes = - (await camera.getVideoStabilizationSupportedModes(cameraId)).toList(); + (await camera.getSupportedVideoStabilizationModes(cameraId)).toList(); expect(modes, [ VideoStabilizationMode.off, @@ -648,7 +648,7 @@ void main() { .thenAnswer((_) async => true); final List modes = - (await camera.getVideoStabilizationSupportedModes(cameraId)).toList(); + (await camera.getSupportedVideoStabilizationModes(cameraId)).toList(); expect(modes, [ VideoStabilizationMode.off, diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index c96ce84ebef..258a0583a98 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -503,11 +503,11 @@ class MethodChannelCamera extends CameraPlatform { } @override - Future> getVideoStabilizationSupportedModes( + Future> getSupportedVideoStabilizationModes( int cameraId) async { try { final List? modes = await _channel.invokeMethod>( - 'getVideoStabilizationSupportedModes', + 'getSupportedVideoStabilizationModes', { 'cameraId': cameraId, }, diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index 80a3c419e55..c62f8323977 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -276,10 +276,10 @@ abstract class CameraPlatform extends PlatformInterface { } /// Gets a list of video stabilization modes that are supported for the selected camera. - Future> getVideoStabilizationSupportedModes( + Future> getSupportedVideoStabilizationModes( int cameraId) async { throw UnimplementedError( - 'getVideoStabilizationSupportedModes() is not implemented.'); + 'getSupportedVideoStabilizationModes() is not implemented.'); } /// Sets the video stabilization mode for the selected camera. diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index bcd984e65d0..fb2e4549cfa 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -1014,19 +1014,19 @@ void main() { 'Illegal zoom error'))); }); - test('Should get empty list from getVideoStabilizationSupportedModes', + test('Should get empty list from getSupportedVideoStabilizationModes', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'getVideoStabilizationSupportedModes': [], + 'getSupportedVideoStabilizationModes': [], }, ); // Act final Iterable modes = - await camera.getVideoStabilizationSupportedModes( + await camera.getSupportedVideoStabilizationModes( cameraId, ); @@ -1034,7 +1034,7 @@ void main() { expect(modes, []); expect(channel.log, [ - isMethodCall('getVideoStabilizationSupportedModes', + isMethodCall('getSupportedVideoStabilizationModes', arguments: { 'cameraId': cameraId, }), @@ -1042,19 +1042,19 @@ void main() { }); test( - 'Should get list containing off from getVideoStabilizationSupportedModes', + 'Should get list containing off from getSupportedVideoStabilizationModes', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'getVideoStabilizationSupportedModes': ['off'], + 'getSupportedVideoStabilizationModes': ['off'], }, ); // Act final Iterable modes = - await camera.getVideoStabilizationSupportedModes( + await camera.getSupportedVideoStabilizationModes( cameraId, ); @@ -1062,7 +1062,7 @@ void main() { expect(modes, [VideoStabilizationMode.off]); expect(channel.log, [ - isMethodCall('getVideoStabilizationSupportedModes', + isMethodCall('getSupportedVideoStabilizationModes', arguments: { 'cameraId': cameraId, }), @@ -1070,13 +1070,13 @@ void main() { }); test( - 'Should get list containing all from getVideoStabilizationSupportedModes', + 'Should get list containing all from getSupportedVideoStabilizationModes', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { - 'getVideoStabilizationSupportedModes': [ + 'getSupportedVideoStabilizationModes': [ 'off', 'level1', 'level2', @@ -1087,7 +1087,7 @@ void main() { // Act final Iterable modes = - await camera.getVideoStabilizationSupportedModes( + await camera.getSupportedVideoStabilizationModes( cameraId, ); @@ -1100,7 +1100,7 @@ void main() { ]); expect(channel.log, [ - isMethodCall('getVideoStabilizationSupportedModes', + isMethodCall('getSupportedVideoStabilizationModes', arguments: { 'cameraId': cameraId, }), From 6689162bfc522ca5a46f3936e1edae322483fe27 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Wed, 12 Mar 2025 08:10:54 +0000 Subject: [PATCH 21/24] invalid video stabilization modes throw ArgumentError --- .../lib/src/android_camera_camerax.dart | 87 ++++++++----------- .../test/android_camera_camerax_test.dart | 14 +-- .../lib/src/avfoundation_camera.dart | 22 +++-- .../test/avfoundation_camera_test.dart | 45 ++++++++-- 4 files changed, 92 insertions(+), 76 deletions(-) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 9d02f416e0e..e10b8fe65e8 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -798,46 +798,20 @@ class AndroidCameraCameraX extends CameraPlatform { @override Future> getSupportedVideoStabilizationModes( int cameraId) async { - final CameraInfo? camInfo = cameraInfo; - if (camInfo == null) { - return []; - } - final Camera2CameraInfo cam2Info = - await proxy.getCamera2CameraInfo(camInfo); - - final List controlModes = - await cam2Info.getAvailableVideoStabilizationModes(); - - /// If new modes need to be supported, the opposite of this mapping - /// code is in [_getControlVideoStabilizationMode(...)] in this class, - /// so don't forget to review that method as well. - - final List modes = [ - for (final int controlMode in controlModes) - if (controlMode == CameraMetadata.controlVideoStabilizationModeOff) - VideoStabilizationMode.off - else if (controlMode == CameraMetadata.controlVideoStabilizationModeOn) - VideoStabilizationMode.level1 - ]; - - return modes; + return (await _getSupportedVideoStabilizationModeMap(cameraId)).keys; } /// Set the video stabilization mode for the selected camera. @override Future setVideoStabilizationMode( int cameraId, VideoStabilizationMode mode) async { - final Iterable availableModes = - await getSupportedVideoStabilizationModes(cameraId); - - if (!availableModes.contains(mode)) { - // TODO(ruicraveiro): add to future possible error codes documentation - // https://github.com/flutter/flutter/issues/69298 - throw CameraException('VIDEO_STABILIIZATION_ERROR', - 'Unavailable video stabilization mode.'); - } + final Map availableModes = + await _getSupportedVideoStabilizationModeMap(cameraId); - final int controlMode = _getControlVideoStabilizationMode(mode); + final int? controlMode = availableModes[mode]; + if (controlMode == null) { + throw ArgumentError('Unavailable video stabilization mode.', 'mode'); + } final CaptureRequestOptions captureRequestOptions = proxy .createCaptureRequestOptions(<( @@ -855,27 +829,34 @@ class AndroidCameraCameraX extends CameraPlatform { await camera2Control.addCaptureRequestOptions(captureRequestOptions); } - /// Maps the common platform VideoStabilizationMode - /// to the Android specific control video stabilization mode - static int _getControlVideoStabilizationMode(VideoStabilizationMode mode) { - // if new modes need to be supported, the opposite of this mapping - // code is in [getSupportedVideoStabilizationModes(...)] in this class, - // so don't forget to review that method as well. - - final int controlMode = switch (mode) { - // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_OFF - VideoStabilizationMode.off => - CameraMetadata.controlVideoStabilizationModeOff, - // https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_ON - VideoStabilizationMode.level1 => - CameraMetadata.controlVideoStabilizationModeOn, - - // TODO(ruicraveiro): add to future possible error codes documentation - // https://github.com/flutter/flutter/issues/69298 - _ => throw CameraException( - 'VIDEO_STABILIIZATION_ERROR', 'Unavailable video stabilization mode.') + /// Gets a map of video stabilization control modes that are supported for the + /// selected camera, indexed by the respective [VideoStabilizationMode]. + Future> + _getSupportedVideoStabilizationModeMap(int cameraId) async { + final CameraInfo? camInfo = cameraInfo; + if (camInfo == null) { + return {}; + } + final Camera2CameraInfo cam2Info = + await proxy.getCamera2CameraInfo(camInfo); + + final List controlModes = + await cam2Info.getAvailableVideoStabilizationModes(); + + /// If new modes need to be supported, the opposite of this mapping + /// code is in [_getControlVideoStabilizationMode(...)] in this class, + /// so don't forget to review that method as well. + + final Map modes = + { + for (final int controlMode in controlModes) + if (controlMode == CameraMetadata.controlVideoStabilizationModeOff) + VideoStabilizationMode.off: controlMode + else if (controlMode == CameraMetadata.controlVideoStabilizationModeOn) + VideoStabilizationMode.level1: controlMode }; - return controlMode; + + return modes; } /// The ui orientation changed. diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index d162d4d377a..49e84fa341f 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -2212,8 +2212,7 @@ void main() { expect(requestedOptions.first.$2, equals(0)); }); - test( - 'setVideoStabilizationMode throws CameraException when mode not available', + test('setVideoStabilizationMode throws ArgumentError when mode not available', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 78; @@ -2247,10 +2246,11 @@ void main() { expect( () => camera.setVideoStabilizationMode( cameraId, VideoStabilizationMode.level2), - throwsA(isA())); + throwsA(isA() + .having((ArgumentError e) => e.name, 'name', 'mode'))); }); - test('setVideoStabilizationMode throws CameraException when mode not mapped', + test('setVideoStabilizationMode throws ArgumentError when mode not mapped', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 78; @@ -2284,11 +2284,13 @@ void main() { expect( () => camera.setVideoStabilizationMode( cameraId, VideoStabilizationMode.level2), - throwsA(isA())); + throwsA(isA() + .having((ArgumentError e) => e.name, 'name', 'mode'))); expect( () => camera.setVideoStabilizationMode( cameraId, VideoStabilizationMode.level3), - throwsA(isA())); + throwsA(isA() + .having((ArgumentError e) => e.name, 'name', 'mode'))); }); test('getVideoStabilizationMode returns no available mode', () async { diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index 078df1a58c7..2be9e4b1c86 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -373,13 +373,13 @@ class AVFoundationCamera extends CameraPlatform { Future setVideoStabilizationMode( int cameraId, VideoStabilizationMode mode) async { try { - final PlatformVideoStabilizationMode? platformMode = - _pigeonVideoStabilizationMode(mode); + final Map + availableModes = + await _getSupportedVideoStabilizationModeMap(cameraId); + + final PlatformVideoStabilizationMode? platformMode = availableModes[mode]; if (platformMode == null) { - // TODO(ruicraveiro): add to future possible error codes documentation - // https://github.com/flutter/flutter/issues/69298 - throw CameraException('VIDEO_STABILIIZATION_ERROR', - 'Unavailable video stabilization mode.'); + throw ArgumentError('Unavailable video stabilization mode.', 'mode'); } await _hostApi.setVideoStabilizationMode(platformMode); } on PlatformException catch (e) { @@ -390,7 +390,13 @@ class AVFoundationCamera extends CameraPlatform { @override Future> getSupportedVideoStabilizationModes( int cameraId) async { - final Set ret = {}; + return (await _getSupportedVideoStabilizationModeMap(cameraId)).keys; + } + + Future> + _getSupportedVideoStabilizationModeMap(int cameraId) async { + final Map ret = + {}; for (final VideoStabilizationMode mode in VideoStabilizationMode.values) { final PlatformVideoStabilizationMode? platformMode = @@ -399,7 +405,7 @@ class AVFoundationCamera extends CameraPlatform { final bool isSupported = await _hostApi.isVideoStabilizationModeSupported(platformMode); if (isSupported) { - ret.add(mode); + ret[mode] = platformMode; } } } diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 4ff3f04d29a..90481e93b51 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -579,6 +579,10 @@ void main() { }); test('Should set video stabilization mode to off', () async { + when(mockApi.isVideoStabilizationModeSupported( + PlatformVideoStabilizationMode.off)) + .thenAnswer((_) async => true); + await camera.setVideoStabilizationMode( cameraId, VideoStabilizationMode.off); @@ -587,6 +591,10 @@ void main() { }); test('Should set video stabilization mode to level1', () async { + when(mockApi.isVideoStabilizationModeSupported( + PlatformVideoStabilizationMode.standard)) + .thenAnswer((_) async => true); + await camera.setVideoStabilizationMode( cameraId, VideoStabilizationMode.level1); @@ -595,6 +603,10 @@ void main() { }); test('Should set video stabilization mode to cinematic', () async { + when(mockApi.isVideoStabilizationModeSupported( + PlatformVideoStabilizationMode.cinematic)) + .thenAnswer((_) async => true); + await camera.setVideoStabilizationMode( cameraId, VideoStabilizationMode.level2); @@ -603,6 +615,10 @@ void main() { }); test('Should set video stabilization mode to cinematicExtended', () async { + when(mockApi.isVideoStabilizationModeSupported( + PlatformVideoStabilizationMode.cinematicExtended)) + .thenAnswer((_) async => true); + await camera.setVideoStabilizationMode( cameraId, VideoStabilizationMode.level3); @@ -659,20 +675,31 @@ void main() { }); test( - 'Should throw CameraException when unavailable video stabilization mode is set', + 'Should throw ArgumentError when unavailable video stabilization mode is set', () async { - const String code = 'VIDEO_STABILIIZATION_ERROR'; - const String message = 'Unavailable video stabilization mode error'; - when(mockApi.setVideoStabilizationMode(any)).thenAnswer( - (_) async => throw PlatformException(code: code, message: message)); + when(mockApi.isVideoStabilizationModeSupported(any)) + .thenAnswer((_) async => false); + expect( + () => camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.off), + throwsA(isA() + .having((ArgumentError e) => e.name, 'name', 'mode'))); + expect( + () => camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.level1), + throwsA(isA() + .having((ArgumentError e) => e.name, 'name', 'mode'))); expect( () => camera.setVideoStabilizationMode( cameraId, VideoStabilizationMode.level2), - throwsA(isA() - .having((CameraException e) => e.code, 'code', code) - .having((CameraException e) => e.description, 'description', - message))); + throwsA(isA() + .having((ArgumentError e) => e.name, 'name', 'mode'))); + expect( + () => camera.setVideoStabilizationMode( + cameraId, VideoStabilizationMode.level3), + throwsA(isA() + .having((ArgumentError e) => e.name, 'name', 'mode'))); }); test('Should set the focus point to a value', () async { From 068141d86f820307a3fa371dcce61a4986990b38 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Wed, 12 Mar 2025 11:45:30 +0000 Subject: [PATCH 22/24] fixed ios nits --- .../Sources/camera_avfoundation/CameraPlugin.m | 2 +- .../Sources/camera_avfoundation/CameraProperties.m | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m index 0c4728238a7..a4f48f050df 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m @@ -466,7 +466,7 @@ - (void)isVideoStabilizationModeSupported:(FCPPlatformVideoStabilizationMode)mod __weak typeof(self) weakSelf = self; dispatch_async(self.captureSessionQueue, ^{ - bool isSupported = [weakSelf.camera isVideoStabilizationModeSupported:mode]; + BOOL isSupported = [weakSelf.camera isVideoStabilizationModeSupported:mode]; completion(@(isSupported), nil); }); } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraProperties.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraProperties.m index af8a6df4e9d..9421f35b637 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraProperties.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraProperties.m @@ -63,10 +63,8 @@ AVCaptureVideoStabilizationMode getAvCaptureVideoStabilizationMode( return AVCaptureVideoStabilizationModeOff; case FCPPlatformVideoStabilizationModeStandard: return AVCaptureVideoStabilizationModeStandard; - case FCPPlatformVideoStabilizationModeCinematic: return AVCaptureVideoStabilizationModeCinematic; - case FCPPlatformVideoStabilizationModeCinematicExtended: if (@available(iOS 13.0, *)) { return AVCaptureVideoStabilizationModeCinematicExtended; From 75382f64da70b99f9ebf537a4ff0b53fbebbf502 Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Wed, 12 Mar 2025 20:09:24 +0000 Subject: [PATCH 23/24] Video stabilization fallback --- .../camera/lib/src/camera_controller.dart | 48 +- .../camera/test/camera_preview_test.dart | 5 +- packages/camera/camera/test/camera_test.dart | 432 +++++++++++++++++- .../lib/src/android_camera_camerax.dart | 3 + .../platform_interface/camera_platform.dart | 2 +- 5 files changed, 469 insertions(+), 21 deletions(-) diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index 4ccb0ff3266..5331199681e 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -702,18 +702,58 @@ class CameraController extends ValueNotifier { /// the supplied [mode] value should be a mode in the list returned /// by [getSupportedVideoStabilizationModes]. /// - /// Throws a [CameraException] when an unsupported video stabilization - /// mode is supplied. - Future setVideoStabilizationMode(VideoStabilizationMode mode) async { + /// When [allowFallback] is true (default) and when + /// the camera supports any video stabilization other than + /// [VideoStabilizationMode.off], then the camera will + /// be set to the best video stabilization mode up to, and including, [mode]. + /// + /// When either [allowFallback] is false or the only + /// supported video stabilization mode is [VideoStabilizationMode.off], + /// and if [mode] is not one of the supported modes, + /// then it throws an [ArgumentError]. + Future setVideoStabilizationMode( + VideoStabilizationMode mode, { + bool allowFallback = true, + }) async { _throwIfNotInitialized('setVideoStabilizationMode'); try { - await CameraPlatform.instance.setVideoStabilizationMode(_cameraId, mode); + final VideoStabilizationMode requestMode = + allowFallback ? await _getVideoStabilizationFallbackMode(mode) : mode; + + await CameraPlatform.instance + .setVideoStabilizationMode(_cameraId, requestMode); value = value.copyWith(videoStabilizationMode: mode); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } + Future _getVideoStabilizationFallbackMode( + VideoStabilizationMode mode) async { + final Iterable supportedModes = await CameraPlatform + .instance + .getSupportedVideoStabilizationModes(_cameraId); + + // if there are no supported modes or if the only supported mode is Off + // and something else is requested, then we throw an ArgumentError. + if (supportedModes.isEmpty || + (mode != VideoStabilizationMode.off && + supportedModes.every((VideoStabilizationMode sm) => + sm == VideoStabilizationMode.off))) { + throw ArgumentError('Unavailable video stabilization mode.', 'mode'); + } + + VideoStabilizationMode requestMode = VideoStabilizationMode.off; + for (final VideoStabilizationMode supportedMode in supportedModes) { + if (supportedMode.index <= mode.index && + supportedMode.index >= requestMode.index) { + requestMode = supportedMode; + } + } + + return requestMode; + } + /// Gets a list of video stabilization modes that are supported for the selected camera. /// /// Will return the list of supported video stabilization modes diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index ee67c338365..a4cf9d3d01e 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -134,7 +134,10 @@ class FakeController extends ValueNotifier CameraDescription get description => value.description; @override - Future setVideoStabilizationMode(VideoStabilizationMode mode) async {} + Future setVideoStabilizationMode( + VideoStabilizationMode mode, { + bool allowFallback = true, + }) async {} @override Future> diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 090f14be216..51a3f065e8e 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1267,7 +1267,95 @@ void main() { ]); }); - test('setVideoStabilizationMode() calls CameraPlatform', () async { + test( + 'setVideoStabilizationMode() throws $CameraException on $PlatformException', + () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => [ + VideoStabilizationMode.off, + VideoStabilizationMode.level1, + ]); + + when(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level1)) + .thenThrow( + PlatformException( + code: 'TEST_ERROR', + message: 'This is a test error message', + ), + ); + + expect( + cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level1), + throwsA(isA().having( + (CameraException error) => error.description, + 'TEST_ERROR', + 'This is a test error message', + ))); + }); + + test( + 'setVideoStabilizationMode() throws $ArgumentError when no supported mode is available', + () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => []); + + expect( + cameraController + .setVideoStabilizationMode(VideoStabilizationMode.off), + throwsA(isA().having( + (ArgumentError error) => error.name, + 'name', + 'mode', + ))); + expect( + cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level1), + throwsA(isA().having( + (ArgumentError error) => error.name, + 'name', + 'mode', + ))); + expect( + cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level2), + throwsA(isA().having( + (ArgumentError error) => error.name, + 'name', + 'mode', + ))); + expect( + cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level3), + throwsA(isA().having( + (ArgumentError error) => error.name, + 'name', + 'mode', + ))); + }); + + test( + 'setVideoStabilizationMode() throws $ArgumentError when only Off mode is available', + () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', @@ -1276,6 +1364,55 @@ void main() { ResolutionPreset.max); await cameraController.initialize(); + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => [ + VideoStabilizationMode.off, + ]); + + expect( + cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level1), + throwsA(isA().having( + (ArgumentError error) => error.name, + 'name', + 'mode', + ))); + expect( + cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level2), + throwsA(isA().having( + (ArgumentError error) => error.name, + 'name', + 'mode', + ))); + expect( + cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level3), + throwsA(isA().having( + (ArgumentError error) => error.name, + 'name', + 'mode', + ))); + }); + + test( + 'setVideoStabilizationMode() calls CameraPlatform with VideoStabilizationMode.off', + () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => [ + VideoStabilizationMode.off, + ]); + await cameraController .setVideoStabilizationMode(VideoStabilizationMode.off); @@ -1285,7 +1422,7 @@ void main() { }); test( - 'setVideoStabilizationMode() throws $CameraException on $PlatformException', + 'setVideoStabilizationMode() calls CameraPlatform with fallback level1', () async { final CameraController cameraController = CameraController( const CameraDescription( @@ -1295,23 +1432,288 @@ void main() { ResolutionPreset.max); await cameraController.initialize(); + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => [ + VideoStabilizationMode.off, + VideoStabilizationMode.level1, + ]); + + when(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level1)) + .thenAnswer((_) async {}); + when(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level2)) + .thenAnswer((_) async {}); when(CameraPlatform.instance.setVideoStabilizationMode( cameraController.cameraId, VideoStabilizationMode.level3)) - .thenThrow( - PlatformException( - code: 'TEST_ERROR', - message: 'This is a test error message', - ), + .thenAnswer((_) async {}); + + clearInteractions(CameraPlatform.instance); + + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.off); + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level1); + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level2); + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level3); + + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.off)) + .called(1); + + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level1)) + .called(3); + + verifyNever(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level2)); + + verifyNever(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level3)); + }); + + test( + 'setVideoStabilizationMode() calls CameraPlatform with fallback level2', + () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => [ + VideoStabilizationMode.off, + VideoStabilizationMode.level1, + VideoStabilizationMode.level2, + ]); + + clearInteractions(CameraPlatform.instance); + + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.off); + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level1); + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level2); + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level3); + + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.off)) + .called(1); + + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level1)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level2)) + .called(2); + verifyNever(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level3)); + }); + + test( + 'setVideoStabilizationMode() calls CameraPlatform with fallback level3', + () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => [ + VideoStabilizationMode.off, + VideoStabilizationMode.level1, + VideoStabilizationMode.level2, + VideoStabilizationMode.level3, + ]); + + clearInteractions(CameraPlatform.instance); + + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.off); + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level1); + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level2); + await cameraController + .setVideoStabilizationMode(VideoStabilizationMode.level3); + + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.off)) + .called(1); + + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level1)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level2)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level3)) + .called(1); + }); + + test( + 'setVideoStabilizationMode() with false allowFallback calls platform with mode as is when no supported mode is available', + () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => []); + + clearInteractions(CameraPlatform.instance); + + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.off, + allowFallback: false, + ); + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.level1, + allowFallback: false, + ); + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.level2, + allowFallback: false, + ); + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.level3, + allowFallback: false, ); - expect( - cameraController - .setVideoStabilizationMode(VideoStabilizationMode.level3), - throwsA(isA().having( - (CameraException error) => error.description, - 'TEST_ERROR', - 'This is a test error message', - ))); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.off)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level1)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level2)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level3)) + .called(1); + }); + + test( + 'setVideoStabilizationMode() with false allowFallback calls platform with mode as is when only VideoStabilizationMode.off is available', + () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => [ + VideoStabilizationMode.off, + ]); + + clearInteractions(CameraPlatform.instance); + + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.off, + allowFallback: false, + ); + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.level1, + allowFallback: false, + ); + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.level2, + allowFallback: false, + ); + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.level3, + allowFallback: false, + ); + + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.off)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level1)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level2)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level3)) + .called(1); + }); + + test( + 'setVideoStabilizationMode() with false allowFallback calls platform with mode as is when all modes are available', + () async { + final CameraController cameraController = CameraController( + const CameraDescription( + name: 'cam', + lensDirection: CameraLensDirection.back, + sensorOrientation: 90), + ResolutionPreset.max); + await cameraController.initialize(); + + when(CameraPlatform.instance + .getSupportedVideoStabilizationModes(mockInitializeCamera)) + .thenAnswer((_) async => [ + VideoStabilizationMode.off, + VideoStabilizationMode.level1, + VideoStabilizationMode.level2, + VideoStabilizationMode.level3, + ]); + + clearInteractions(CameraPlatform.instance); + + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.off, + allowFallback: false, + ); + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.level1, + allowFallback: false, + ); + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.level2, + allowFallback: false, + ); + await cameraController.setVideoStabilizationMode( + VideoStabilizationMode.level3, + allowFallback: false, + ); + + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.off)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level1)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level2)) + .called(1); + verify(CameraPlatform.instance.setVideoStabilizationMode( + cameraController.cameraId, VideoStabilizationMode.level3)) + .called(1); }); test('pausePreview() calls $CameraPlatform', () async { diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index e10b8fe65e8..cf6673454c5 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -802,6 +802,9 @@ class AndroidCameraCameraX extends CameraPlatform { } /// Set the video stabilization mode for the selected camera. + /// + /// Throws a [ArgumentError] when an unsupported [mode] is + /// supplied. @override Future setVideoStabilizationMode( int cameraId, VideoStabilizationMode mode) async { diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index c62f8323977..303ff5a8ba4 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -284,7 +284,7 @@ abstract class CameraPlatform extends PlatformInterface { /// Sets the video stabilization mode for the selected camera. /// - /// Throws a [CameraException] when a not supported video stabilization + /// Throws a [ArgumentError] when a not supported video stabilization /// mode is supplied. Future setVideoStabilizationMode( int cameraId, VideoStabilizationMode mode) async { From 0317ccec3956b07a50544c05a72b60f783e4155b Mon Sep 17 00:00:00 2001 From: ruicraveiro Date: Thu, 13 Mar 2025 17:45:13 +0000 Subject: [PATCH 24/24] removed vestigial comments --- .../lib/src/android_camera_camerax.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index cf6673454c5..7dbd35c235a 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -846,10 +846,6 @@ class AndroidCameraCameraX extends CameraPlatform { final List controlModes = await cam2Info.getAvailableVideoStabilizationModes(); - /// If new modes need to be supported, the opposite of this mapping - /// code is in [_getControlVideoStabilizationMode(...)] in this class, - /// so don't forget to review that method as well. - final Map modes = { for (final int controlMode in controlModes)