diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md
index a11579d528a4..9789e9e80b69 100644
--- a/packages/camera/camera/CHANGELOG.md
+++ b/packages/camera/camera/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.6.1
+
+* Add flash support for Android and iOS implementations.
+
## 0.6.0+2
* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276))
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 306dd447cfb9..27f1355319c1 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
@@ -17,6 +17,8 @@
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
@@ -34,6 +36,8 @@
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.camera.media.MediaRecorderBuilder;
+import io.flutter.plugins.camera.types.FlashMode;
+import io.flutter.plugins.camera.types.ResolutionPreset;
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
import java.io.File;
import java.io.FileOutputStream;
@@ -69,16 +73,8 @@ public class Camera {
private CamcorderProfile recordingProfile;
private int currentOrientation = ORIENTATION_UNKNOWN;
private Context applicationContext;
-
- // Mirrors camera.dart
- public enum ResolutionPreset {
- low,
- medium,
- high,
- veryHigh,
- ultraHigh,
- max,
- }
+ private FlashMode flashMode;
+ private PictureCaptureRequest pictureCaptureRequest;
public Camera(
final Activity activity,
@@ -97,6 +93,7 @@ public Camera(
this.dartMessenger = dartMessenger;
this.cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
this.applicationContext = activity.getApplicationContext();
+ this.flashMode = FlashMode.auto;
orientationEventListener =
new OrientationEventListener(activity.getApplicationContext()) {
@Override
@@ -220,58 +217,125 @@ SurfaceTextureEntry getFlutterTexture() {
}
public void takePicture(@NonNull final Result result) {
+ // Only take 1 picture at a time
+ if (pictureCaptureRequest != null && !pictureCaptureRequest.isFinished()) {
+ result.error("captureAlreadyActive", "Picture is currently already being captured", null);
+ return;
+ }
+ // Store the result
+ this.pictureCaptureRequest = new PictureCaptureRequest(result);
+
+ // Create temporary file
final File outputDir = applicationContext.getCacheDir();
final File file;
try {
file = File.createTempFile("CAP", ".jpg", outputDir);
} catch (IOException | SecurityException e) {
- result.error("cannotCreateFile", e.getMessage(), null);
+ pictureCaptureRequest.error("cannotCreateFile", e.getMessage(), null);
return;
}
+ // Listen for picture being taken
pictureImageReader.setOnImageAvailableListener(
reader -> {
try (Image image = reader.acquireLatestImage()) {
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
writeToFile(buffer, file);
- result.success(file.getAbsolutePath());
+ pictureCaptureRequest.finish(file.getAbsolutePath());
} catch (IOException e) {
- result.error("IOError", "Failed saving image", null);
+ pictureCaptureRequest.error("IOError", "Failed saving image", null);
}
},
null);
+ runPicturePreCapture();
+ }
+
+ private final CameraCaptureSession.CaptureCallback pictureCaptureCallback =
+ new CameraCaptureSession.CaptureCallback() {
+ @Override
+ public void onCaptureCompleted(
+ @NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {
+ assert (pictureCaptureRequest != null);
+ switch (pictureCaptureRequest.getState()) {
+ case awaitingPreCapture:
+ Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ // Some devices might return null here, in which case we will also continue.
+ if (aeState == null
+ || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED
+ || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
+ runPictureCapture();
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onCaptureFailed(
+ @NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull CaptureFailure failure) {
+ assert (pictureCaptureRequest != null);
+ String reason;
+ switch (failure.getReason()) {
+ case CaptureFailure.REASON_ERROR:
+ reason = "An error happened in the framework";
+ break;
+ case CaptureFailure.REASON_FLUSHED:
+ reason = "The capture has failed due to an abortCaptures() call";
+ break;
+ default:
+ reason = "Unknown reason";
+ }
+ pictureCaptureRequest.error("captureFailure", reason, null);
+ }
+ };
+
+ private void runPicturePreCapture() {
+ assert (pictureCaptureRequest != null);
+ pictureCaptureRequest.setState(PictureCaptureRequest.State.awaitingPreCapture);
+
+ captureRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ try {
+ cameraCaptureSession.capture(captureRequestBuilder.build(), pictureCaptureCallback, null);
+ captureRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
+ } catch (CameraAccessException e) {
+ pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
+ }
+ }
+
+ private void runPictureCapture() {
+ assert (pictureCaptureRequest != null);
+ pictureCaptureRequest.setState(PictureCaptureRequest.State.capturing);
try {
final CaptureRequest.Builder captureBuilder =
cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(pictureImageReader.getSurface());
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getMediaOrientation());
-
- cameraCaptureSession.capture(
- captureBuilder.build(),
- new CameraCaptureSession.CaptureCallback() {
- @Override
- public void onCaptureFailed(
- @NonNull CameraCaptureSession session,
- @NonNull CaptureRequest request,
- @NonNull CaptureFailure failure) {
- String reason;
- switch (failure.getReason()) {
- case CaptureFailure.REASON_ERROR:
- reason = "An error happened in the framework";
- break;
- case CaptureFailure.REASON_FLUSHED:
- reason = "The capture has failed due to an abortCaptures() call";
- break;
- default:
- reason = "Unknown reason";
- }
- result.error("captureFailure", reason, null);
- }
- },
- null);
+ switch (flashMode) {
+ case off:
+ captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+ captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ break;
+ case auto:
+ captureBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ break;
+ case always:
+ default:
+ captureBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+ break;
+ }
+ cameraCaptureSession.capture(captureBuilder.build(), pictureCaptureCallback, null);
} catch (CameraAccessException e) {
- result.error("cameraAccess", e.getMessage(), null);
+ pictureCaptureRequest.error("cameraAccess", e.getMessage(), null);
}
}
@@ -314,8 +378,7 @@ public void onConfigured(@NonNull CameraCaptureSession session) {
return;
}
cameraCaptureSession = session;
- captureRequestBuilder.set(
- CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+ initPreviewCaptureBuilder();
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
if (onSuccessCallback != null) {
onSuccessCallback.run();
@@ -452,6 +515,54 @@ public void resumeVideoRecording(@NonNull final Result result) {
result.success(null);
}
+ public void setFlashMode(@NonNull final Result result, FlashMode mode)
+ throws CameraAccessException {
+ // Get the flash availability
+ Boolean flashAvailable;
+ try {
+ flashAvailable =
+ cameraManager
+ .getCameraCharacteristics(cameraDevice.getId())
+ .get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ } catch (CameraAccessException e) {
+ result.error("setFlashModeFailed", e.getMessage(), null);
+ return;
+ }
+ // Check if flash is available.
+ if (flashAvailable == null || !flashAvailable) {
+ result.error("setFlashModeFailed", "Device does not have flash capabilities", null);
+ return;
+ }
+ // Get flash
+
+ this.flashMode = mode;
+ initPreviewCaptureBuilder();
+ this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
+ result.success(null);
+ }
+
+ private void initPreviewCaptureBuilder() {
+ captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ switch (flashMode) {
+ case off:
+ captureRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+ captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ break;
+ case auto:
+ captureRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ break;
+ case always:
+ default:
+ captureRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+ captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+ break;
+ }
+ }
+
public void startPreview() throws CameraAccessException {
if (pictureImageReader == null || pictureImageReader.getSurface() == null) return;
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java
index a7bb3b7d4914..3b665d6b24f2 100644
--- a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java
@@ -10,7 +10,7 @@
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.CamcorderProfile;
import android.util.Size;
-import io.flutter.plugins.camera.Camera.ResolutionPreset;
+import io.flutter.plugins.camera.types.ResolutionPreset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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 6c2e65e76f9e..12b99b72f642 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
@@ -10,6 +10,7 @@
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry;
+import io.flutter.plugins.camera.types.FlashMode;
import io.flutter.view.TextureRegistry;
import java.util.HashMap;
import java.util.Map;
@@ -122,6 +123,21 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
camera.resumeVideoRecording(result);
break;
}
+ case "setFlashMode":
+ {
+ String modeStr = call.argument("mode");
+ FlashMode mode = FlashMode.getValueForString(modeStr);
+ if (mode == null) {
+ result.error("setFlashModeFailed", "Unknown flash mode " + modeStr, null);
+ return;
+ }
+ try {
+ camera.setFlashMode(result, mode);
+ } catch (Exception e) {
+ handleException(e, result);
+ }
+ break;
+ }
case "startImageStream":
{
try {
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java
new file mode 100644
index 000000000000..e365f071d9a8
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/PictureCaptureRequest.java
@@ -0,0 +1,49 @@
+package io.flutter.plugins.camera;
+
+import androidx.annotation.Nullable;
+import io.flutter.plugin.common.MethodChannel;
+
+class PictureCaptureRequest {
+
+ enum State {
+ idle,
+ awaitingPreCapture,
+ capturing,
+ finished,
+ error,
+ }
+
+ private final MethodChannel.Result result;
+ private State state;
+
+ public PictureCaptureRequest(MethodChannel.Result result) {
+ this.result = result;
+ state = State.idle;
+ }
+
+ public void setState(State state) {
+ if (isFinished()) throw new IllegalStateException("Request has already been finished");
+ this.state = state;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public boolean isFinished() {
+ return state == State.finished || state == State.error;
+ }
+
+ public void finish(String absolutePath) {
+ if (isFinished()) throw new IllegalStateException("Request has already been finished");
+ result.success(absolutePath);
+ state = State.finished;
+ }
+
+ public void error(
+ String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
+ if (isFinished()) throw new IllegalStateException("Request has already been finished");
+ result.error(errorCode, errorMessage, errorDetails);
+ state = State.error;
+ }
+}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java
new file mode 100644
index 000000000000..eddeddc47eab
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java
@@ -0,0 +1,16 @@
+package io.flutter.plugins.camera.types;
+
+// Mirrors flash_mode.dart
+public enum FlashMode {
+ off,
+ auto,
+ always;
+
+ public static FlashMode getValueForString(String modeStr) {
+ try {
+ return valueOf(modeStr);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ return null;
+ }
+ }
+}
diff --git a/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java
new file mode 100644
index 000000000000..ffbe2e62095d
--- /dev/null
+++ b/packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java
@@ -0,0 +1,11 @@
+package io.flutter.plugins.camera.types;
+
+// Mirrors camera.dart
+public enum ResolutionPreset {
+ low,
+ medium,
+ high,
+ veryHigh,
+ ultraHigh,
+ max,
+}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java
new file mode 100644
index 000000000000..2b6aa0f25fcf
--- /dev/null
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/PictureCaptureRequestTest.java
@@ -0,0 +1,93 @@
+package io.flutter.plugins.camera;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.flutter.plugin.common.MethodChannel;
+import org.junit.Test;
+
+public class PictureCaptureRequestTest {
+
+ @Test
+ public void state_is_idle_by_default() {
+ PictureCaptureRequest req = new PictureCaptureRequest(null);
+ assertEquals("Default state is idle", req.getState(), PictureCaptureRequest.State.idle);
+ }
+
+ @Test
+ public void setState_sets_state() {
+ PictureCaptureRequest req = new PictureCaptureRequest(null);
+ req.setState(PictureCaptureRequest.State.awaitingPreCapture);
+ assertEquals(
+ "State is awaitingPreCapture",
+ req.getState(),
+ PictureCaptureRequest.State.awaitingPreCapture);
+ req.setState(PictureCaptureRequest.State.capturing);
+ assertEquals(
+ "State is awaitingPreCapture", req.getState(), PictureCaptureRequest.State.capturing);
+ }
+
+ @Test
+ public void finish_sets_result_and_state() {
+ // Setup
+ MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+ PictureCaptureRequest req = new PictureCaptureRequest(mockResult);
+ // Act
+ req.finish("/test/path");
+ // Test
+ verify(mockResult).success("/test/path");
+ assertEquals("State is finished", req.getState(), PictureCaptureRequest.State.finished);
+ }
+
+ @Test
+ public void isFinished_is_true_When_state_is_finished_or_error() {
+ // Setup
+ PictureCaptureRequest req = new PictureCaptureRequest(null);
+ // Test false states
+ req.setState(PictureCaptureRequest.State.idle);
+ assertFalse(req.isFinished());
+ req.setState(PictureCaptureRequest.State.awaitingPreCapture);
+ assertFalse(req.isFinished());
+ req.setState(PictureCaptureRequest.State.capturing);
+ assertFalse(req.isFinished());
+ // Test true states
+ req.setState(PictureCaptureRequest.State.finished);
+ assertTrue(req.isFinished());
+ req = new PictureCaptureRequest(null); // Refresh
+ req.setState(PictureCaptureRequest.State.error);
+ assertTrue(req.isFinished());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void finish_throws_When_already_finished() {
+ // Setup
+ PictureCaptureRequest req = new PictureCaptureRequest(null);
+ req.setState(PictureCaptureRequest.State.finished);
+ // Act
+ req.finish("/test/path");
+ }
+
+ @Test
+ public void error_sets_result_and_state() {
+ // Setup
+ MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+ PictureCaptureRequest req = new PictureCaptureRequest(mockResult);
+ // Act
+ req.error("ERROR_CODE", "Error Message", null);
+ // Test
+ verify(mockResult).error("ERROR_CODE", "Error Message", null);
+ assertEquals("State is error", req.getState(), PictureCaptureRequest.State.error);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void error_throws_When_already_finished() {
+ // Setup
+ PictureCaptureRequest req = new PictureCaptureRequest(null);
+ req.setState(PictureCaptureRequest.State.finished);
+ // Act
+ req.error(null, null, null);
+ }
+}
diff --git a/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java
new file mode 100644
index 000000000000..0549e4fc750e
--- /dev/null
+++ b/packages/camera/camera/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java
@@ -0,0 +1,26 @@
+package io.flutter.plugins.camera.types;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class FlashModeTest {
+
+ @Test
+ public void getValueForString_returns_correct_values() {
+ assertEquals(
+ "Returns FlashMode.off for 'off'", FlashMode.getValueForString("off"), FlashMode.off);
+ assertEquals(
+ "Returns FlashMode.auto for 'auto'", FlashMode.getValueForString("auto"), FlashMode.auto);
+ assertEquals(
+ "Returns FlashMode.always for 'always'",
+ FlashMode.getValueForString("always"),
+ FlashMode.always);
+ }
+
+ @Test
+ public void getValueForString_returns_null_for_nonexistant_value() {
+ assertEquals(
+ "Returns null for 'nonexistant'", FlashMode.getValueForString("nonexistant"), null);
+ }
+}
diff --git a/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml b/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml
index f37f82306024..f216a7251bcf 100644
--- a/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/camera/camera/example/android/app/src/main/AndroidManifest.xml
@@ -37,4 +37,5 @@
android:required="true"/>
+
diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart
index e1edc1b06386..847779dade09 100644
--- a/packages/camera/camera/example/lib/main.dart
+++ b/packages/camera/camera/example/lib/main.dart
@@ -101,6 +101,7 @@ class _CameraExampleHomeState extends State
),
),
_captureControlRowWidget(),
+ _flashModeRowWidget(),
_toggleAudioWidget(),
Padding(
padding: const EdgeInsets.all(5.0),
@@ -191,6 +192,43 @@ class _CameraExampleHomeState extends State
);
}
+ /// Display a bar with buttons to change the flash mode
+ Widget _flashModeRowWidget() {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ IconButton(
+ icon: const Icon(Icons.flash_off),
+ color: controller?.value?.flashMode == FlashMode.off
+ ? Colors.orange
+ : Colors.blue,
+ onPressed: controller != null
+ ? () => onFlashModeButtonPressed(FlashMode.off)
+ : null,
+ ),
+ IconButton(
+ icon: const Icon(Icons.flash_auto),
+ color: controller?.value?.flashMode == FlashMode.auto
+ ? Colors.orange
+ : Colors.blue,
+ onPressed: controller != null
+ ? () => onFlashModeButtonPressed(FlashMode.auto)
+ : null,
+ ),
+ IconButton(
+ icon: const Icon(Icons.flash_on),
+ color: controller?.value?.flashMode == FlashMode.always
+ ? Colors.orange
+ : Colors.blue,
+ onPressed: controller != null
+ ? () => onFlashModeButtonPressed(FlashMode.always)
+ : null,
+ ),
+ ],
+ );
+ }
+
/// Display the control bar with buttons to take pictures and record videos.
Widget _captureControlRowWidget() {
return Row(
@@ -317,6 +355,13 @@ class _CameraExampleHomeState extends State
});
}
+ void onFlashModeButtonPressed(FlashMode mode) {
+ setFlashMode(mode).then((_) {
+ if (mounted) setState(() {});
+ showInSnackBar('Flash mode set to ${mode.toString().split('.').last}');
+ });
+ }
+
void onVideoRecordButtonPressed() {
startVideoRecording().then((_) {
if (mounted) setState(() {});
@@ -406,6 +451,15 @@ class _CameraExampleHomeState extends State
}
}
+ Future setFlashMode(FlashMode mode) async {
+ try {
+ await controller.setFlashMode(mode);
+ } on CameraException catch (e) {
+ _showCameraException(e);
+ rethrow;
+ }
+ }
+
Future _startVideoPlayer() async {
final VideoPlayerController vController =
VideoPlayerController.file(File(videoFile.path));
diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m
index 9455375b8524..cc70ddf209ce 100644
--- a/packages/camera/camera/ios/Classes/CameraPlugin.m
+++ b/packages/camera/camera/ios/Classes/CameraPlugin.m
@@ -114,6 +114,24 @@ - (UIImageOrientation)getImageRotation {
}
@end
+static AVCaptureFlashMode getFlashModeForString(NSString *mode) {
+ if ([mode isEqualToString:@"off"]) {
+ return AVCaptureFlashModeOff;
+ } else if ([mode isEqualToString:@"auto"]) {
+ return AVCaptureFlashModeAuto;
+ } else if ([mode isEqualToString:@"always"]) {
+ return AVCaptureFlashModeOn;
+ } else {
+ NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
+ code:NSURLErrorUnknown
+ userInfo:@{
+ NSLocalizedDescriptionKey : [NSString
+ stringWithFormat:@"Unknown flash mode %@", mode]
+ }];
+ @throw error;
+ }
+}
+
// Mirrors ResolutionPreset in camera.dart
typedef enum {
veryLow,
@@ -181,6 +199,7 @@ @interface FLTCam : NSObject *)messenger {
if (!_isStreamingImages) {
FlutterEventChannel *eventChannel =
@@ -910,6 +956,8 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re
[_camera pauseVideoRecordingWithResult:result];
} else if ([@"resumeVideoRecording" isEqualToString:call.method]) {
[_camera resumeVideoRecordingWithResult:result];
+ } else if ([@"setFlashMode" isEqualToString:call.method]) {
+ [_camera setFlashModeWithResult:result mode:call.arguments[@"mode"]];
} else {
result(FlutterMethodNotImplemented);
}
diff --git a/packages/camera/camera/lib/camera.dart b/packages/camera/camera/lib/camera.dart
index 6c6d90b9bcee..6c6214e96951 100644
--- a/packages/camera/camera/lib/camera.dart
+++ b/packages/camera/camera/lib/camera.dart
@@ -11,5 +11,6 @@ export 'package:camera_platform_interface/camera_platform_interface.dart'
CameraDescription,
CameraException,
CameraLensDirection,
+ FlashMode,
ResolutionPreset,
XFile;
diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart
index fcf00245ce7f..2c2ce3b633f1 100644
--- a/packages/camera/camera/lib/src/camera_controller.dart
+++ b/packages/camera/camera/lib/src/camera_controller.dart
@@ -36,6 +36,7 @@ class CameraValue {
this.isTakingPicture,
this.isStreamingImages,
bool isRecordingPaused,
+ this.flashMode,
}) : _isRecordingPaused = isRecordingPaused;
/// Creates a new camera controller state for an uninitialized controller.
@@ -46,6 +47,7 @@ class CameraValue {
isTakingPicture: false,
isStreamingImages: false,
isRecordingPaused: false,
+ flashMode: FlashMode.auto,
);
/// True after [CameraController.initialize] has completed successfully.
@@ -86,6 +88,9 @@ class CameraValue {
/// When true [errorDescription] describes the error.
bool get hasError => errorDescription != null;
+ /// The flash mode the camera is currently set to.
+ final FlashMode flashMode;
+
/// Creates a modified copy of the object.
///
/// Explicitly specified fields get the specified value, all other fields get
@@ -98,6 +103,7 @@ class CameraValue {
String errorDescription,
Size previewSize,
bool isRecordingPaused,
+ FlashMode flashMode,
}) {
return CameraValue(
isInitialized: isInitialized ?? this.isInitialized,
@@ -107,6 +113,7 @@ class CameraValue {
isTakingPicture: isTakingPicture ?? this.isTakingPicture,
isStreamingImages: isStreamingImages ?? this.isStreamingImages,
isRecordingPaused: isRecordingPaused ?? _isRecordingPaused,
+ flashMode: flashMode ?? this.flashMode,
);
}
@@ -117,7 +124,8 @@ class CameraValue {
'isInitialized: $isInitialized, '
'errorDescription: $errorDescription, '
'previewSize: $previewSize, '
- 'isStreamingImages: $isStreamingImages)';
+ 'isStreamingImages: $isStreamingImages, '
+ 'flashMode: $flashMode)';
}
}
@@ -468,6 +476,16 @@ class CameraController extends ValueNotifier {
}
}
+ /// Sets the flash mode for taking pictures.
+ Future setFlashMode(FlashMode mode) async {
+ try {
+ await CameraPlatform.instance.setFlashMode(_cameraId, mode);
+ value = value.copyWith(flashMode: mode);
+ } on PlatformException catch (e) {
+ throw CameraException(e.code, e.message);
+ }
+ }
+
/// Releases the resources of this camera.
@override
Future dispose() async {
diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml
index 3c3e0cea3565..3194fff33684 100644
--- a/packages/camera/camera/pubspec.yaml
+++ b/packages/camera/camera/pubspec.yaml
@@ -2,13 +2,13 @@ name: camera
description: A Flutter plugin for getting information about and controlling the
camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video,
and streaming image buffers to dart.
-version: 0.6.0+2
+version: 0.6.1
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera
dependencies:
flutter:
sdk: flutter
- camera_platform_interface: ^1.0.0
+ camera_platform_interface: ^1.0.4
dev_dependencies:
path_provider: ^0.5.0
diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart
index b129849cd141..c19aa9718f47 100644
--- a/packages/camera/camera/test/camera_test.dart
+++ b/packages/camera/camera/test/camera_test.dart
@@ -310,6 +310,51 @@ void main() {
'startVideoRecording was called while a camera was streaming images.',
)));
});
+
+ test('setFlashMode() calls $CameraPlatform', () async {
+ CameraController cameraController = CameraController(
+ CameraDescription(
+ name: 'cam',
+ lensDirection: CameraLensDirection.back,
+ sensorOrientation: 90),
+ ResolutionPreset.max);
+ await cameraController.initialize();
+
+ await cameraController.setFlashMode(FlashMode.always);
+
+ verify(CameraPlatform.instance
+ .setFlashMode(cameraController.cameraId, FlashMode.always))
+ .called(1);
+ });
+
+ test('setFlashMode() throws $CameraException on $PlatformException',
+ () async {
+ CameraController cameraController = CameraController(
+ CameraDescription(
+ name: 'cam',
+ lensDirection: CameraLensDirection.back,
+ sensorOrientation: 90),
+ ResolutionPreset.max);
+ await cameraController.initialize();
+
+ when(CameraPlatform.instance
+ .setFlashMode(cameraController.cameraId, FlashMode.always))
+ .thenThrow(
+ PlatformException(
+ code: 'TEST_ERROR',
+ message: 'This is a test error message',
+ details: null,
+ ),
+ );
+
+ expect(
+ cameraController.setFlashMode(FlashMode.always),
+ throwsA(isA().having(
+ (error) => error.description,
+ 'TEST_ERROR',
+ 'This is a test error message',
+ )));
+ });
});
}
diff --git a/packages/camera/camera/test/camera_value_test.dart b/packages/camera/camera/test/camera_value_test.dart
index 28255eb0a568..06b327cb1c29 100644
--- a/packages/camera/camera/test/camera_value_test.dart
+++ b/packages/camera/camera/test/camera_value_test.dart
@@ -5,6 +5,7 @@
import 'dart:ui';
import 'package:camera/camera.dart';
+import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -19,6 +20,7 @@ void main() {
isRecordingVideo: false,
isTakingPicture: false,
isStreamingImages: false,
+ flashMode: FlashMode.auto,
);
expect(cameraValue, isA());
@@ -56,6 +58,7 @@ void main() {
expect(cameraValue.isRecordingVideo, isFalse);
expect(cameraValue.isTakingPicture, isFalse);
expect(cameraValue.isStreamingImages, isFalse);
+ expect(cameraValue.flashMode, FlashMode.auto);
});
test('Has aspectRatio after setting size', () {
@@ -93,10 +96,11 @@ void main() {
isRecordingVideo: false,
isTakingPicture: false,
isStreamingImages: false,
+ flashMode: FlashMode.auto,
);
expect(cameraValue.toString(),
- 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false)');
+ 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto)');
});
});
}