Skip to content

Commit 8294234

Browse files
committed
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 to AVCaptureVideoStabilizationModeStandard on iOS. standard, /// Cinematic video stabilization is enabled. /// Maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION on Android /// and to AVCaptureVideoStabilizationModeCinematic on iOS. cinematic, /// Extended cinematic video stabilization is enabled. /// The same as cinematic on Android, so maps to CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION. /// Maps to AVCaptureVideoStabilizationModeCinematicExtended on iOS. cinematicExtended,
1 parent 2677981 commit 8294234

File tree

55 files changed

+1584
-30
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1584
-30
lines changed

packages/camera/camera/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,4 @@ Aleksandr Yurkovskiy <sanekyy@gmail.com>
6464
Anton Borries <mail@antonborri.es>
6565
Alex Li <google@alexv525.com>
6666
Rahul Raj <64.rahulraj@gmail.com>
67+
Rui Craveiro <ruicraveiro@squarealfa.com>

packages/camera/camera/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.12.0
2+
3+
* Adds support for video stabilization.
4+
15
## 0.11.0+1
26

37
* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.

packages/camera/camera/lib/camera.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export 'package:camera_platform_interface/camera_platform_interface.dart'
1212
FocusMode,
1313
ImageFormatGroup,
1414
ResolutionPreset,
15+
VideoStabilizationMode,
1516
XFile;
1617

1718
export 'src/camera_controller.dart';

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class CameraValue {
5454
this.recordingOrientation,
5555
this.isPreviewPaused = false,
5656
this.previewPauseOrientation,
57+
this.videoStabilizationMode = VideoStabilizationMode.off,
5758
}) : _isRecordingPaused = isRecordingPaused;
5859

5960
/// Creates a new camera controller state for an uninitialized controller.
@@ -72,6 +73,7 @@ class CameraValue {
7273
deviceOrientation: DeviceOrientation.portraitUp,
7374
isPreviewPaused: false,
7475
description: description,
76+
videoStabilizationMode: VideoStabilizationMode.off,
7577
);
7678

7779
/// True after [CameraController.initialize] has completed successfully.
@@ -148,6 +150,9 @@ class CameraValue {
148150
/// The properties of the camera device controlled by this controller.
149151
final CameraDescription description;
150152

153+
/// The video stabilization mode in
154+
final VideoStabilizationMode videoStabilizationMode;
155+
151156
/// Creates a modified copy of the object.
152157
///
153158
/// Explicitly specified fields get the specified value, all other fields get
@@ -171,6 +176,7 @@ class CameraValue {
171176
bool? isPreviewPaused,
172177
CameraDescription? description,
173178
Optional<DeviceOrientation>? previewPauseOrientation,
179+
VideoStabilizationMode? videoStabilizationMode,
174180
}) {
175181
return CameraValue(
176182
isInitialized: isInitialized ?? this.isInitialized,
@@ -198,6 +204,8 @@ class CameraValue {
198204
previewPauseOrientation: previewPauseOrientation == null
199205
? this.previewPauseOrientation
200206
: previewPauseOrientation.orNull,
207+
videoStabilizationMode:
208+
videoStabilizationMode ?? this.videoStabilizationMode,
201209
);
202210
}
203211

@@ -219,6 +227,7 @@ class CameraValue {
219227
'recordingOrientation: $recordingOrientation, '
220228
'isPreviewPaused: $isPreviewPaused, '
221229
'previewPausedOrientation: $previewPauseOrientation, '
230+
'videoStabilizationMode: $videoStabilizationMode, '
222231
'description: $description)';
223232
}
224233
}
@@ -689,6 +698,40 @@ class CameraController extends ValueNotifier<CameraValue> {
689698
}
690699
}
691700

