Skip to content

Commit

Permalink
Merge pull request #1048 from navaronbracke/fix_ios_torch_mode
Browse files Browse the repository at this point in the history
fix: provide correct initial torch state
  • Loading branch information
navaronbracke authored Apr 30, 2024
2 parents c5e0289 + abaeadb commit a71d9ff
Show file tree
Hide file tree
Showing 25 changed files with 256 additions and 149 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Bugs fixed:
* Fixed a crash when the controller is disposed while it is still starting. [#1036](https://github.com/juliansteenbakker/mobile_scanner/pull/1036) (thanks @EArminjon !)
* Fixed an issue that causes the initial torch state to be out of sync.

Improvements:
* Updated the lifeycle code sample to handle not-initialized controllers.

## 5.0.1

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ class MyState extends State<MyStatefulWidget> with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
// If the controller is not ready, do not try to start or stop it.
// Permission dialogs can trigger lifecycle changes before the controller is ready.
if (!controller.value.isInitialized) {
return;
}
switch (state) {
case AppLifecycleState.detached:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.core.TorchState
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.core.resolutionselector.ResolutionStrategy
Expand Down Expand Up @@ -368,11 +369,22 @@ class MobileScanner(
val height = resolution.height.toDouble()
val portrait = (camera?.cameraInfo?.sensorRotationDegrees ?: 0) % 180 == 0

// Start with 'unavailable' torch state.
var currentTorchState: Int = -1

camera?.cameraInfo?.let {
if (!it.hasFlashUnit()) {
return@let
}

currentTorchState = it.torchState.value ?: -1
}

mobileScannerStartedCallback(
MobileScannerStartParameters(
if (portrait) width else height,
if (portrait) height else width,
camera?.cameraInfo?.hasFlashUnit() ?: false,
currentTorchState,
textureEntry!!.id(),
numberOfCameras ?: 0
)
Expand Down Expand Up @@ -411,13 +423,16 @@ class MobileScanner(
/**
* Toggles the flash light on or off.
*/
fun toggleTorch(enableTorch: Boolean) {
if (camera == null) {
return
}
fun toggleTorch() {
camera?.let {
if (!it.cameraInfo.hasFlashUnit()) {
return@let
}

if (camera?.cameraInfo?.hasFlashUnit() == true) {
camera?.cameraControl?.enableTorch(enableTorch)
when(it.cameraInfo.torchState.value) {
TorchState.OFF -> it.cameraControl.enableTorch(true)
TorchState.ON -> it.cameraControl.enableTorch(false)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class MobileScannerHandler(
private var mobileScanner: MobileScanner? = null

private val torchStateCallback: TorchStateCallback = {state: Int ->
// Off = 0, On = 1
barcodeHandler.publishEvent(mapOf("name" to "torchState", "data" to state))
}

Expand Down Expand Up @@ -121,8 +122,8 @@ class MobileScannerHandler(
}
})
"start" -> start(call, result)
"torch" -> toggleTorch(call, result)
"stop" -> stop(result)
"toggleTorch" -> toggleTorch(result)
"analyzeImage" -> analyzeImage(call, result)
"setScale" -> setScale(call, result)
"resetScale" -> resetScale(result)
Expand Down Expand Up @@ -167,7 +168,7 @@ class MobileScannerHandler(
val position =
if (facing == 0) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA

val detectionSpeed: DetectionSpeed = DetectionSpeed.values().first { it.intValue == speed}
val detectionSpeed: DetectionSpeed = DetectionSpeed.entries.first { it.intValue == speed}

mobileScanner!!.start(
barcodeScannerOptions,
Expand All @@ -182,7 +183,7 @@ class MobileScannerHandler(
result.success(mapOf(
"textureId" to it.id,
"size" to mapOf("width" to it.width, "height" to it.height),
"torchable" to it.hasFlashUnit,
"currentTorchState" to it.currentTorchState,
"numberOfCameras" to it.numberOfCameras
))
}
Expand Down Expand Up @@ -243,8 +244,8 @@ class MobileScannerHandler(
mobileScanner!!.analyzeImage(uri, analyzeImageSuccessCallback, analyzeImageErrorCallback)
}

private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
mobileScanner!!.toggleTorch(call.arguments == 1)
private fun toggleTorch(result: MethodChannel.Result) {
mobileScanner?.toggleTorch()
result.success(null)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package dev.steenbakker.mobile_scanner.objects
class MobileScannerStartParameters(
val width: Double = 0.0,
val height: Double,
val hasFlashUnit: Boolean,
val currentTorchState: Int,
val id: Long,
val numberOfCameras: Int
)
26 changes: 22 additions & 4 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
3DBCC0215D7BED1D9A756EA3 /* [CP] Embed Pods Frameworks */,
BB0C8EA8DA81A75DE53F052F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
Expand All @@ -215,7 +216,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
Expand Down Expand Up @@ -317,6 +318,23 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
BB0C8EA8DA81A75DE53F052F /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
C7DE006A696F551C4E067E41 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -495,7 +513,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile-scanner.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner.RunnerTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand All @@ -513,7 +531,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile-scanner.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner.RunnerTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
Expand All @@ -529,7 +547,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile-scanner.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = "com.example.mobile-scanner.RunnerTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
8 changes: 4 additions & 4 deletions example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
Expand All @@ -28,6 +30,8 @@
<string>This app needs camera access to scan QR codes</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photos access to get QR code from photo library</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -47,9 +51,5 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
4 changes: 3 additions & 1 deletion example/lib/barcode_scanner_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ class _BarcodeScannerWithControllerState

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (!controller.value.isInitialized) {
return;
}

switch (state) {
case AppLifecycleState.detached:
Expand Down
9 changes: 9 additions & 0 deletions example/lib/scanner_button_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ class ToggleFlashlightButton extends StatelessWidget {
}

switch (state.torchState) {
case TorchState.auto:
return IconButton(
color: Colors.white,
iconSize: 32.0,
icon: const Icon(Icons.flash_auto),
onPressed: () async {
await controller.toggleTorch();
},
);
case TorchState.off:
return IconButton(
color: Colors.white,
Expand Down
2 changes: 1 addition & 1 deletion example/macos/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
61 changes: 46 additions & 15 deletions ios/Classes/MobileScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
// as they interact with the hardware camera.
if (torch) {
DispatchQueue.main.async {
do {
try self.toggleTorch(.on)
} catch {
// If the torch does not turn on,
// continue with the capture session anyway.
}
self.turnTorchOn()
}
}

Expand All @@ -283,13 +278,12 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
// as this does not change the configuration of the hardware camera.
let dimensions = CMVideoFormatDescriptionGetDimensions(
device.activeFormat.formatDescription)
let hasTorch = device.hasTorch

completion(
MobileScannerStartParameters(
width: Double(dimensions.height),
height: Double(dimensions.width),
hasTorch: hasTorch,
currentTorchState: device.hasTorch ? device.torchMode.rawValue : -1,
textureId: self.textureId ?? 0
)
)
Expand Down Expand Up @@ -324,30 +318,67 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
device = nil
}

/// Set the torch mode.
/// Toggle the torch.
///
/// This method should be called on the main DispatchQueue.
func toggleTorch(_ torch: AVCaptureDevice.TorchMode) throws {
func toggleTorch() {
guard let device = self.device else {
return
}

if (!device.hasTorch || !device.isTorchAvailable || !device.isTorchModeSupported(torch)) {
if (!device.hasTorch || !device.isTorchAvailable) {
return
}

if (device.torchMode != torch) {
var newTorchMode: AVCaptureDevice.TorchMode = device.torchMode

switch(device.torchMode) {
case AVCaptureDevice.TorchMode.auto:
newTorchMode = device.isTorchActive ? AVCaptureDevice.TorchMode.off : AVCaptureDevice.TorchMode.on
break;
case AVCaptureDevice.TorchMode.off:
newTorchMode = AVCaptureDevice.TorchMode.on
break;
case AVCaptureDevice.TorchMode.on:
newTorchMode = AVCaptureDevice.TorchMode.off
break;
default:
return;
}

if (!device.isTorchModeSupported(newTorchMode) || device.torchMode == newTorchMode) {
return;
}

do {
try device.lockForConfiguration()
device.torchMode = torch
device.torchMode = newTorchMode
device.unlockForConfiguration()
} catch(_) {}
}

/// Turn the torch on.
private func turnTorchOn() {
guard let device = self.device else {
return
}

if (!device.hasTorch || !device.isTorchAvailable || !device.isTorchModeSupported(.on) || device.torchMode == .on) {
return
}

do {
try device.lockForConfiguration()
device.torchMode = .on
device.unlockForConfiguration()
} catch(_) {}
}

// Observer for torch state
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
switch keyPath {
case "torchMode":
// off = 0; on = 1; auto = 2
// Off = 0, On = 1, Auto = 2
let state = change?[.newKey] as? Int
torchModeChangeCallback(state)
case "videoZoomFactor":
Expand Down Expand Up @@ -459,7 +490,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
struct MobileScannerStartParameters {
var width: Double = 0.0
var height: Double = 0.0
var hasTorch = false
var currentTorchState: Int = -1
var textureId: Int64 = 0
}
}
Expand Down
Loading

0 comments on commit a71d9ff

Please sign in to comment.