Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[camera] add video stabilization #7108

Open
wants to merge 49 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9bdf58e
Adds video stabilization to camera
ruicraveiro Jul 16, 2024
39e2adf
do not merge! dependency overrides
ruicraveiro Jul 16, 2024
edf33b0
Update camera_info.dart
ruicraveiro Jul 16, 2024
741e032
Moved getAvCaptureVideoStabilizationMode from FLTCam to CameraProperties
ruicraveiro Jul 16, 2024
738ba2d
Merge branch 'main' into vs_squashed
ruicraveiro Jul 17, 2024
b404388
Merge branch 'main' into vs_squashed
ruicraveiro Jul 18, 2024
c900fa4
Merge branch 'main' into vs_squashed
ruicraveiro Jul 18, 2024
e1f7cb8
Merge branch 'main' into vs_squashed
ruicraveiro Jul 19, 2024
d220078
Refactored Camera2CameraInfoHostApiImpl with single @OptIn annotation
ruicraveiro Jul 22, 2024
887c565
Moved Android video stabilization mapping to Camera2CameraInfo
ruicraveiro Jul 22, 2024
8d986ed
fixed android camerax tests
ruicraveiro Jul 22, 2024
8040dc6
Merge branch 'main' into vs_squashed
ruicraveiro Jul 22, 2024
dba6738
Merge branch 'main' into vs_squashed
ruicraveiro Jul 23, 2024
f027dfc
Merge branch 'main' into vs_squashed
ruicraveiro Jul 24, 2024
b85a0be
Merge branch 'main' into vs_squashed
ruicraveiro Jul 24, 2024
291a740
Merge branch 'main' into vs_squashed
ruicraveiro Jul 25, 2024
1d06d3d
Merge branch 'main' into vs_squashed
ruicraveiro Jul 27, 2024
680097f
Better naming for local variable in Camera2CameraInfoHostApi.getAvail…
ruicraveiro Jul 29, 2024
a8e6544
video stabilization mapping back to android_camera_camerax
ruicraveiro Jul 29, 2024
754c577
Merge branch 'main' into vs_squashed
ruicraveiro Jul 30, 2024
55a4da8
Merge branch 'main' into vs_squashed
ruicraveiro Jul 30, 2024
06719a5
Merge branch 'main' into vs_squashed
ruicraveiro Aug 1, 2024
15d918c
Merge branch 'main' into vs_squashed
ruicraveiro Aug 2, 2024
69eb909
Merge branch 'vs_squashed' of github.com:ruicraveiro/packages into vs…
ruicraveiro Aug 5, 2024
672d834
Merge branch 'main' into vs_squashed
ruicraveiro Aug 5, 2024
1cef9b3
Merge branch 'main' into vs_squashed
ruicraveiro Aug 6, 2024
e6b483f
Update pubspec.yaml
ruicraveiro Aug 6, 2024
45fa745
Update packages/camera/camera_android_camerax/lib/src/android_camera_…
ruicraveiro Aug 6, 2024
ddc370c
Merge branch 'main' into vs_squashed
ruicraveiro Aug 7, 2024
1e4885c
Merge branch 'main' into vs_squashed
ruicraveiro Aug 17, 2024
183088d
Merge branch 'main' into vs_squashed
ruicraveiro Aug 21, 2024
c333768
video stabilizaton methods throw unimplemented
ruicraveiro Aug 21, 2024
94651bc
Using enhanced enum features with VideoStabilizationMode
ruicraveiro Aug 21, 2024
1ec3f54
Removed $ in name of test
ruicraveiro Aug 21, 2024
a9366c2
Merge branch 'main' into vs_squashed
ruicraveiro Aug 23, 2024
81afd32
Merge branch 'main' into vs_squashed
ruicraveiro Aug 26, 2024
99e604f
Merge branch 'main' into vs_squashed
ruicraveiro Aug 27, 2024
a816c5b
Merge branch 'main' into vs_squashed
ruicraveiro Aug 27, 2024
94fd363
Merge branch 'main' into vs_squashed
ruicraveiro Aug 28, 2024
76db0eb
Merge branch 'main' into vs_squashed
ruicraveiro Aug 29, 2024
3c4252b
Merge branch 'main' into vs_squashed
ruicraveiro Aug 30, 2024
c99c727
Added message when throwing UnimplementedError
ruicraveiro Aug 30, 2024
a77bea1
minor improvements to CameraController method comments
ruicraveiro Aug 30, 2024
5bb8379
Restructured video stabilization levels
ruicraveiro Sep 11, 2024
4f8b587
Merge branch 'main' into vs_squashed
ruicraveiro Sep 12, 2024
4fe99bb
Adjusted camera/camera unit tests for new VideoStabilizationMode enum…
ruicraveiro Sep 12, 2024
bf6c7d9
Merge branch 'main' into vs_squashed
ruicraveiro Sep 13, 2024
39bf897
Removed platform-specific comments
ruicraveiro Sep 13, 2024
91fd9bf
Merge branch 'main' into vs_squashed
ruicraveiro Sep 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/camera/camera/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy <sanekyy@gmail.com>
Anton Borries <mail@antonborri.es>
Alex Li <google@alexv525.com>
Rahul Raj <64.rahulraj@gmail.com>
Rui Craveiro <ruicraveiro@squarealfa.com>
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.12.0

