Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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.0+3
Comment thread
toyaji marked this conversation as resolved.
Outdated

* 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.0+3

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