Navaron Bracke
Committed by GitHub

Merge pull request #842 from navaronbracke/fix_torch_on_start

fix: Fix torch state sync issues
@@ -376,7 +376,7 @@ class MobileScanner( @@ -376,7 +376,7 @@ class MobileScanner(
376 */ 376 */
377 fun toggleTorch(enableTorch: Boolean) { 377 fun toggleTorch(enableTorch: Boolean) {
378 if (camera == null) { 378 if (camera == null) {
379 - throw TorchWhenStopped() 379 + return
380 } 380 }
381 381
382 if (camera?.cameraInfo?.hasFlashUnit() == true) { 382 if (camera?.cameraInfo?.hasFlashUnit() == true) {
@@ -4,6 +4,5 @@ class NoCamera : Exception() @@ -4,6 +4,5 @@ class NoCamera : Exception()
4 class AlreadyStarted : Exception() 4 class AlreadyStarted : Exception()
5 class AlreadyStopped : Exception() 5 class AlreadyStopped : Exception()
6 class CameraError : Exception() 6 class CameraError : Exception()
7 -class TorchWhenStopped : Exception()  
8 class ZoomWhenStopped : Exception() 7 class ZoomWhenStopped : Exception()
9 class ZoomNotInRange : Exception() 8 class ZoomNotInRange : Exception()
@@ -237,12 +237,8 @@ class MobileScannerHandler( @@ -237,12 +237,8 @@ class MobileScannerHandler(
237 } 237 }
238 238
239 private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) { 239 private fun toggleTorch(call: MethodCall, result: MethodChannel.Result) {
240 - try {  
241 mobileScanner!!.toggleTorch(call.arguments == 1) 240 mobileScanner!!.toggleTorch(call.arguments == 1)
242 result.success(null) 241 result.success(null)
243 - } catch (e: TorchWhenStopped) {  
244 - result.error("MobileScanner", "Called toggleTorch() while stopped!", null)  
245 - }  
246 } 242 }
247 243
248 private fun setScale(call: MethodCall, result: MethodChannel.Result) { 244 private fun setScale(call: MethodCall, result: MethodChannel.Result) {
@@ -133,7 +133,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -133,7 +133,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
133 } 133 }
134 134
135 /// Start scanning for barcodes 135 /// Start scanning for barcodes
136 - func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: AVCaptureDevice.TorchMode, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws { 136 + func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws {
137 self.detectionSpeed = detectionSpeed 137 self.detectionSpeed = detectionSpeed
138 if (device != nil) { 138 if (device != nil) {
139 throw MobileScannerError.alreadyStarted 139 throw MobileScannerError.alreadyStarted
@@ -213,18 +213,23 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -213,18 +213,23 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
213 213
214 backgroundQueue.async { 214 backgroundQueue.async {
215 self.captureSession.startRunning() 215 self.captureSession.startRunning()
216 - // Enable the torch if parameter is set and torch is available  
217 - // torch should be set after 'startRunning' is called 216 +
  217 + // Turn on the flashlight if requested,
  218 + // but after the capture session started.
  219 + if (torch) {
218 do { 220 do {
219 - try self.toggleTorch(torch) 221 + try self.toggleTorch(.on)
220 } catch { 222 } catch {
221 - print("Failed to set initial torch state.") 223 + // If the torch does not turn on,
  224 + // continue with the capture session anyway.
  225 + }
222 } 226 }
223 227
224 do { 228 do {
225 try self.resetScale() 229 try self.resetScale()
226 } catch { 230 } catch {
227 - print("Failed to reset zoom scale") 231 + // If the zoom scale could not be reset,
  232 + // continue with the capture session anyway.
228 } 233 }
229 234
230 if let device = self.device { 235 if let device = self.device {
@@ -270,19 +275,16 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -270,19 +275,16 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
270 device = nil 275 device = nil
271 } 276 }
272 277
273 - /// Toggle the flashlight between on and off 278 + /// Toggle the flashlight between on and off.
274 func toggleTorch(_ torch: AVCaptureDevice.TorchMode) throws { 279 func toggleTorch(_ torch: AVCaptureDevice.TorchMode) throws {
275 - if (device == nil) {  
276 - throw MobileScannerError.torchWhenStopped 280 + if (device == nil || !device.hasTorch || !device.isTorchAvailable) {
  281 + return
277 } 282 }
278 - if (device.hasTorch && device.isTorchAvailable) {  
279 - do { 283 +
  284 + if (device.torchMode != torch) {
280 try device.lockForConfiguration() 285 try device.lockForConfiguration()
281 device.torchMode = torch 286 device.torchMode = torch
282 device.unlockForConfiguration() 287 device.unlockForConfiguration()
283 - } catch {  
284 - throw MobileScannerError.torchError(error)  
285 - }  
286 } 288 }
287 } 289 }
288 290
@@ -10,9 +10,7 @@ enum MobileScannerError: Error { @@ -10,9 +10,7 @@ enum MobileScannerError: Error {
10 case noCamera 10 case noCamera
11 case alreadyStarted 11 case alreadyStarted
12 case alreadyStopped 12 case alreadyStopped
13 - case torchError(_ error: Error)  
14 case cameraError(_ error: Error) 13 case cameraError(_ error: Error)
15 - case torchWhenStopped  
16 case zoomWhenStopped 14 case zoomWhenStopped
17 case zoomError(_ error: Error) 15 case zoomError(_ error: Error)
18 case analyzerError(_ error: Error) 16 case analyzerError(_ error: Error)
@@ -120,7 +120,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -120,7 +120,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
120 let detectionSpeed: DetectionSpeed = DetectionSpeed(rawValue: speed)! 120 let detectionSpeed: DetectionSpeed = DetectionSpeed(rawValue: speed)!
121 121
122 do { 122 do {
123 - try mobileScanner.start(barcodeScannerOptions: barcodeOptions, returnImage: returnImage, cameraPosition: position, torch: torch ? .on : .off, detectionSpeed: detectionSpeed) { parameters in 123 + try mobileScanner.start(barcodeScannerOptions: barcodeOptions, returnImage: returnImage, cameraPosition: position, torch: torch, detectionSpeed: detectionSpeed) { parameters in
124 DispatchQueue.main.async { 124 DispatchQueue.main.async {
125 result([ 125 result([
126 "textureId": parameters.textureId, 126 "textureId": parameters.textureId,
@@ -136,17 +136,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -136,17 +136,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
136 result(FlutterError(code: "MobileScanner", 136 result(FlutterError(code: "MobileScanner",
137 message: "No camera found or failed to open camera!", 137 message: "No camera found or failed to open camera!",
138 details: nil)) 138 details: nil))
139 - } catch MobileScannerError.torchError(let error) {  
140 - result(FlutterError(code: "MobileScanner",  
141 - message: "Error occured when setting torch!",  
142 - details: error))  
143 } catch MobileScannerError.cameraError(let error) { 139 } catch MobileScannerError.cameraError(let error) {
144 result(FlutterError(code: "MobileScanner", 140 result(FlutterError(code: "MobileScanner",
145 message: "Error occured when setting up camera!", 141 message: "Error occured when setting up camera!",
146 details: error)) 142 details: error))
147 } catch { 143 } catch {
148 result(FlutterError(code: "MobileScanner", 144 result(FlutterError(code: "MobileScanner",
149 - message: "Unknown error occured..", 145 + message: "Unknown error occured.",
150 details: nil)) 146 details: nil))
151 } 147 }
152 } 148 }
@@ -165,9 +161,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { @@ -165,9 +161,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
165 try mobileScanner.toggleTorch(call.arguments as? Int == 1 ? .on : .off) 161 try mobileScanner.toggleTorch(call.arguments as? Int == 1 ? .on : .off)
166 result(nil) 162 result(nil)
167 } catch { 163 } catch {
168 - result(FlutterError(code: "MobileScanner",  
169 - message: "Called toggleTorch() while stopped!",  
170 - details: nil)) 164 + result(FlutterError(code: "MobileScanner", message: error.localizedDescription, details: nil))
171 } 165 }
172 } 166 }
173 167
@@ -149,9 +149,10 @@ class MobileScannerWebPlugin { @@ -149,9 +149,10 @@ class MobileScannerWebPlugin {
149 }); 149 });
150 150
151 final hasTorch = await barCodeReader.hasTorch(); 151 final hasTorch = await barCodeReader.hasTorch();
  152 + final bool? enableTorch = arguments['torch'] as bool?;
152 153
153 - if (hasTorch && arguments.containsKey('torch')) {  
154 - await barCodeReader.toggleTorch(enabled: arguments['torch'] as bool); 154 + if (hasTorch && enableTorch != null) {
  155 + await barCodeReader.toggleTorch(enabled: enableTorch);
155 } 156 }
156 157
157 return { 158 return {
@@ -282,9 +282,6 @@ class MobileScannerController { @@ -282,9 +282,6 @@ class MobileScannerController {
282 282
283 final hasTorch = startResult['torchable'] as bool? ?? false; 283 final hasTorch = startResult['torchable'] as bool? ?? false;
284 hasTorchState.value = hasTorch; 284 hasTorchState.value = hasTorch;
285 - if (hasTorch && torchEnabled) {  
286 - torchState.value = TorchState.on;  
287 - }  
288 285
289 final Size size; 286 final Size size;
290 287
@@ -314,11 +311,11 @@ class MobileScannerController { @@ -314,11 +311,11 @@ class MobileScannerController {
314 311
315 /// Stops the camera, but does not dispose this controller. 312 /// Stops the camera, but does not dispose this controller.
316 Future<void> stop() async { 313 Future<void> stop() async {
317 - try {  
318 await _methodChannel.invokeMethod('stop'); 314 await _methodChannel.invokeMethod('stop');
319 - } catch (e) {  
320 - debugPrint('$e');  
321 - } 315 +
  316 + // After the camera stopped, set the torch state to off,
  317 + // as the torch state callback is never called when the camera is stopped.
  318 + torchState.value = TorchState.off;
322 } 319 }
323 320
324 /// Switches the torch on or off. 321 /// Switches the torch on or off.
@@ -333,14 +330,16 @@ class MobileScannerController { @@ -333,14 +330,16 @@ class MobileScannerController {
333 throw const MobileScannerException( 330 throw const MobileScannerException(
334 errorCode: MobileScannerErrorCode.controllerUninitialized, 331 errorCode: MobileScannerErrorCode.controllerUninitialized,
335 ); 332 );
336 - } else if (!hasTorch) { 333 + }
  334 +
  335 + if (!hasTorch) {
337 return; 336 return;
338 } 337 }
339 338
340 - torchState.value = 339 + final TorchState newState =
341 torchState.value == TorchState.off ? TorchState.on : TorchState.off; 340 torchState.value == TorchState.off ? TorchState.on : TorchState.off;
342 341
343 - await _methodChannel.invokeMethod('torch', torchState.value.rawValue); 342 + await _methodChannel.invokeMethod('torch', newState.rawValue);
344 } 343 }
345 344
346 /// Changes the state of the camera (front or back). 345 /// Changes the state of the camera (front or back).
@@ -274,15 +274,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -274,15 +274,13 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
274 return 274 return
275 } 275 }
276 276
277 - // Enable the torch if parameter is set and torch is available  
278 - if (device.hasTorch) { 277 + // Turn on the torch if requested.
  278 + if (torch) {
279 do { 279 do {
280 - try device.lockForConfiguration()  
281 - device.torchMode = torch ? .on : .off  
282 - device.unlockForConfiguration() 280 + try self.toggleTorchInternal(.on)
283 } catch { 281 } catch {
284 - result(FlutterError(code: error.localizedDescription, message: nil, details: nil))  
285 - return 282 + // If the torch could not be turned on,
  283 + // continue the capture session.
286 } 284 }
287 } 285 }
288 286
@@ -294,7 +292,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -294,7 +292,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
294 let input = try AVCaptureDeviceInput(device: device) 292 let input = try AVCaptureDeviceInput(device: device)
295 captureSession.addInput(input) 293 captureSession.addInput(input)
296 } catch { 294 } catch {
297 - result(FlutterError(code: error.localizedDescription, message: nil, details: nil)) 295 + result(FlutterError(code: "MobileScanner", message: error.localizedDescription, details: nil))
298 return 296 return
299 } 297 }
300 captureSession.sessionPreset = AVCaptureSession.Preset.photo 298 captureSession.sessionPreset = AVCaptureSession.Preset.photo
@@ -320,18 +318,33 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -320,18 +318,33 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
320 result(answer) 318 result(answer)
321 } 319 }
322 320
323 - func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {  
324 - if (device == nil) {  
325 - result(nil) 321 + // TODO: this method should be removed when iOS and MacOS share their implementation.
  322 + private func toggleTorchInternal(_ torch: AVCaptureDevice.TorchMode) throws {
  323 + if (device == nil || !device.hasTorch) {
326 return 324 return
327 } 325 }
328 - do { 326 +
  327 + if #available(macOS 15.0, *) {
  328 + if(!device.isTorchAvailable) {
  329 + return
  330 + }
  331 + }
  332 +
  333 + if (device.torchMode != torch) {
329 try device.lockForConfiguration() 334 try device.lockForConfiguration()
330 - device.torchMode = call.arguments as! Int == 1 ? .on : .off 335 + device.torchMode = torch
331 device.unlockForConfiguration() 336 device.unlockForConfiguration()
  337 + }
  338 + }
  339 +
  340 + func toggleTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  341 + let requestedTorchMode: AVCaptureDevice.TorchMode = call.arguments as! Int == 1 ? .on : .off
  342 +
  343 + do {
  344 + try self.toggleTorchInternal(requestedTorchMode)
332 result(nil) 345 result(nil)
333 } catch { 346 } catch {
334 - result(FlutterError(code: error.localizedDescription, message: nil, details: nil)) 347 + result(FlutterError(code: "MobileScanner", message: error.localizedDescription, details: nil))
335 } 348 }
336 } 349 }
337 350