* Adds support for video stabilization.

## 0.11.0+2

* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
Expand Down
9 changes: 5 additions & 4 deletions packages/camera/camera/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ dev_dependencies:
integration_test:
sdk: flutter

dependency_overrides:
camera_web:
path: ../../camera_web

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}}
1 change: 1 addition & 0 deletions packages/camera/camera/lib/camera.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export 'package:camera_platform_interface/camera_platform_interface.dart'
FocusMode,
ImageFormatGroup,
ResolutionPreset,
VideoStabilizationMode,
XFile;

export 'src/camera_controller.dart';
Expand Down
43 changes: 43 additions & 0 deletions packages/camera/camera/lib/src/camera_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -72,6 +73,7 @@ class CameraValue {
deviceOrientation: DeviceOrientation.portraitUp,
isPreviewPaused: false,
description: description,
videoStabilizationMode: VideoStabilizationMode.off,
);

/// True after [CameraController.initialize] has completed successfully.
Expand Down Expand Up @@ -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
Expand All @@ -171,6 +176,7 @@ class CameraValue {
bool? isPreviewPaused,
CameraDescription? description,
Optional<DeviceOrientation>? previewPauseOrientation,
VideoStabilizationMode? videoStabilizationMode,
}) {
return CameraValue(
isInitialized: isInitialized ?? this.isInitialized,
Expand Down Expand Up @@ -198,6 +204,8 @@ class CameraValue {
previewPauseOrientation: previewPauseOrientation == null
? this.previewPauseOrientation
: previewPauseOrientation.orNull,
videoStabilizationMode:
videoStabilizationMode ?? this.videoStabilizationMode,
);
}

Expand All @@ -219,6 +227,7 @@ class CameraValue {
'recordingOrientation: $recordingOrientation, '
'isPreviewPaused: $isPreviewPaused, '
'previewPausedOrientation: $previewPauseOrientation, '
'videoStabilizationMode: $videoStabilizationMode, '
'description: $description)';
}
}
Expand Down Expand Up @@ -689,6 +698,40 @@ class CameraController extends ValueNotifier<CameraValue> {
}
}

/// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think this is a bit clearer

Suggested change
/// Throws a [CameraException] when a not supported video stabilization
/// Throws a [CameraException] when an unsupported video stabilization

/// mode is supplied.
Future<void> 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will return an empty list on all other platforms.

This is no longer true. It throws an UnimplementedError on other platforms.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just fixed it. Thanks!