701+
/// Set the video stabilization mode for the selected camera.
702+
///
703+
/// On Android (when using camera_android_camerax) and on iOS
704+
/// the supplied [mode] value should be a mode in the list returned
705+
/// by [getVideoStabilizationSupportedModes].
706+
///
707+
/// Throws a [CameraException] when a not supported video stabilization
708+
/// mode is supplied.
709+
Future<void> setVideoStabilizationMode(VideoStabilizationMode mode) async {
710+
_throwIfNotInitialized('setVideoStabilizationMode');
711+
try {
712+
await CameraPlatform.instance.setVideoStabilizationMode(_cameraId, mode);
713+
value = value.copyWith(videoStabilizationMode: mode);
714+
} on PlatformException catch (e) {
715+
throw CameraException(e.code, e.message);
716+
}
717+
}
718+
719+
/// Gets a list of video stabilization modes that are supported for the selected camera.
720+
///
721+
/// Will return the list of supported video stabilization modes
722+
/// on Android (when using camera_android_camerax package) and
723+
/// on iOS. Will return an empty list on all other platforms.
724+
Future<Iterable<VideoStabilizationMode>>
725+
getVideoStabilizationSupportedModes() {
726+
_throwIfNotInitialized('isVideoStabilizationModeSupported');
727+
try {
728+
return CameraPlatform.instance
729+
.getVideoStabilizationSupportedModes(_cameraId);
730+
} on PlatformException catch (e) {
731+
throw CameraException(e.code, e.message);
732+
}
733+
}
734+
692735
/// Sets the flash mode for taking pictures.
693736
Future<void> setFlashMode(FlashMode mode) async {
694737
try {

packages/camera/camera/pubspec.yaml

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

99
environment:
1010
sdk: ^3.2.3
@@ -21,9 +21,9 @@ flutter:
2121
default_package: camera_web
2222

2323
dependencies:
24-
camera_android_camerax: ^0.6.5
25-
camera_avfoundation: ^0.9.15
26-
camera_platform_interface: ^2.6.0
24+
camera_android_camerax: ^0.7.0
25+
camera_avfoundation: ^0.10.0
26+
camera_platform_interface: ^2.9.0
2727
camera_web: ^0.3.3
2828
flutter:
2929
sdk: flutter

packages/camera/camera/test/camera_preview_test.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ class FakeController extends ValueNotifier<CameraValue>
132132

133133
@override
134134
CameraDescription get description => value.description;
135+
136+
@override
137+
Future<void> setVideoStabilizationMode(VideoStabilizationMode mode) async {}
138+
139+
@override
140+
Future<Iterable<VideoStabilizationMode>>
141+
getVideoStabilizationSupportedModes() async => <VideoStabilizationMode>[];
135142
}
136143

137144
void main() {

packages/camera/camera/test/camera_test.dart

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,131 @@ void main() {
11901190
.called(4);
11911191
});
11921192

1193+
test('getVideoStabilizationSupportedModes() returns empty list', () async {
1194+
// arrange
1195+
final CameraController cameraController = CameraController(
1196+
const CameraDescription(
1197+
name: 'cam',
1198+
lensDirection: CameraLensDirection.back,
1199+
sensorOrientation: 90),
1200+
ResolutionPreset.max);
1201+
1202+
await cameraController.initialize();
1203+
when(CameraPlatform.instance
1204+
.getVideoStabilizationSupportedModes(mockInitializeCamera))
1205+
.thenAnswer((_) async => <VideoStabilizationMode>[]);
1206+
1207+
// act
1208+
final Iterable<VideoStabilizationMode> modes =
1209+
await cameraController.getVideoStabilizationSupportedModes();
1210+
1211+
// assert
1212+
expect(modes, <VideoStabilizationMode>[]);
1213+
});
1214+
1215+
test('getVideoStabilizationSupportedModes() returns off', () async {
1216+
// arrange
1217+
final CameraController cameraController = CameraController(
1218+
const CameraDescription(
1219+
name: 'cam',
1220+
lensDirection: CameraLensDirection.back,
1221+
sensorOrientation: 90),
1222+
ResolutionPreset.max);
1223+
1224+
await cameraController.initialize();
1225+
when(CameraPlatform.instance
1226+
.getVideoStabilizationSupportedModes(mockInitializeCamera))
1227+
.thenAnswer((_) async =>
1228+
<VideoStabilizationMode>[VideoStabilizationMode.off]);
1229+
1230+
// act
1231+
final Iterable<VideoStabilizationMode> modes =
1232+
await cameraController.getVideoStabilizationSupportedModes();
1233+
1234+
// assert
1235+
expect(modes, <VideoStabilizationMode>[VideoStabilizationMode.off]);
1236+
});
1237+
1238+
test('getVideoStabilizationSupportedModes() returns all modes', () async {
1239+
// arrange
1240+
final CameraController cameraController = CameraController(
1241+
const CameraDescription(
1242+
name: 'cam',
1243+
lensDirection: CameraLensDirection.back,
1244+
sensorOrientation: 90),
1245+
ResolutionPreset.max);
1246+
1247+
await cameraController.initialize();
1248+
when(CameraPlatform.instance
1249+
.getVideoStabilizationSupportedModes(mockInitializeCamera))
1250+
.thenAnswer((_) async => <VideoStabilizationMode>[
1251+
VideoStabilizationMode.off,
1252+
VideoStabilizationMode.standard,
1253+
VideoStabilizationMode.cinematic,
1254+
VideoStabilizationMode.cinematicExtended,
1255+
]);
1256+
1257+
// act
1258+
final Iterable<VideoStabilizationMode> modes =
1259+
await cameraController.getVideoStabilizationSupportedModes();
1260+
1261+
// assert
1262+
expect(modes, <VideoStabilizationMode>[
1263+
VideoStabilizationMode.off,
1264+
VideoStabilizationMode.standard,
1265+
VideoStabilizationMode.cinematic,
1266+
VideoStabilizationMode.cinematicExtended,
1267+
]);
1268+
});
1269+
1270+
test('setVideoStabilizationMode() calls $CameraPlatform', () async {
1271+
final CameraController cameraController = CameraController(
1272+
const CameraDescription(
1273+
name: 'cam',
1274+
lensDirection: CameraLensDirection.back,
1275+
sensorOrientation: 90),
1276+
ResolutionPreset.max);
1277+
await cameraController.initialize();
1278+
1279+
await cameraController
1280+
.setVideoStabilizationMode(VideoStabilizationMode.off);
1281+
1282+
verify(CameraPlatform.instance.setVideoStabilizationMode(
1283+
cameraController.cameraId, VideoStabilizationMode.off))
1284+
.called(1);
1285+
});
1286+
1287+
test(
1288+
'setVideoStabilizationMode() throws $CameraException on $PlatformException',
1289+
() async {
1290+
final CameraController cameraController = CameraController(
1291+
const CameraDescription(
1292+
name: 'cam',
1293+
lensDirection: CameraLensDirection.back,
1294+
sensorOrientation: 90),
1295+
ResolutionPreset.max);
1296+
await cameraController.initialize();
1297+
1298+
when(CameraPlatform.instance.setVideoStabilizationMode(
1299+
cameraController.cameraId,
1300+
VideoStabilizationMode.cinematicExtended))
1301+
.thenThrow(
1302+
PlatformException(
1303+
code: 'TEST_ERROR',
1304+
message: 'This is a test error message',
1305+
),
1306+
);
1307+
1308+
expect(
1309+
cameraController.setVideoStabilizationMode(
1310+
VideoStabilizationMode.cinematicExtended),
1311+
throwsA(isA<CameraException>().having(
1312+
(CameraException error) => error.description,
1313+
'TEST_ERROR',
1314+
'This is a test error message',
1315+
)));
1316+
});
1317+
11931318
test('pausePreview() calls $CameraPlatform', () async {
11941319
final CameraController cameraController = CameraController(
11951320
const CameraDescription(
@@ -1602,6 +1727,23 @@ class MockCameraPlatform extends Mock
16021727
Invocation.method(#setExposureOffset, <Object?>[cameraId, offset]),
16031728
returnValue: Future<double>.value(1.0),
16041729
) as Future<double>;
1730+
1731+
@override
1732+
Future<Iterable<VideoStabilizationMode>> getVideoStabilizationSupportedModes(
1733+
int cameraId) {
1734+
return super.noSuchMethod(
1735+
Invocation.method(
1736+
#getVideoStabilizationSupportedModes, <Object?>[cameraId]),
1737+
returnValue: Future<Iterable<VideoStabilizationMode>>.value(
1738+
<VideoStabilizationMode>[]),
1739+
) as Future<Iterable<VideoStabilizationMode>>;
1740+
}
1741+
1742+
@override
1743+
Future<void> setVideoStabilizationMode(
1744+
int cameraId, VideoStabilizationMode mode) async =>
1745+
super.noSuchMethod(Invocation.method(
1746+
#setVideoStabilizationMode, <Object?>[cameraId, mode]));
16051747
}
16061748

16071749
class MockCameraDescription extends CameraDescription {

packages/camera/camera/test/camera_value_test.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ void main() {
2828
focusPointSupported: true,
2929
previewPauseOrientation: DeviceOrientation.portraitUp,
3030
description: FakeController.fakeDescription,
31+
videoStabilizationMode: VideoStabilizationMode.cinematic,
3132
);
3233

3334
expect(cameraValue, isA<CameraValue>());
@@ -47,6 +48,8 @@ void main() {
4748
expect(cameraValue.recordingOrientation, DeviceOrientation.portraitUp);
4849
expect(cameraValue.isPreviewPaused, false);
4950
expect(cameraValue.previewPauseOrientation, DeviceOrientation.portraitUp);
51+
expect(
52+
cameraValue.videoStabilizationMode, VideoStabilizationMode.cinematic);
5053
});
5154

5255
test('Can be created as uninitialized', () {
@@ -70,6 +73,7 @@ void main() {
7073
expect(cameraValue.recordingOrientation, null);
7174
expect(cameraValue.isPreviewPaused, isFalse);
7275
expect(cameraValue.previewPauseOrientation, null);
76+
expect(cameraValue.videoStabilizationMode, VideoStabilizationMode.off);
7377
});
7478

7579
test('Can be copied with isInitialized', () {
@@ -94,6 +98,7 @@ void main() {
9498
expect(cameraValue.recordingOrientation, null);
9599
expect(cameraValue.isPreviewPaused, isFalse);
96100
expect(cameraValue.previewPauseOrientation, null);
101+
expect(cameraValue.videoStabilizationMode, VideoStabilizationMode.off);
97102
});
98103

99104
test('Has aspectRatio after setting size', () {
@@ -144,10 +149,11 @@ void main() {
144149
isPreviewPaused: true,
145150
previewPauseOrientation: DeviceOrientation.portraitUp,
146151
description: FakeController.fakeDescription,
152+
videoStabilizationMode: VideoStabilizationMode.cinematicExtended,
147153
);
148154

149155
expect(cameraValue.toString(),
150-
'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))');
156+
'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))');
151157
});
152158
});
153159
}

packages/camera/camera_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.10.9+9
2+
3+
* Updates camera_platform_interface lib to 2.9.0.
4+
15
## 0.10.9+8
26

37
* Removes unused code related to `maxVideoDuration`.

packages/camera/camera_android/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Android implementation of the camera plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
55

6-
version: 0.10.9+8
6+
version: 0.10.9+9
77

88
environment:
99
sdk: ^3.4.0
@@ -19,7 +19,7 @@ flutter:
1919
dartPluginClass: AndroidCamera
2020

2121
dependencies:
22-
camera_platform_interface: ^2.6.0
22+
camera_platform_interface: ^2.9.0
2323
flutter:
2424
sdk: flutter
2525
flutter_plugin_android_lifecycle: ^2.0.2

packages/camera/camera_android_camerax/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
# Name/Organization <email address>
55

66
Google Inc.
7+
Rui Craveiro <ruicraveiro@squarealfa.com>

packages/camera/camera_android_camerax/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.7.0
2+
3+
* Adds video stabilization.
4+
15
## 0.6.7+1
26

37
* Updates README to remove references to `maxVideoDuration`, as it was never

0 commit comments

Comments
 (0)