[camera] Add Android & iOS implementations for pausing the camera preview (#4258)
diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md
index 6d38fa2..bb00480 100644
--- a/packages/camera/camera/CHANGELOG.md
+++ b/packages/camera/camera/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.9.2
+
+* Added functions to pause and resume the camera preview.
+
## 0.9.1+1
* Replace `device_info` reference with `device_info_plus` in the [README.md](README.md)
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
index 43479ac..c036c1c 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java
@@ -126,6 +126,8 @@
private MediaRecorder mediaRecorder;
/** True when recording video. */
private boolean recordingVideo;
+ /** True when the preview is paused. */
+ private boolean pausedPreview;
private File captureFile;
@@ -428,8 +430,10 @@
}
try {
- captureSession.setRepeatingRequest(
- previewRequestBuilder.build(), cameraCaptureCallback, backgroundHandler);
+ if (!pausedPreview) {
+ captureSession.setRepeatingRequest(
+ previewRequestBuilder.build(), cameraCaptureCallback, backgroundHandler);
+ }
if (onSuccessCallback != null) {
onSuccessCallback.run();
@@ -834,33 +838,36 @@
* For focus mode an extra step of actually locking/unlocking the
* focus has to be done, in order to ensure it goes into the correct state.
*/
- switch (newMode) {
- case locked:
- // Perform a single focus trigger.
- lockAutoFocus();
- if (captureSession == null) {
- Log.i(TAG, "[unlockAutoFocus] captureSession null, returning");
- return;
- }
-
- // Set AF state to idle again.
- previewRequestBuilder.set(
- CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
-
- try {
- captureSession.setRepeatingRequest(
- previewRequestBuilder.build(), null, backgroundHandler);
- } catch (CameraAccessException e) {
- if (result != null) {
- result.error("setFocusModeFailed", "Error setting focus mode: " + e.getMessage(), null);
+ if (!pausedPreview) {
+ switch (newMode) {
+ case locked:
+ // Perform a single focus trigger.
+ if (captureSession == null) {
+ Log.i(TAG, "[unlockAutoFocus] captureSession null, returning");
+ return;
}
- return;
- }
- break;
- case auto:
- // Cancel current AF trigger and set AF to idle again.
- unlockAutoFocus();
- break;
+ lockAutoFocus();
+
+ // Set AF state to idle again.
+ previewRequestBuilder.set(
+ CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+
+ try {
+ captureSession.setRepeatingRequest(
+ previewRequestBuilder.build(), null, backgroundHandler);
+ } catch (CameraAccessException e) {
+ if (result != null) {
+ result.error(
+ "setFocusModeFailed", "Error setting focus mode: " + e.getMessage(), null);
+ }
+ return;
+ }
+ break;
+ case auto:
+ // Cancel current AF trigger and set AF to idle again.
+ unlockAutoFocus();
+ break;
+ }
}
if (result != null) {
@@ -966,6 +973,19 @@
cameraFeatures.getSensorOrientation().unlockCaptureOrientation();
}
+ /** Pause the preview from dart. */
+ public void pausePreview() throws CameraAccessException {
+ this.pausedPreview = true;
+ this.captureSession.stopRepeating();
+ }
+
+ /** Resume the preview from dart. */
+ public void resumePreview() {
+ this.pausedPreview = false;
+ this.refreshPreviewCaptureSession(
+ null, (code, message) -> dartMessenger.sendCameraErrorEvent(message));
+ }
+
public void startPreview() throws CameraAccessException {
if (pictureImageReader == null || pictureImageReader.getSurface() == null) return;
Log.i(TAG, "startPreview");
@@ -1022,8 +1042,8 @@
private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) {
imageStreamReader.setOnImageAvailableListener(
reader -> {
- // Use acquireNextImage since image reader is only for one image.
Image img = reader.acquireNextImage();
+ // Use acquireNextImage since image reader is only for one image.
if (img == null) return;
List<Map<String, Object>> planes = new ArrayList<>();
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java
index 893785f..5e25353 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java
@@ -339,6 +339,22 @@
}
break;
}
+ case "pausePreview":
+ {
+ try {
+ camera.pausePreview();
+ result.success(null);
+ } catch (Exception e) {
+ handleException(e, result);
+ }
+ break;
+ }
+ case "resumePreview":
+ {
+ camera.resumePreview();
+ result.success(null);
+ break;
+ }
case "dispose":
{
if (camera != null) {
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java
index cab2ae8..5431df0 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/CameraTest.java
@@ -744,6 +744,33 @@
verify(mockSensorOrientationFeature, times(1)).unlockCaptureOrientation();
}
+ @Test
+ public void pausePreview_shouldPausePreview() throws CameraAccessException {
+ camera.pausePreview();
+
+ assertEquals(TestUtils.getPrivateField(camera, "pausedPreview"), true);
+ verify(mockCaptureSession, times(1)).stopRepeating();
+ }
+
+ @Test
+ public void resumePreview_shouldResumePreview() throws CameraAccessException {
+ camera.resumePreview();
+
+ assertEquals(TestUtils.getPrivateField(camera, "pausedPreview"), false);
+ verify(mockCaptureSession, times(1)).setRepeatingRequest(any(), any(), any());
+ }
+
+ @Test
+ public void resumePreview_shouldSendErrorEventOnCameraAccessException()
+ throws CameraAccessException {
+ when(mockCaptureSession.setRepeatingRequest(any(), any(), any()))
+ .thenThrow(new CameraAccessException(0));
+
+ camera.resumePreview();
+
+ verify(mockDartMessenger, times(1)).sendCameraErrorEvent(any());
+ }
+
private static class TestCameraFeatureFactory implements CameraFeatureFactory {
private final AutoFocusFeature mockAutoFocusFeature;
private final ExposureLockFeature mockExposureLockFeature;
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/MethodCallHandlerImplTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/MethodCallHandlerImplTest.java
new file mode 100644
index 0000000..35eed7a
--- /dev/null
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/MethodCallHandlerImplTest.java
@@ -0,0 +1,69 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.camera;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.hardware.camera2.CameraAccessException;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugins.camera.utils.TestUtils;
+import io.flutter.view.TextureRegistry;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MethodCallHandlerImplTest {
+
+ MethodChannel.MethodCallHandler handler;
+ MethodChannel.Result mockResult;
+ Camera mockCamera;
+
+ @Before
+ public void setUp() {
+ handler =
+ new MethodCallHandlerImpl(
+ mock(Activity.class),
+ mock(BinaryMessenger.class),
+ mock(CameraPermissions.class),
+ mock(CameraPermissions.PermissionsRegistry.class),
+ mock(TextureRegistry.class),
+ null);
+ mockResult = mock(MethodChannel.Result.class);
+ mockCamera = mock(Camera.class);
+ TestUtils.setPrivateField(handler, "camera", mockCamera);
+ }
+
+ @Test
+ public void onMethodCall_pausePreview_shouldPausePreviewAndSendSuccessResult()
+ throws CameraAccessException {
+ handler.onMethodCall(new MethodCall("pausePreview", null), mockResult);
+
+ verify(mockCamera, times(1)).pausePreview();
+ verify(mockResult, times(1)).success(null);
+ }
+
+ @Test
+ public void onMethodCall_pausePreview_shouldSendErrorResultOnCameraAccessException()
+ throws CameraAccessException {
+ doThrow(new CameraAccessException(0)).when(mockCamera).pausePreview();
+
+ handler.onMethodCall(new MethodCall("pausePreview", null), mockResult);
+
+ verify(mockResult, times(1)).error("CameraAccess", null, null);
+ }
+
+ @Test
+ public void onMethodCall_resumePreview_shouldResumePreviewAndSendSuccessResult() {
+ handler.onMethodCall(new MethodCall("resumePreview", null), mockResult);
+
+ verify(mockCamera, times(1)).resumePreview();
+ verify(mockResult, times(1)).success(null);
+ }
+}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java
index dbf9d11..fce99b5 100644
--- a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java
@@ -33,4 +33,15 @@
Assert.fail("Unable to mock private field: " + fieldName);
}
}
+
+ public static <T> Object getPrivateField(T instance, String fieldName) {
+ try {
+ Field field = instance.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return field.get(instance);
+ } catch (Exception e) {
+ Assert.fail("Unable to mock private field: " + fieldName);
+ return null;
+ }
+ }
}
diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj
index aead167..5a622f1 100644
--- a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj
@@ -18,6 +18,7 @@
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
A513685080F868CF2695CE75 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5555DD51E06E67921CFA83DD /* libPods-RunnerTests.a */; };
D065CD815D405ECB22FB1BBA /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A4F2DE74AE0C572296A00BF /* libPods-Runner.a */; };
+ E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -68,6 +69,7 @@
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A4725B4F24805CD3CA67828F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
D1FF8C34CA9E9BE702C5EC06 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
+ E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPreviewPauseTests.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -96,6 +98,7 @@
03BB766A2665316900CE5A93 /* CameraFocusTests.m */,
03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */,
03BB766C2665316900CE5A93 /* Info.plist */,
+ E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */,
);
path = RunnerTests;
sourceTree = "<group>";
@@ -359,6 +362,7 @@
buildActionMask = 2147483647;
files = (
03BB766B2665316900CE5A93 /* CameraFocusTests.m in Sources */,
+ E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */,
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/packages/camera/camera/example/ios/RunnerTests/CameraPreviewPauseTests.m b/packages/camera/camera/example/ios/RunnerTests/CameraPreviewPauseTests.m
new file mode 100644
index 0000000..549b40a
--- /dev/null
+++ b/packages/camera/camera/example/ios/RunnerTests/CameraPreviewPauseTests.m
@@ -0,0 +1,50 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@import camera;
+@import XCTest;
+@import AVFoundation;
+#import <OCMock/OCMock.h>
+
+@interface FLTCam : NSObject <FlutterTexture,
+ AVCaptureVideoDataOutputSampleBufferDelegate,
+ AVCaptureAudioDataOutputSampleBufferDelegate>
+@property(assign, nonatomic) BOOL isPreviewPaused;
+- (void)pausePreviewWithResult:(FlutterResult)result;
+- (void)resumePreviewWithResult:(FlutterResult)result;
+@end
+
+@interface CameraPreviewPauseTests : XCTestCase
+@property(readonly, nonatomic) FLTCam* camera;
+@end
+
+@implementation CameraPreviewPauseTests
+
+- (void)setUp {
+ _camera = [[FLTCam alloc] init];
+}
+
+- (void)testPausePreviewWithResult_shouldPausePreview {
+ XCTestExpectation* resultExpectation =
+ [self expectationWithDescription:@"Succeeding result with nil value"];
+ [_camera pausePreviewWithResult:^void(id _Nullable result) {
+ XCTAssertNil(result);
+ [resultExpectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:2.0 handler:nil];
+ XCTAssertTrue(_camera.isPreviewPaused);
+}
+
+- (void)testResumePreviewWithResult_shouldResumePreview {
+ XCTestExpectation* resultExpectation =
+ [self expectationWithDescription:@"Succeeding result with nil value"];
+ [_camera resumePreviewWithResult:^void(id _Nullable result) {
+ XCTAssertNil(result);
+ [resultExpectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:2.0 handler:nil];
+ XCTAssertFalse(_camera.isPreviewPaused);
+}
+
+@end
diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart
index 2314aec..364f59d 100644
--- a/packages/camera/camera/example/lib/main.dart
+++ b/packages/camera/camera/example/lib/main.dart
@@ -530,7 +530,16 @@
cameraController.value.isRecordingVideo
? onStopButtonPressed
: null,
- )
+ ),
+ IconButton(
+ icon: const Icon(Icons.pause_presentation),
+ color:
+ cameraController != null && cameraController.value.isPreviewPaused
+ ? Colors.red
+ : Colors.blue,
+ onPressed:
+ cameraController == null ? null : onPausePreviewButtonPressed,
+ ),
],
);
}
@@ -747,6 +756,23 @@
});
}
+ Future<void> onPausePreviewButtonPressed() async {
+ final CameraController? cameraController = controller;
+
+ if (cameraController == null || !cameraController.value.isInitialized) {
+ showInSnackBar('Error: select a camera first.');
+ return;
+ }
+
+ if (cameraController.value.isPreviewPaused) {
+ await cameraController.resumePreview();
+ } else {
+ await cameraController.pausePreview();
+ }
+
+ if (mounted) setState(() {});
+ }
+
void onPauseButtonPressed() {
pauseVideoRecording().then((_) {
if (mounted) setState(() {});
diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m
index ea03ce5..cb93e9f 100644
--- a/packages/camera/camera/ios/Classes/CameraPlugin.m
+++ b/packages/camera/camera/ios/Classes/CameraPlugin.m
@@ -330,6 +330,7 @@
@property(assign, nonatomic) BOOL audioIsDisconnected;
@property(assign, nonatomic) BOOL isAudioSetup;
@property(assign, nonatomic) BOOL isStreamingImages;
+@property(assign, nonatomic) BOOL isPreviewPaused;
@property(assign, nonatomic) ResolutionPreset resolutionPreset;
@property(assign, nonatomic) ExposureMode exposureMode;
@property(assign, nonatomic) FocusMode focusMode;
@@ -1035,6 +1036,16 @@
[captureDevice unlockForConfiguration];
}
+- (void)pausePreviewWithResult:(FlutterResult)result {
+ _isPreviewPaused = true;
+ result(nil);
+}
+
+- (void)resumePreviewWithResult:(FlutterResult)result {
+ _isPreviewPaused = false;
+ result(nil);
+}
+
- (CGPoint)getCGPointForCoordsWithOrientation:(UIDeviceOrientation)orientation
x:(double)x
y:(double)y {
@@ -1432,7 +1443,9 @@
__weak CameraPlugin *weakSelf = self;
_camera.onFrameAvailable = ^{
- [weakSelf.registry textureFrameAvailable:cameraId];
+ if (![weakSelf.camera isPreviewPaused]) {
+ [weakSelf.registry textureFrameAvailable:cameraId];
+ }
};
FlutterMethodChannel *methodChannel = [FlutterMethodChannel
methodChannelWithName:[NSString stringWithFormat:@"flutter.io/cameraPlugin/camera%lu",
@@ -1519,6 +1532,10 @@
y = ((NSNumber *)call.arguments[@"y"]).doubleValue;
}
[_camera setFocusPointWithResult:result x:x y:y];
+ } else if ([@"pausePreview" isEqualToString:call.method]) {
+ [_camera pausePreviewWithResult:result];
+ } else if ([@"resumePreview" isEqualToString:call.method]) {
+ [_camera resumePreviewWithResult:result];
} else {
result(FlutterMethodNotImplemented);
}
diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart
index 37869fe..58193bd 100644
--- a/packages/camera/camera/lib/src/camera_controller.dart
+++ b/packages/camera/camera/lib/src/camera_controller.dart
@@ -47,6 +47,8 @@
required this.deviceOrientation,
this.lockedCaptureOrientation,
this.recordingOrientation,
+ this.isPreviewPaused = false,
+ this.previewPauseOrientation,
}) : _isRecordingPaused = isRecordingPaused;
/// Creates a new camera controller state for an uninitialized controller.
@@ -63,6 +65,7 @@
focusMode: FocusMode.auto,
focusPointSupported: false,
deviceOrientation: DeviceOrientation.portraitUp,
+ isPreviewPaused: false,
);
/// True after [CameraController.initialize] has completed successfully.
@@ -79,6 +82,12 @@
final bool _isRecordingPaused;
+ /// True when the preview widget has been paused manually.
+ final bool isPreviewPaused;
+
+ /// Set to the orientation the preview was paused in, if it is currently paused.
+ final DeviceOrientation? previewPauseOrientation;
+
/// True when camera [isRecordingVideo] and recording is paused.
bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused;
@@ -150,6 +159,8 @@
DeviceOrientation? deviceOrientation,
Optional<DeviceOrientation>? lockedCaptureOrientation,
Optional<DeviceOrientation>? recordingOrientation,
+ bool? isPreviewPaused,
+ Optional<DeviceOrientation>? previewPauseOrientation,
}) {
return CameraValue(
isInitialized: isInitialized ?? this.isInitialized,
@@ -172,6 +183,10 @@
recordingOrientation: recordingOrientation == null
? this.recordingOrientation
: recordingOrientation.orNull,
+ isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused,
+ previewPauseOrientation: previewPauseOrientation == null
+ ? this.previewPauseOrientation
+ : previewPauseOrientation.orNull,
);
}
@@ -190,7 +205,9 @@
'focusPointSupported: $focusPointSupported, '
'deviceOrientation: $deviceOrientation, '
'lockedCaptureOrientation: $lockedCaptureOrientation, '
- 'recordingOrientation: $recordingOrientation)';
+ 'recordingOrientation: $recordingOrientation, '
+ 'isPreviewPaused: $isPreviewPaused, '
+ 'previewPausedOrientation: $previewPauseOrientation)';
}
}
@@ -325,6 +342,35 @@
await CameraPlatform.instance.prepareForVideoRecording();
}
+ /// Pauses the current camera preview
+ Future<void> pausePreview() async {
+ if (value.isPreviewPaused) {
+ return;
+ }
+ try {
+ await CameraPlatform.instance.pausePreview(_cameraId);
+ value = value.copyWith(
+ isPreviewPaused: true,
+ previewPauseOrientation: Optional.of(this.value.deviceOrientation));
+ } on PlatformException catch (e) {
+ throw CameraException(e.code, e.message);
+ }
+ }
+
+ /// Resumes the current camera preview
+ Future<void> resumePreview() async {
+ if (!value.isPreviewPaused) {
+ return;
+ }
+ try {
+ await CameraPlatform.instance.resumePreview(_cameraId);
+ value = value.copyWith(
+ isPreviewPaused: false, previewPauseOrientation: Optional.absent());
+ } on PlatformException catch (e) {
+ throw CameraException(e.code, e.message);
+ }
+ }
+
/// Captures an image and returns the file where it was saved.
///
/// Throws a [CameraException] if the capture fails.
diff --git a/packages/camera/camera/lib/src/camera_preview.dart b/packages/camera/camera/lib/src/camera_preview.dart
index 1df9f8e..6a15896 100644
--- a/packages/camera/camera/lib/src/camera_preview.dart
+++ b/packages/camera/camera/lib/src/camera_preview.dart
@@ -71,7 +71,8 @@
DeviceOrientation _getApplicableOrientation() {
return controller.value.isRecordingVideo
? controller.value.recordingOrientation!
- : (controller.value.lockedCaptureOrientation ??
+ : (controller.value.previewPauseOrientation ??
+ controller.value.lockedCaptureOrientation ??
controller.value.deviceOrientation);
}
}
diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml
index 1009191..3e3fad1 100644
--- a/packages/camera/camera/pubspec.yaml
+++ b/packages/camera/camera/pubspec.yaml
@@ -4,7 +4,7 @@
and streaming image buffers to dart.
repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.9.1+1
+version: 0.9.2
environment:
sdk: ">=2.12.0 <3.0.0"
@@ -20,7 +20,7 @@
pluginClass: CameraPlugin
dependencies:
- camera_platform_interface: ^2.0.0
+ camera_platform_interface: ^2.1.0
flutter:
sdk: flutter
pedantic: ^1.10.0
diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart
index 8275461..14afdda 100644
--- a/packages/camera/camera/test/camera_preview_test.dart
+++ b/packages/camera/camera/test/camera_preview_test.dart
@@ -113,6 +113,12 @@
@override
Future<void> unlockCaptureOrientation() async {}
+
+ @override
+ Future<void> pausePreview() async {}
+
+ @override
+ Future<void> resumePreview() async {}
}
void main() {
diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart
index 26382a9..6904e68 100644
--- a/packages/camera/camera/test/camera_test.dart
+++ b/packages/camera/camera/test/camera_test.dart
@@ -1137,6 +1137,138 @@
.called(4);
});
+ test('pausePreview() calls $CameraPlatform', () async {
+ CameraController cameraController = CameraController(
+ CameraDescription(
+ name: 'cam',
+ lensDirection: CameraLensDirection.back,
+ sensorOrientation: 90),
+ ResolutionPreset.max);
+ await cameraController.initialize();
+ cameraController.value = cameraController.value
+ .copyWith(deviceOrientation: DeviceOrientation.portraitUp);
+
+ await cameraController.pausePreview();
+
+ verify(CameraPlatform.instance.pausePreview(cameraController.cameraId))
+ .called(1);
+ expect(cameraController.value.isPreviewPaused, equals(true));
+ expect(cameraController.value.previewPauseOrientation,
+ DeviceOrientation.portraitUp);
+ });
+
+ test('pausePreview() does not call $CameraPlatform when already paused',
+ () async {
+ CameraController cameraController = CameraController(
+ CameraDescription(
+ name: 'cam',
+ lensDirection: CameraLensDirection.back,
+ sensorOrientation: 90),
+ ResolutionPreset.max);
+ await cameraController.initialize();
+ cameraController.value =
+ cameraController.value.copyWith(isPreviewPaused: true);
+
+ await cameraController.pausePreview();
+
+ verifyNever(
+ CameraPlatform.instance.pausePreview(cameraController.cameraId));
+ expect(cameraController.value.isPreviewPaused, equals(true));
+ });
+
+ test('pausePreview() throws $CameraException on $PlatformException',
+ () async {
+ CameraController cameraController = CameraController(
+ CameraDescription(
+ name: 'cam',
+ lensDirection: CameraLensDirection.back,
+ sensorOrientation: 90),
+ ResolutionPreset.max);
+ await cameraController.initialize();
+ when(CameraPlatform.instance.pausePreview(cameraController.cameraId))
+ .thenThrow(
+ PlatformException(
+ code: 'TEST_ERROR',
+ message: 'This is a test error message',
+ details: null,
+ ),
+ );
+
+ expect(
+ cameraController.pausePreview(),
+ throwsA(isA<CameraException>().having(
+ (error) => error.description,
+ 'TEST_ERROR',
+ 'This is a test error message',
+ )));
+ });
+
+ test('resumePreview() calls $CameraPlatform', () async {
+ CameraController cameraController = CameraController(
+ CameraDescription(
+ name: 'cam',
+ lensDirection: CameraLensDirection.back,
+ sensorOrientation: 90),
+ ResolutionPreset.max);
+ await cameraController.initialize();
+ cameraController.value =
+ cameraController.value.copyWith(isPreviewPaused: true);
+
+ await cameraController.resumePreview();
+
+ verify(CameraPlatform.instance.resumePreview(cameraController.cameraId))
+ .called(1);
+ expect(cameraController.value.isPreviewPaused, equals(false));
+ });
+
+ test('resumePreview() does not call $CameraPlatform when not paused',
+ () async {
+ CameraController cameraController = CameraController(
+ CameraDescription(
+ name: 'cam',
+ lensDirection: CameraLensDirection.back,
+ sensorOrientation: 90),
+ ResolutionPreset.max);
+ await cameraController.initialize();
+ cameraController.value =
+ cameraController.value.copyWith(isPreviewPaused: false);
+
+ await cameraController.resumePreview();
+
+ verifyNever(
+ CameraPlatform.instance.resumePreview(cameraController.cameraId));
+ expect(cameraController.value.isPreviewPaused, equals(false));
+ });
+
+ test('resumePreview() throws $CameraException on $PlatformException',
+ () async {
+ CameraController cameraController = CameraController(
+ CameraDescription(
+ name: 'cam',
+ lensDirection: CameraLensDirection.back,
+ sensorOrientation: 90),
+ ResolutionPreset.max);
+ await cameraController.initialize();
+ cameraController.value =
+ cameraController.value.copyWith(isPreviewPaused: true);
+ when(CameraPlatform.instance.resumePreview(cameraController.cameraId))
+ .thenThrow(
+ PlatformException(
+ code: 'TEST_ERROR',
+ message: 'This is a test error message',
+ details: null,
+ ),
+ );
+
+ expect(
+ cameraController.resumePreview(),
+ throwsA(isA<CameraException>().having(
+ (error) => error.description,
+ 'TEST_ERROR',
+ 'This is a test error message',
+ )));
+ });
+
test('lockCaptureOrientation() calls $CameraPlatform', () async {
CameraController cameraController = CameraController(
CameraDescription(
@@ -1315,6 +1447,14 @@
.noSuchMethod(Invocation.method(#unlockCaptureOrientation, [cameraId]));
@override
+ Future<void> pausePreview(int? cameraId) async =>
+ super.noSuchMethod(Invocation.method(#pausePreview, [cameraId]));
+
+ @override
+ Future<void> resumePreview(int? cameraId) async =>
+ super.noSuchMethod(Invocation.method(#resumePreview, [cameraId]));
+
+ @override
Future<double> getMaxZoomLevel(int? cameraId) async => super.noSuchMethod(
Invocation.method(#getMaxZoomLevel, [cameraId]),
returnValue: 1.0,
diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart
index e0378cc..4718d89 100644
--- a/packages/camera/camera/test/camera_value_test.dart
+++ b/packages/camera/camera/test/camera_value_test.dart
@@ -29,6 +29,8 @@
lockedCaptureOrientation: DeviceOrientation.portraitUp,
recordingOrientation: DeviceOrientation.portraitUp,
focusPointSupported: true,
+ isPreviewPaused: false,
+ previewPauseOrientation: DeviceOrientation.portraitUp,
);
expect(cameraValue, isA<CameraValue>());
@@ -46,6 +48,8 @@
expect(
cameraValue.lockedCaptureOrientation, DeviceOrientation.portraitUp);
expect(cameraValue.recordingOrientation, DeviceOrientation.portraitUp);
+ expect(cameraValue.isPreviewPaused, false);
+ expect(cameraValue.previewPauseOrientation, DeviceOrientation.portraitUp);
});
test('Can be created as uninitialized', () {
@@ -66,6 +70,8 @@
expect(cameraValue.deviceOrientation, DeviceOrientation.portraitUp);
expect(cameraValue.lockedCaptureOrientation, null);
expect(cameraValue.recordingOrientation, null);
+ expect(cameraValue.isPreviewPaused, isFalse);
+ expect(cameraValue.previewPauseOrientation, null);
});
test('Can be copied with isInitialized', () {
@@ -87,6 +93,8 @@
expect(cameraValue.deviceOrientation, DeviceOrientation.portraitUp);
expect(cameraValue.lockedCaptureOrientation, null);
expect(cameraValue.recordingOrientation, null);
+ expect(cameraValue.isPreviewPaused, isFalse);
+ expect(cameraValue.previewPauseOrientation, null);
});
test('Has aspectRatio after setting size', () {
@@ -117,25 +125,26 @@
test('toString() works as expected', () {
var cameraValue = const CameraValue(
- isInitialized: false,
- errorDescription: null,
- previewSize: Size(10, 10),
- isRecordingPaused: false,
- isRecordingVideo: false,
- isTakingPicture: false,
- isStreamingImages: false,
- flashMode: FlashMode.auto,
- exposureMode: ExposureMode.auto,
- focusMode: FocusMode.auto,
- exposurePointSupported: true,
- focusPointSupported: true,
- deviceOrientation: DeviceOrientation.portraitUp,
- lockedCaptureOrientation: DeviceOrientation.portraitUp,
- recordingOrientation: DeviceOrientation.portraitUp,
- );
+ isInitialized: false,
+ errorDescription: null,
+ previewSize: Size(10, 10),
+ isRecordingPaused: false,
+ isRecordingVideo: false,
+ isTakingPicture: false,
+ 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,
+ previewPauseOrientation: DeviceOrientation.portraitUp);
expect(cameraValue.toString(),
- '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)');
+ '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)');
});
});
}