Future<Iterable<VideoStabilizationMode>>
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<void> setFlashMode(FlashMode mode) async {
try {
Expand Down
13 changes: 9 additions & 4 deletions packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
Dart.
repository: https://github.com/flutter/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+2
version: 0.12.0

environment:
sdk: ^3.3.0
Expand All @@ -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
Expand All @@ -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}}
7 changes: 7 additions & 0 deletions packages/camera/camera/test/camera_preview_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ class FakeController extends ValueNotifier<CameraValue>

@override
CameraDescription get description => value.description;

@override
Future<void> setVideoStabilizationMode(VideoStabilizationMode mode) async {}

@override
Future<Iterable<VideoStabilizationMode>>
getVideoStabilizationSupportedModes() async => <VideoStabilizationMode>[];
}

void main() {
Expand Down
144 changes: 144 additions & 0 deletions packages/camera/camera/test/camera_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 => <VideoStabilizationMode>[]);

// act
final Iterable<VideoStabilizationMode> modes =
await cameraController.getVideoStabilizationSupportedModes();

// assert
expect(modes, <VideoStabilizationMode>[]);
});

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>[VideoStabilizationMode.off]);

// act
final Iterable<VideoStabilizationMode> modes =
await cameraController.getVideoStabilizationSupportedModes();

// assert
expect(modes, <VideoStabilizationMode>[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>[
VideoStabilizationMode.off,
VideoStabilizationMode.on,
VideoStabilizationMode.standard,
VideoStabilizationMode.cinematic,
VideoStabilizationMode.cinematicExtended,
]);

// act
final Iterable<VideoStabilizationMode> modes =
await cameraController.getVideoStabilizationSupportedModes();

// assert
expect(modes, <VideoStabilizationMode>[
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<CameraException>().having(
(CameraException error) => error.description,
'TEST_ERROR',
'This is a test error message',
)));
});

test('pausePreview() calls $CameraPlatform', () async {
final CameraController cameraController = CameraController(
const CameraDescription(
Expand Down Expand Up @@ -1602,6 +1729,23 @@ class MockCameraPlatform extends Mock
Invocation.method(#setExposureOffset, <Object?>[cameraId, offset]),
returnValue: Future<double>.value(1.0),
) as Future<double>;

@override
Future<Iterable<VideoStabilizationMode>> getVideoStabilizationSupportedModes(
int cameraId) {
return super.noSuchMethod(
Invocation.method(
#getVideoStabilizationSupportedModes, <Object?>[cameraId]),
returnValue: Future<Iterable<VideoStabilizationMode>>.value(
<VideoStabilizationMode>[]),
) as Future<Iterable<VideoStabilizationMode>>;
}

@override
Future<void> setVideoStabilizationMode(
int cameraId, VideoStabilizationMode mode) async =>
super.noSuchMethod(Invocation.method(
#setVideoStabilizationMode, <Object?>[cameraId, mode]));
}

class MockCameraDescription extends CameraDescription {
Expand Down
8 changes: 7 additions & 1 deletion packages/camera/camera/test/camera_value_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ void main() {
focusPointSupported: true,
previewPauseOrientation: DeviceOrientation.portraitUp,
description: FakeController.fakeDescription,
videoStabilizationMode: VideoStabilizationMode.cinematic,
);

expect(cameraValue, isA<CameraValue>());
Expand All @@ -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', () {
Expand All @@ -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', () {
Expand All @@ -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', () {
Expand Down Expand Up @@ -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))');
});
});
}
1 change: 1 addition & 0 deletions packages/camera/camera_android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

## 0.10.9+9

* Updates camera_platform_interface lib to 2.9.0.
* Updates lint checks to ignore NewerVersionAvailable.

## 0.10.9+8
Expand Down
5 changes: 5 additions & 0 deletions packages/camera/camera_android/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Loading