Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.12.1

* Adds `CameraController.isZeroShutterLagSupported` and
`CameraController.setZeroShutterLagEnabled` for zero-shutter-lag still image
capture on platforms that support it.

## 0.12.0+2

* Fixes a crash where a `CameraController` could update its value after being disposed, throwing "A CameraController was used after being disposed".
Expand Down
4 changes: 4 additions & 0 deletions packages/camera/camera/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ 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: ../../../../packages/camera/camera_platform_interface}
29 changes: 29 additions & 0 deletions packages/camera/camera/lib/src/camera_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,35 @@ class CameraController extends ValueNotifier<CameraValue> {
}
}

/// Returns whether the selected camera supports zero-shutter-lag capture.
///
/// Zero-shutter-lag reduces the latency of [takePicture] by returning a
/// recently buffered frame instead of waiting for a new capture.
Future<bool> isZeroShutterLagSupported() async {
_throwIfNotInitialized('isZeroShutterLagSupported');
try {
return await CameraPlatform.instance.isZeroShutterLagSupported(_cameraId);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}

/// Enables or disables zero-shutter-lag capture for still image capture.
///
/// [isZeroShutterLagSupported] must be called first; only call this method
/// when it returns `true`.
///
/// When enabled, [takePicture] returns a recently buffered frame instead of
/// waiting for a new capture, reducing shutter latency.
Future<void> setZeroShutterLagEnabled(bool enabled) async {
_throwIfNotInitialized('setZeroShutterLagEnabled');
try {
await CameraPlatform.instance.setZeroShutterLagEnabled(_cameraId, enabled);
} 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
8 changes: 6 additions & 2 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.12.0+2
version: 0.12.1

environment:
sdk: ^3.10.0
Expand All @@ -23,7 +23,7 @@ flutter:
dependencies:
camera_android_camerax: ^0.7.0
camera_avfoundation: ^0.10.0
camera_platform_interface: ^2.12.0
camera_platform_interface: ^2.14.0
camera_web: ^0.3.3
flutter:
sdk: flutter
Expand All @@ -38,3 +38,7 @@ 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: ../../../packages/camera/camera_platform_interface}
6 changes: 6 additions & 0 deletions packages/camera/camera/test/camera_preview_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ class FakeController extends ValueNotifier<CameraValue> implements CameraControl
@override
Future<void> setZoomLevel(double zoom) async {}

@override
Future<bool> isZeroShutterLagSupported() async => false;

@override
Future<void> setZeroShutterLagEnabled(bool enabled) async {}

@override
Future<void> startImageStream(onLatestImageAvailable onAvailable) async {}

Expand Down
117 changes: 117 additions & 0 deletions packages/camera/camera/test/camera_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,110 @@ void main() {
verify(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)).called(1);
});

test('isZeroShutterLagSupported() returns the platform value.', () async {
final cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90,
),
ResolutionPreset.max,
);

await cameraController.initialize();
when(
CameraPlatform.instance.isZeroShutterLagSupported(mockInitializeCamera),
).thenAnswer((_) async => true);

expect(await cameraController.isZeroShutterLagSupported(), isTrue);
verify(CameraPlatform.instance.isZeroShutterLagSupported(mockInitializeCamera)).called(1);

reset(CameraPlatform.instance);
});

test('isZeroShutterLagSupported() throws $CameraException when uninitialized.', () async {
final cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90,
),
ResolutionPreset.max,
);

expect(
() => cameraController.isZeroShutterLagSupported(),
throwsA(
isA<CameraException>()
.having(
(CameraException error) => error.code,
'code',
'Uninitialized CameraController',
)
.having(
(CameraException error) => error.description,
'description',
'isZeroShutterLagSupported() was called on an uninitialized CameraController.',
),
),
);
});

test(
'setZeroShutterLagEnabled() completes and calls method channel with correct value.',
() async {
final cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90,
),
ResolutionPreset.max,
);

await cameraController.initialize();
await cameraController.setZeroShutterLagEnabled(true);

verify(
CameraPlatform.instance.setZeroShutterLagEnabled(mockInitializeCamera, true),
).called(1);
},
);

test(
'setZeroShutterLagEnabled() throws $CameraException when a platform exception occured.',
() async {
final cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90,
),
ResolutionPreset.max,
);

await cameraController.initialize();
when(
CameraPlatform.instance.setZeroShutterLagEnabled(mockInitializeCamera, true),
).thenThrow(CameraException('TEST_ERROR', 'This is a test error messge'));

expect(
() => cameraController.setZeroShutterLagEnabled(true),
throwsA(
isA<CameraException>()
.having((CameraException error) => error.code, 'code', 'TEST_ERROR')
.having(
(CameraException error) => error.description,
'description',
'This is a test error messge',
),
),
);

reset(CameraPlatform.instance);
},
);

