diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 7e1ff85e8574..13694d311fc9 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -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+9 * Update android compileSdkVersion to 29. diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 63e4d03e982a..6db4e7256cdf 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -50,6 +50,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; @@ -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(); @@ -281,6 +311,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()); diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 132075555f26..8a35c4d1b381 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -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) { diff --git a/packages/camera/example/ios/Flutter/.last_build_id b/packages/camera/example/ios/Flutter/.last_build_id new file mode 100644 index 000000000000..133c323a68b8 --- /dev/null +++ b/packages/camera/example/ios/Flutter/.last_build_id @@ -0,0 +1 @@ +ca4deb5e1e4d8fdaf00843cb47ee20c7 \ No newline at end of file diff --git a/packages/camera/example/ios/Flutter/Flutter.podspec b/packages/camera/example/ios/Flutter/Flutter.podspec new file mode 100644 index 000000000000..5ca30416bac0 --- /dev/null +++ b/packages/camera/example/ios/Flutter/Flutter.podspec @@ -0,0 +1,18 @@ +# +# NOTE: This podspec is NOT to be published. It is only used as a local source! +# + +Pod::Spec.new do |s| + s.name = 'Flutter' + s.version = '1.0.0' + s.summary = 'High-performance, high-fidelity mobile apps.' + s.description = <<-DESC +Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. + DESC + s.homepage = 'https://flutter.io' + s.license = { :type => 'MIT' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } + s.ios.deployment_target = '8.0' + s.vendored_frameworks = 'Flutter.framework' +end diff --git a/packages/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/example/ios/Runner.xcodeproj/project.pbxproj index 862ee64fb666..0096f9662da9 100644 --- a/packages/camera/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,11 +9,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 75201D617916C49BDEDF852A /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 620DDA07C00B5FF2F937CB5B /* libPods-Runner.a */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -28,8 +24,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -40,7 +34,6 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 483D985F075B951ADBAD218E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 620DDA07C00B5FF2F937CB5B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -48,7 +41,6 @@ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -63,8 +55,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 75201D617916C49BDEDF852A /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -83,9 +73,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -229,7 +217,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 3E30118C54AB12C3EB9EDF27 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -269,9 +257,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../Flutter/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -315,7 +306,6 @@ /* Begin XCBuildConfiguration section */ 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -372,7 +362,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; diff --git a/packages/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index ce8d37457123..cf20da97d6bb 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -43,6 +43,8 @@ class _CameraExampleHomeState extends State VideoPlayerController videoController; VoidCallback videoPlayerListener; bool enableAudio = true; + bool enableTorch = false; + bool torchSupported = false; @override void initState() { @@ -102,7 +104,12 @@ class _CameraExampleHomeState extends State ), ), _captureControlRowWidget(), - _toggleAudioWidget(), + Row( + children: [ + _toggleAudioWidget(), + _toggleTorchWidget(), + ], + ), Padding( padding: const EdgeInsets.all(5.0), child: Row( @@ -158,6 +165,36 @@ class _CameraExampleHomeState extends State ); } + /// Toggle torch mode + Widget _toggleTorchWidget() { + return Opacity( + opacity: torchSupported ? 1.0 : 0.2, + child: Padding( + padding: const EdgeInsets.only(left: 25), + child: Row( + children: [ + 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( @@ -237,7 +274,7 @@ class _CameraExampleHomeState extends State controller.value.isRecordingVideo ? onStopButtonPressed : null, - ) + ), ], ); } @@ -278,6 +315,7 @@ class _CameraExampleHomeState extends State void onNewCameraSelected(CameraDescription cameraDescription) async { if (controller != null) { await controller.dispose(); + setState(() => enableTorch = false); } controller = CameraController( cameraDescription, @@ -295,6 +333,8 @@ class _CameraExampleHomeState extends State try { await controller.initialize(); + final hasTorch = await controller.hasTorch(); + setState(() => torchSupported = hasTorch); } on CameraException catch (e) { _showCameraException(e); } diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index 525c1286717a..485acfaaf648 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -180,6 +180,7 @@ @interface FLTCam : NSObject isRecordingVideo && _isRecordingPaused; @@ -209,6 +214,7 @@ class CameraValue { String errorDescription, Size previewSize, bool isRecordingPaused, + bool torchEnabled, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -218,6 +224,7 @@ class CameraValue { isTakingPicture: isTakingPicture ?? this.isTakingPicture, isStreamingImages: isStreamingImages ?? this.isStreamingImages, isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, + torchEnabled: torchEnabled ?? this.torchEnabled, ); } @@ -229,6 +236,7 @@ class CameraValue { 'isInitialized: $isInitialized, ' 'errorDescription: $errorDescription, ' 'previewSize: $previewSize, ' + 'torchEnabled: $torchEnabled, ' 'isStreamingImages: $isStreamingImages)'; } } @@ -569,6 +577,77 @@ class CameraController extends ValueNotifier { } } + /// Enables flash torch mode + Future 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('enableTorch'); + value = value.copyWith(torchEnabled: true); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Disables flash torch mode + Future 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('disableTorch'); + value = value.copyWith(torchEnabled: false); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Check if the camera supports torch mode + Future 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('hasTorch'); + } 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/pubspec.yaml b/packages/camera/pubspec.yaml index 95a84d046b5e..2678569faf9a 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,7 +2,7 @@ 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.5.8+9 +version: 0.5.9 homepage: https://github.com/flutter/plugins/tree/master/packages/camera