Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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 <[email protected]>
Anton Borries <[email protected]>
Alex Li <[email protected]>
Rahul Raj <[email protected]>
Rui Craveiro <[email protected]>
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7.
* Updates README to reflect that only Android API 24+ is supported.

## 0.12.0

* Adds support for video stabilization.

## 0.11.2

* Fixes overflowed toggles in the camera example.
Expand Down
5 changes: 5 additions & 0 deletions packages/camera/camera/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: {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 @@ -13,6 +13,7 @@ export 'package:camera_platform_interface/camera_platform_interface.dart'
FocusMode,
ImageFormatGroup,
ResolutionPreset,
VideoStabilizationMode,
XFile;

export 'src/camera_controller.dart';
Expand Down
114 changes: 114 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 @@ -201,6 +207,8 @@ class CameraValue {
previewPauseOrientation == null
? this.previewPauseOrientation
: previewPauseOrientation.orNull,
videoStabilizationMode:
videoStabilizationMode ?? this.videoStabilizationMode,
);
}

Expand All @@ -222,6 +230,7 @@ class CameraValue {
'recordingOrientation: $recordingOrientation, '
'isPreviewPaused: $isPreviewPaused, '
'previewPausedOrientation: $previewPauseOrientation, '
'videoStabilizationMode: $videoStabilizationMode, '
'description: $description)';
}
}
Expand Down Expand Up @@ -701,6 +710,111 @@ class CameraController extends ValueNotifier<CameraValue> {
}
}

/// Set the video stabilization mode for the selected camera.
///
/// When [allowFallback] is true (default) the camera will
/// be set to the best video stabilization mode up to,
/// and including, [mode].
///
/// When [allowFallback] is false and if
/// [mode] is not one of the supported modes
/// (see [getSupportedVideoStabilizationModes]),
/// then it throws an [ArgumentError].
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are these comments wrapped to such a short line length, instead of the 80 characters used by the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

///
/// This feature is only available on Android
/// (when using camera_android_camerax package)
/// and iOS. It is a no-op on all other platforms.
Copy link
Collaborator

Choose a reason for hiding this comment

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