test('setFlashMode() calls $CameraPlatform', () async {
final cameraController = CameraController(
const CameraDescription(
Expand Down Expand Up @@ -3646,6 +3750,19 @@ class MockCameraPlatform extends Mock with MockPlatformInterfaceMixin implements
@override
Future<void> setVideoStabilizationMode(int cameraId, VideoStabilizationMode mode) async =>
super.noSuchMethod(Invocation.method(#setVideoStabilizationMode, <Object?>[cameraId, mode]));

@override
Future<bool> isZeroShutterLagSupported(int cameraId) async =>
super.noSuchMethod(
Invocation.method(#isZeroShutterLagSupported, <Object?>[cameraId]),
returnValue: Future<bool>.value(false),
)
as Future<bool>;

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

class MockCameraDescription extends CameraDescription {
Expand Down
4 changes: 4 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,7 @@ 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: ../../../../packages/camera/camera_platform_interface}
4 changes: 4 additions & 0 deletions packages/camera/camera_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ 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: ../../../packages/camera/camera_platform_interface}
5 changes: 5 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.7.5

* Adds support for zero-shutter-lag still image capture via
`isZeroShutterLagSupported` and `setZeroShutterLagEnabled`.

## 0.7.4+7

* Updates pigeon dev_dependency to ^27.3.2 for analyzer 14 compatibility.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import androidx.camera.core.CameraInfo;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ExperimentalLensFacing;
import androidx.camera.core.ExperimentalZeroShutterLag;
import androidx.camera.core.ExposureState;

/**
Expand Down Expand Up @@ -61,4 +62,10 @@ public LiveDataProxyApi.LiveDataWrapper getZoomState(CameraInfo pigeonInstance)
return new LiveDataProxyApi.LiveDataWrapper(
pigeonInstance.getZoomState(), LiveDataSupportedType.ZOOM_STATE);
}

@Override
@OptIn(markerClass = ExperimentalZeroShutterLag.class)
public boolean isZslSupported(CameraInfo pigeonInstance) {
return pigeonInstance.isZslSupported();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2199,6 +2199,13 @@ abstract class PigeonApiCameraInfo(
pigeon_instance: androidx.camera.core.CameraInfo
): io.flutter.plugins.camerax.LiveDataProxyApi.LiveDataWrapper

/**
* Returns whether the camera supports zero-shutter-lag capture.
*
* See https://developer.android.com/reference/androidx/camera/core/CameraInfo#isZslSupported().
*/
abstract fun isZslSupported(pigeon_instance: androidx.camera.core.CameraInfo): Boolean

companion object {
@Suppress("LocalVariableName")
fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiCameraInfo?) {
Expand Down Expand Up @@ -2247,6 +2254,28 @@ abstract class PigeonApiCameraInfo(
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.camera_android_camerax.CameraInfo.isZslSupported",
codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val pigeon_instanceArg = args[0] as androidx.camera.core.CameraInfo
val wrapped: List<Any?> =
try {
listOf(api.isZslSupported(pigeon_instanceArg))
} catch (exception: Throwable) {
CameraXLibraryPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}

Expand Down Expand Up @@ -4248,7 +4277,8 @@ abstract class PigeonApiImageCapture(
resolutionSelector: androidx.camera.core.resolutionselector.ResolutionSelector?,
targetRotation: Long?,
flashMode: CameraXFlashMode?,
jpegQuality: Long?
jpegQuality: Long?,
zeroShutterLagEnabled: Boolean?
): androidx.camera.core.ImageCapture

abstract fun resolutionSelector(
Expand Down Expand Up @@ -4290,11 +4320,16 @@ abstract class PigeonApiImageCapture(
val targetRotationArg = args[2] as Long?
val flashModeArg = args[3] as CameraXFlashMode?
val jpegQualityArg = args[4] as Long?
val zeroShutterLagEnabledArg = args[5] as Boolean?
val wrapped: List<Any?> =
try {
api.pigeonRegistrar.instanceManager.addDartCreatedInstance(
api.pigeon_defaultConstructor(
resolutionSelectorArg, targetRotationArg, flashModeArg, jpegQualityArg),
resolutionSelectorArg,
targetRotationArg,
flashModeArg,
jpegQualityArg,
zeroShutterLagEnabledArg),
pigeon_identifierArg)
listOf(null)
} catch (exception: Throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.camera.core.ExperimentalZeroShutterLag;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.resolutionselector.ResolutionSelector;
Expand Down Expand Up @@ -37,15 +39,20 @@ public ProxyApiRegistrar getPigeonRegistrar() {

@NonNull
@Override
@OptIn(markerClass = ExperimentalZeroShutterLag.class)
public ImageCapture pigeon_defaultConstructor(
@Nullable ResolutionSelector resolutionSelector,
@Nullable Long targetRotation,
@Nullable CameraXFlashMode flashMode,
@Nullable Long jpegQuality) {
@Nullable Long jpegQuality,
@Nullable Boolean zeroShutterLagEnabled) {
final ImageCapture.Builder builder = new ImageCapture.Builder();
if (targetRotation != null) {
builder.setTargetRotation(targetRotation.intValue());
}
if (Boolean.TRUE.equals(zeroShutterLagEnabled)) {
builder.setCaptureMode(ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG);
}
if (flashMode != null) {
// This sets the requested flash mode, but may fail silently.
switch (flashMode) {
Expand Down
Loading