Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
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
3 changes: 3 additions & 0 deletions packages/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.5.9
* Added `enableTorch`, `disableTorch`, and `hasTorch` to `CameraController` to enable the use of the flash in torch mode (continuous on).

## 0.5.8+3

* Fix bug in usage example in README.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class Camera {
private final Size captureSize;
private final Size previewSize;
private final boolean enableAudio;
private final boolean flashSupported;
private boolean torchEnabled = false;

private CameraDevice cameraDevice;
private CameraCaptureSession cameraCaptureSession;
Expand Down Expand Up @@ -110,12 +112,40 @@ public void onOrientationChanged(int i) {
isFrontFacing =
characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT;
ResolutionPreset preset = ResolutionPreset.valueOf(resolutionPreset);
Boolean flashInfoAvailable = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
flashSupported = flashInfoAvailable == null ? false : flashInfoAvailable;
recordingProfile =
CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset);
captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight);
previewSize = computeBestPreviewSize(cameraName, preset);
}

// Will turn the torch on/off as long as the device and camera supports it
public void toggleTorch(boolean enable, @NonNull final Result result) {
try {
if (flashSupported) {
if (enable) {
captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
torchEnabled = true;
} else {
captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
torchEnabled = false;
}
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
result.success(null);
} else {
result.error("flashFailed", "Flash is not supported on this device", "");
}
} catch (CameraAccessException e) {
result.error("cameraAccess", e.getMessage(), null);
}
}

// Returns true if camera supports the torch
public void hasTorch(@NonNull final Result result) {
result.success(flashSupported);
}

private void prepareMediaRecorder(String outputFilePath) throws IOException {
if (mediaRecorder != null) {
mediaRecorder.release();
Expand Down Expand Up @@ -292,6 +322,11 @@ private void createCaptureSession(
// Create a new capture builder.
captureRequestBuilder = cameraDevice.createCaptureRequest(templateType);

// When starting a video recording, re-enable flash torch if we had it enabled before starting
if (torchEnabled) {
captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
}

// Build Flutter surface to render to
SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture();
surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,33 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
}
break;
}
case "enableTorch":
{
try {
camera.toggleTorch(true, result);
} catch (Exception e) {
handleException(e, result);
}
break;
}
case "disableTorch":
{
try {
camera.toggleTorch(false, result);
} catch (Exception e) {
handleException(e, result);
}
break;
}
case "hasTorch":
{
try {
camera.hasTorch(result);
} catch (Exception e) {
handleException(e, result);
}
break;
}
case "dispose":
{
if (camera != null) {
Expand Down
1 change: 1 addition & 0 deletions packages/camera/example/ios/Flutter/.last_build_id
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ca4deb5e1e4d8fdaf00843cb47ee20c7
44 changes: 42 additions & 2 deletions packages/camera/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
VideoPlayerController videoController;
VoidCallback videoPlayerListener;
bool enableAudio = true;
bool enableTorch = false;
bool torchSupported = false;

@override
void initState() {
Expand Down Expand Up @@ -102,7 +104,12 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
),
),
_captureControlRowWidget(),
_toggleAudioWidget(),
Row(
children: [
_toggleAudioWidget(),
_toggleTorchWidget(),
],
),
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
Expand Down Expand Up @@ -158,6 +165,36 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
);
}

/// Toggle torch mode
Widget _toggleTorchWidget() {
return Opacity(
opacity: torchSupported ? 1.0 : 0.2,
child: Padding(
padding: const EdgeInsets.only(left: 25),
child: Row(
children: <Widget>[
const Text('Toggle Torch:'),
Switch(
value: enableTorch,
onChanged: (bool value) async {
if (controller == null || !torchSupported) {
return;
}

setState(() => enableTorch = value);
if (enableTorch) {
await controller.enableTorch();
} else {
await controller.disableTorch();
}
},
),
],
),
),
);
}