"This feature is only available if [getSupportedVideoStabilizationModes] returns at least one value other than [VideoStabilizationMode.off]."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Future<void> setVideoStabilizationMode(
VideoStabilizationMode mode, {
bool allowFallback = true,
}) async {
_throwIfNotInitialized('setVideoStabilizationMode');
try {
final VideoStabilizationMode? modeToSet =
await _getVideoStabilizationModeToSet(mode, allowFallback);

// When _getVideoStabilizationModeToSet returns null
// it means that the device doesn't support any
// video stabilization mode and that doing nothing
// is valid because allowFallback is true or [mode]
// is [VideoStabilizationMode.off], so this results
// in a no-op.
if (modeToSet == null) {
return;
}
await CameraPlatform.instance.setVideoStabilizationMode(
_cameraId,
modeToSet,
);
value = value.copyWith(videoStabilizationMode: modeToSet);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}

Future<VideoStabilizationMode?> _getVideoStabilizationModeToSet(
VideoStabilizationMode requestedMode,
bool allowFallback,
) async {
final Iterable<VideoStabilizationMode> supportedModes = await CameraPlatform
.instance
.getSupportedVideoStabilizationModes(_cameraId);

// In this case the device doesn't report any
// available stabilization mode available and
// if either it can fallback or if the requested mode
// is off, then this returns null to signal that
// there is nothing to be done.
if (supportedModes.isEmpty &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

isEmpty will never be true.

Copy link
Contributor Author

@ruicraveiro ruicraveiro Oct 10, 2025

Choose a reason for hiding this comment

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

Yeah, that's a leftover of the previous logic.

I added the guarantee that supported modes is never empty at the camera package level, not at the platform level. In other words, CameraController.getSupportedVideoStabilizationModes will never return an empty list, but CameraPlatform.instance.getSupportedVideoStabilizationModes() may and that is the method that is being called by _getVideoStabilizationModeToSet to get the actual modes available from the device . This is actually needed by CameraController.setVideoStabilizationMode() so that it knows whether it's supposed to fallback to a no-op in case the device doesn't support video stabilization at all (not even off) or if the fallback is to turn off.

This is the bit of code inside CameraController.getSupportedVideoStabilizationModes that guarantees that off is always included:

      final Set<VideoStabilizationMode> modes = <VideoStabilizationMode>{
        VideoStabilizationMode.off,
        ...await CameraPlatform.instance.getSupportedVideoStabilizationModes(
          _cameraId,
        ),
      };

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Anyway, I ended changing this code as well while changing _getVideoStabilizationModeToSet() to use the new getFallbackVideoStabilizationMode() added to CameraPlatform. The only bit of code that I am not 100% sure of, but I think is in accordance of what we agreed is the following:

    // If it can't fallback and the specific
    // requested mode isn't available, then...
    if (!allowFallback && !supportedModes.contains(requestedMode)) {
      // if the request is off, it is a no-op
      if (requestedMode == VideoStabilizationMode.off) {
        return null;
      }
      // otherwise, it throws.
      throw ArgumentError('Unavailable video stabilization mode.', 'mode');
    }

So, basically here I am handling the case where the request is for off. In this case, even though if allowFallback is false, we're handling off as a special situation in which it is still fallback-ish in the sense that if the device doesn't even support that, it is to be handled as a no-op. This is in the spirit that setVideoStabilizationMode will always accept a mode returned by getSupportedVideoStabilizationModes() without throwing, even when allowFallback=false, and that getSupportedVideoStabilizationModes() will always return off, even when the device doesn't support any mode at all, which means that getSupportedVideoStabilizationModes() must handle off as a valid request even with allowFallback=false and the way it handles off when it is not supported is by doing nothing.

(allowFallback || requestedMode == VideoStabilizationMode.off)) {
return null;
}

// If it can't fallback and the specific
// requested mode isn't available, then it throws.
if (!allowFallback && !supportedModes.contains(requestedMode)) {
throw ArgumentError('Unavailable video stabilization mode.', 'mode');
}

// The following assumes that [VideoStabilizationMode.off] will
// always be present if any other level is reported by the device.
// It iterates through all the modes returned by the device,
// looking for the highest mode, up to [mode].
VideoStabilizationMode requestMode = VideoStabilizationMode.off;
for (final VideoStabilizationMode supportedMode in supportedModes) {
if (supportedMode.index <= requestedMode.index &&
supportedMode.index >= requestMode.index) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If a new orthogonal mode were added to the platform interface in the future, which wouldn't be a breaking change for the platform interface package, this code would suddenly become wrong for anyone getting the latest version of all packages (up until the app-facing package were updated).

I think the way to implement this would be a public method in the platform interface package that has a switch mapping each mode to its next best fallback mode, and then, starting from the requested mode, call that helper function until it returns something that's in supportedModes.

That way, setting the fallback logic for a new mode happens in the same package as adding the new mode, so it all works at runtime regardless of version combinations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. I just pushed a new commit with this and I actually like it better.

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
/// on Android (when using camera_android_camerax package) and
/// on iOS.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This paragraph should be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

///
/// [VideoStabilizationMode.off] will always be listed.
Future<Iterable<VideoStabilizationMode>>
getSupportedVideoStabilizationModes() async {
_throwIfNotInitialized('isVideoStabilizationModeSupported');
try {
final Set<VideoStabilizationMode> modes = <VideoStabilizationMode>{
VideoStabilizationMode.off,
...await CameraPlatform.instance.getSupportedVideoStabilizationModes(
_cameraId,
),
};
return modes;
} 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.2
version: 0.12.0

environment:
sdk: ^3.7.0
Expand All @@ -21,9 +21,9 @@ flutter:
default_package: camera_web

dependencies:
camera_android_camerax: ^0.6.13
camera_avfoundation: ^0.9.18
camera_platform_interface: ^2.10.0
camera_android_camerax: ^0.7.0
camera_avfoundation: ^0.10.0
camera_platform_interface: ^2.12.0
camera_web: ^0.3.3
flutter:
sdk: flutter
Expand All @@ -38,3 +38,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}}
10 changes: 10 additions & 0 deletions packages/camera/camera/test/camera_preview_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ class FakeController extends ValueNotifier<CameraValue>
@override
CameraDescription get description => value.description;

@override
Future<void> setVideoStabilizationMode(
VideoStabilizationMode mode, {
bool allowFallback = true,
}) async {}

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

@override
bool supportsImageStreaming() => true;
}
Expand Down
Loading