@@ -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 current video stabilization mode.
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}
@@ -720,6 +729,94 @@ class CameraController extends ValueNotifier<CameraValue> {
720729 }
721730 }
722731
732+ /// Set the video stabilization mode for the selected camera.
733+ ///
734+ /// When [allowFallback] is true (default) the camera will be set to the best
735+ /// video stabilization mode up to, and including, [mode] .
736+ ///
737+ /// When [allowFallback] is false and if [mode] is not one of the supported
738+ /// modes (see [getSupportedVideoStabilizationModes] ), then it throws an
739+ /// [ArgumentError] .
740+ ///
741+ /// This feature is only available if [getSupportedVideoStabilizationModes]
742+ /// returns at least one value other than [VideoStabilizationMode.off] .
743+ Future <void > setVideoStabilizationMode (
744+ VideoStabilizationMode mode, {
745+ bool allowFallback = true ,
746+ }) async {
747+ _throwIfNotInitialized ('setVideoStabilizationMode' );
748+ try {
749+ final VideoStabilizationMode ? modeToSet =
750+ await _getVideoStabilizationModeToSet (mode, allowFallback);
751+
752+ // When _getVideoStabilizationModeToSet returns null
753+ // it means that the device doesn't support any
754+ // video stabilization mode and that doing nothing
755+ // is valid because allowFallback is true or [mode]
756+ // is [VideoStabilizationMode.off], so this results
757+ // in a no-op.
758+ if (modeToSet == null ) {
759+ return ;
760+ }
761+ await CameraPlatform .instance.setVideoStabilizationMode (
762+ _cameraId,
763+ modeToSet,
764+ );
765+ value = value.copyWith (videoStabilizationMode: modeToSet);
766+ } on PlatformException catch (e) {
767+ throw CameraException (e.code, e.message);
768+ }
769+ }
770+
771+ Future <VideoStabilizationMode ?> _getVideoStabilizationModeToSet (
772+ VideoStabilizationMode requestedMode,
773+ bool allowFallback,
774+ ) async {
775+ final Iterable <VideoStabilizationMode > supportedModes = await CameraPlatform
776+ .instance
777+ .getSupportedVideoStabilizationModes (_cameraId);
778+
779+ // If it can't fallback and the specific
780+ // requested mode isn't available, then...
781+ if (! allowFallback && ! supportedModes.contains (requestedMode)) {
782+ // if the request is off, it is a no-op
783+ if (requestedMode == VideoStabilizationMode .off) {
784+ return null ;
785+ }
786+ // otherwise, it throws.
787+ throw ArgumentError ('Unavailable video stabilization mode.' , 'mode' );
788+ }
789+
790+ VideoStabilizationMode ? fallbackMode = requestedMode;
791+ while (fallbackMode != null && ! supportedModes.contains (fallbackMode)) {
792+ fallbackMode = CameraPlatform .getFallbackVideoStabilizationMode (
793+ fallbackMode,
794+ );
795+ }
796+
797+ return fallbackMode;
798+ }
799+
800+ /// Gets a list of video stabilization modes that are supported
801+ /// for the selected camera.
802+ ///
803+ /// [VideoStabilizationMode.off] will always be listed.
804+ Future <Iterable <VideoStabilizationMode >>
805+ getSupportedVideoStabilizationModes () async {
806+ _throwIfNotInitialized ('getSupportedVideoStabilizationModes' );
807+ try {
808+ final modes = < VideoStabilizationMode > {
809+ VideoStabilizationMode .off,
810+ ...await CameraPlatform .instance.getSupportedVideoStabilizationModes (
811+ _cameraId,
812+ ),
813+ };
814+ return modes;
815+ } on PlatformException catch (e) {
816+ throw CameraException (e.code, e.message);
817+ }
818+ }
819+
723820 /// Sets the flash mode for taking pictures.
724821 Future <void > setFlashMode (FlashMode mode) async {
725822 try {
0 commit comments