/// Display the thumbnail of the captured image or video.
Widget _thumbnailWidget() {
return Expanded(
Expand Down Expand Up @@ -237,7 +274,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
controller.value.isRecordingVideo
? onStopButtonPressed
: null,
)
),
],
);
}
Expand Down Expand Up @@ -278,6 +315,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
void onNewCameraSelected(CameraDescription cameraDescription) async {
if (controller != null) {
await controller.dispose();
setState(() => enableTorch = false);
}
controller = CameraController(
cameraDescription,
Expand All @@ -295,6 +333,8 @@ class _CameraExampleHomeState extends State<CameraExampleHome>

try {
await controller.initialize();
final hasTorch = await controller.hasTorch();
setState(() => torchSupported = hasTorch);
} on CameraException catch (e) {
_showCameraException(e);
}
Expand Down
45 changes: 45 additions & 0 deletions packages/camera/ios/Classes/CameraPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ @interface FLTCam : NSObject <FlutterTexture,
@property(assign, nonatomic) BOOL audioIsDisconnected;
@property(assign, nonatomic) BOOL isAudioSetup;
@property(assign, nonatomic) BOOL isStreamingImages;
@property(assign, nonatomic) BOOL isTorchEnabled;
@property(assign, nonatomic) ResolutionPreset resolutionPreset;
@property(assign, nonatomic) CMTime lastVideoSampleTime;
@property(assign, nonatomic) CMTime lastAudioSampleTime;
Expand Down Expand Up @@ -669,6 +670,32 @@ - (void)stopImageStream {
}
}

- (void)toggleTorch:(bool)enabled:(FlutterResult)result:(AVCaptureDevice *)device {
NSLog(@"[toggleTorch] Calling with enabled: %s _isTorchEnabled: %s",
enabled == true ? "true" : "false", _isTorchEnabled == true ? "true" : "false");
if ([device hasTorch]) {
[device lockForConfiguration:nil];
if (!enabled) {
[device setTorchMode:AVCaptureTorchModeOff];
_isTorchEnabled = false;
result(nil);
} else {
NSError *anyError;
BOOL success = [device setTorchModeOnWithLevel:AVCaptureMaxAvailableTorchLevel
error:&anyError];
[device unlockForConfiguration];
if (!success) {
result(getFlutterError(anyError));
} else {
_isTorchEnabled = true;
result(nil);
}
}
} else {
result([FlutterError errorWithCode:@"UNAVAILABLE" message:@"Torch is unavailable" details:nil]);
}
}

- (BOOL)setupWriterForPath:(NSString *)path {
NSError *error = nil;
NSURL *outputURL;
Expand Down Expand Up @@ -726,6 +753,14 @@ - (BOOL)setupWriterForPath:(NSString *)path {
[_audioOutput setSampleBufferDelegate:self queue:_dispatchQueue];
}

// When starting video capture the torch will be turned off, so re-enable it here so it's started
// in time for recording to start.
if (_isTorchEnabled) {
[self.captureDevice lockForConfiguration:nil];
[self.captureDevice setTorchModeOnWithLevel:AVCaptureMaxAvailableTorchLevel error:nil];
[self.captureDevice unlockForConfiguration];
}

[_videoWriter addInput:_videoWriterInput];
[_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue];

Expand Down Expand Up @@ -828,6 +863,16 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re
}];
}
result(reply);
} else if ([@"enableTorch" isEqualToString:call.method]) {
[_camera toggleTorch:true:result:_camera.captureDevice];
} else if ([@"disableTorch" isEqualToString:call.method]) {
[_camera toggleTorch:false:result:_camera.captureDevice];
} else if ([@"hasTorch" isEqualToString:call.method]) {
if ([_camera.captureDevice hasTorch]) {
result(@(YES));
} else {
result(@(NO));
}
} else if ([@"initialize" isEqualToString:call.method]) {
NSString *cameraName = call.arguments[@"cameraName"];
NSString *resolutionPreset = call.arguments[@"resolutionPreset"];
Expand Down
79 changes: 79 additions & 0 deletions packages/camera/lib/camera.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class CameraValue {
this.isRecordingVideo,
this.isTakingPicture,
this.isStreamingImages,
this.torchEnabled,
bool isRecordingPaused,
}) : _isRecordingPaused = isRecordingPaused;

Expand All @@ -168,6 +169,7 @@ class CameraValue {
isTakingPicture: false,
isStreamingImages: false,
isRecordingPaused: false,
torchEnabled: false,
);

/// True after [CameraController.initialize] has completed successfully.
Expand All @@ -184,6 +186,9 @@ class CameraValue {

final bool _isRecordingPaused;

/// True when flash torch is enabled.
final bool torchEnabled;

/// True when camera [isRecordingVideo] and recording is paused.
bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused;

Expand All @@ -209,6 +214,7 @@ class CameraValue {
String errorDescription,
Size previewSize,
bool isRecordingPaused,
bool torchEnabled,
}) {
return CameraValue(
isInitialized: isInitialized ?? this.isInitialized,
Expand All @@ -218,6 +224,7 @@ class CameraValue {
isTakingPicture: isTakingPicture ?? this.isTakingPicture,
isStreamingImages: isStreamingImages ?? this.isStreamingImages,
isRecordingPaused: isRecordingPaused ?? _isRecordingPaused,
torchEnabled: torchEnabled ?? this.torchEnabled,
);
}

Expand All @@ -229,6 +236,7 @@ class CameraValue {
'isInitialized: $isInitialized, '
'errorDescription: $errorDescription, '
'previewSize: $previewSize, '
'torchEnabled: $torchEnabled, '
'isStreamingImages: $isStreamingImages)';
}
}
Expand Down Expand Up @@ -569,6 +577,77 @@ class CameraController extends ValueNotifier<CameraValue> {
}
}

/// Enables flash torch mode
Future<void> enableTorch() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'enableTorch was called on uninitialized CameraController',
);
}
if (value.isTakingPicture) {
throw CameraException(
'Previous capture has not returned yet.',
'takePicture was called before the previous capture returned.',
);
}
if (value.torchEnabled) {
return;
}
try {
await _channel.invokeMethod<void>('enableTorch');
value = value.copyWith(torchEnabled: true);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}

/// Disables flash torch mode
Future<void> disableTorch() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'disableTorch was called on uninitialized CameraController',
);
}
if (value.isTakingPicture) {
throw CameraException(
'Previous capture has not returned yet.',
'takePicture was called before the previous capture returned.',
);
}
if (!value.torchEnabled) {
return;
}
try {
await _channel.invokeMethod<void>('disableTorch');
value = value.copyWith(torchEnabled: false);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}

/// Check if the camera supports torch mode
Future<bool> hasTorch() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'disableTorch was called on uninitialized CameraController',
);
}
if (value.isTakingPicture) {
throw CameraException(
'Previous capture has not returned yet.',
'takePicture was called before the previous capture returned.',
);
}
try {
return _channel.invokeMethod<bool>('hasTorch');
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
}

/// Releases the resources of this camera.
@override
Future<void> dispose() async {
Expand Down
Loading