Navaron Bracke

fix nil capture session bug

  1 +## 4.0.1
  2 +Bugs fixed:
  3 +* [iOS] Fixed a crash with a nil capture session when starting the camera. (thanks @navaronbracke !)
  4 +
1 ## 4.0.0 5 ## 4.0.0
2 BREAKING CHANGES: 6 BREAKING CHANGES:
3 * [Android] compileSdk has been upgraded to version 34. 7 * [Android] compileSdk has been upgraded to version 34.
@@ -17,7 +17,7 @@ typealias ZoomScaleChangeCallback = ((Double?) -> ()) @@ -17,7 +17,7 @@ typealias ZoomScaleChangeCallback = ((Double?) -> ())
17 17
18 public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, FlutterTexture { 18 public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, FlutterTexture {
19 /// Capture session of the camera 19 /// Capture session of the camera
20 - var captureSession: AVCaptureSession! 20 + var captureSession: AVCaptureSession?
21 21
22 /// The selected camera 22 /// The selected camera
23 var device: AVCaptureDevice! 23 var device: AVCaptureDevice!
@@ -173,7 +173,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -173,7 +173,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
173 /// Start scanning for barcodes 173 /// Start scanning for barcodes
174 func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws { 174 func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws {
175 self.detectionSpeed = detectionSpeed 175 self.detectionSpeed = detectionSpeed
176 - if (device != nil) { 176 + if (device != nil || captureSession != nil) {
177 throw MobileScannerError.alreadyStarted 177 throw MobileScannerError.alreadyStarted
178 } 178 }
179 179
@@ -216,17 +216,17 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -216,17 +216,17 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
216 device.unlockForConfiguration() 216 device.unlockForConfiguration()
217 } catch {} 217 } catch {}
218 218
219 - captureSession.beginConfiguration() 219 + captureSession!.beginConfiguration()
220 220
221 // Add device input 221 // Add device input
222 do { 222 do {
223 let input = try AVCaptureDeviceInput(device: device) 223 let input = try AVCaptureDeviceInput(device: device)
224 - captureSession.addInput(input) 224 + captureSession!.addInput(input)
225 } catch { 225 } catch {
226 throw MobileScannerError.cameraError(error) 226 throw MobileScannerError.cameraError(error)
227 } 227 }
228 228
229 - captureSession.sessionPreset = AVCaptureSession.Preset.photo 229 + captureSession!.sessionPreset = AVCaptureSession.Preset.photo
230 // Add video output. 230 // Add video output.
231 let videoOutput = AVCaptureVideoDataOutput() 231 let videoOutput = AVCaptureVideoDataOutput()
232 232
@@ -237,17 +237,21 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -237,17 +237,21 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
237 // calls captureOutput() 237 // calls captureOutput()
238 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) 238 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
239 239
240 - captureSession.addOutput(videoOutput) 240 + captureSession!.addOutput(videoOutput)
241 for connection in videoOutput.connections { 241 for connection in videoOutput.connections {
242 connection.videoOrientation = .portrait 242 connection.videoOrientation = .portrait
243 if cameraPosition == .front && connection.isVideoMirroringSupported { 243 if cameraPosition == .front && connection.isVideoMirroringSupported {
244 connection.isVideoMirrored = true 244 connection.isVideoMirrored = true
245 } 245 }
246 } 246 }
247 - captureSession.commitConfiguration() 247 + captureSession!.commitConfiguration()
248 248
249 backgroundQueue.async { 249 backgroundQueue.async {
250 - self.captureSession.startRunning() 250 + guard let captureSession = self.captureSession else {
  251 + return
  252 + }
  253 +
  254 + captureSession.startRunning()
251 255
252 // After the capture session started, turn on the torch (if requested) 256 // After the capture session started, turn on the torch (if requested)
253 // and reset the zoom scale back to the default. 257 // and reset the zoom scale back to the default.
@@ -299,15 +303,16 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega @@ -299,15 +303,16 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
299 303
300 /// Stop scanning for barcodes 304 /// Stop scanning for barcodes
301 func stop() throws { 305 func stop() throws {
302 - if (device == nil) { 306 + if (device == nil || captureSession == nil) {
303 throw MobileScannerError.alreadyStopped 307 throw MobileScannerError.alreadyStopped
304 } 308 }
305 - captureSession.stopRunning()  
306 - for input in captureSession.inputs {  
307 - captureSession.removeInput(input) 309 +
  310 + captureSession!.stopRunning()
  311 + for input in captureSession!.inputs {
  312 + captureSession!.removeInput(input)
308 } 313 }
309 - for output in captureSession.outputs {  
310 - captureSession.removeOutput(output) 314 + for output in captureSession!.outputs {
  315 + captureSession!.removeOutput(output)
311 } 316 }
312 317
313 latestBuffer = nil 318 latestBuffer = nil
@@ -15,7 +15,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -15,7 +15,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
15 var textureId: Int64! 15 var textureId: Int64!
16 16
17 // Capture session of the camera 17 // Capture session of the camera
18 - var captureSession: AVCaptureSession! 18 + var captureSession: AVCaptureSession?
19 19
20 // The selected camera 20 // The selected camera
21 weak var device: AVCaptureDevice! 21 weak var device: AVCaptureDevice!
@@ -239,7 +239,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -239,7 +239,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
239 } 239 }
240 240
241 func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 241 func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
242 - if (device != nil) { 242 + if (device != nil || captureSession != nil) {
243 result(FlutterError(code: "MobileScanner", 243 result(FlutterError(code: "MobileScanner",
244 message: "Called start() while already started!", 244 message: "Called start() while already started!",
245 details: nil)) 245 details: nil))
@@ -289,17 +289,17 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -289,17 +289,17 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
289 } 289 }
290 290
291 device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode), options: .new, context: nil) 291 device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode), options: .new, context: nil)
292 - captureSession.beginConfiguration() 292 + captureSession!.beginConfiguration()
293 293
294 // Add device input 294 // Add device input
295 do { 295 do {
296 let input = try AVCaptureDeviceInput(device: device) 296 let input = try AVCaptureDeviceInput(device: device)
297 - captureSession.addInput(input) 297 + captureSession!.addInput(input)
298 } catch { 298 } catch {
299 result(FlutterError(code: "MobileScanner", message: error.localizedDescription, details: nil)) 299 result(FlutterError(code: "MobileScanner", message: error.localizedDescription, details: nil))
300 return 300 return
301 } 301 }
302 - captureSession.sessionPreset = AVCaptureSession.Preset.photo 302 + captureSession!.sessionPreset = AVCaptureSession.Preset.photo
303 // Add video output. 303 // Add video output.
304 let videoOutput = AVCaptureVideoDataOutput() 304 let videoOutput = AVCaptureVideoDataOutput()
305 305
@@ -307,15 +307,15 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -307,15 +307,15 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
307 videoOutput.alwaysDiscardsLateVideoFrames = true 307 videoOutput.alwaysDiscardsLateVideoFrames = true
308 308
309 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) 309 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
310 - captureSession.addOutput(videoOutput) 310 + captureSession!.addOutput(videoOutput)
311 for connection in videoOutput.connections { 311 for connection in videoOutput.connections {
312 // connection.videoOrientation = .portrait 312 // connection.videoOrientation = .portrait
313 if position == .front && connection.isVideoMirroringSupported { 313 if position == .front && connection.isVideoMirroringSupported {
314 connection.isVideoMirrored = true 314 connection.isVideoMirrored = true
315 } 315 }
316 } 316 }
317 - captureSession.commitConfiguration()  
318 - captureSession.startRunning() 317 + captureSession!.commitConfiguration()
  318 + captureSession!.startRunning()
319 let dimensions = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription) 319 let dimensions = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription)
320 let size = ["width": Double(dimensions.width), "height": Double(dimensions.height)] 320 let size = ["width": Double(dimensions.width), "height": Double(dimensions.height)]
321 let answer: [String : Any?] = ["textureId": textureId, "size": size, "torchable": device.hasTorch] 321 let answer: [String : Any?] = ["textureId": textureId, "size": size, "torchable": device.hasTorch]
@@ -374,17 +374,17 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -374,17 +374,17 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
374 // } 374 // }
375 375
376 func stop(_ result: FlutterResult) { 376 func stop(_ result: FlutterResult) {
377 - if (device == nil) { 377 + if (device == nil || captureSession == nil) {
378 result(nil) 378 result(nil)
379 379
380 return 380 return
381 } 381 }
382 - captureSession.stopRunning()  
383 - for input in captureSession.inputs {  
384 - captureSession.removeInput(input) 382 + captureSession!.stopRunning()
  383 + for input in captureSession!.inputs {
  384 + captureSession!.removeInput(input)
385 } 385 }
386 - for output in captureSession.outputs {  
387 - captureSession.removeOutput(output) 386 + for output in captureSession!.outputs {
  387 + captureSession!.removeOutput(output)
388 } 388 }
389 device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode)) 389 device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
390 registry.unregisterTexture(textureId) 390 registry.unregisterTexture(textureId)
1 name: mobile_scanner 1 name: mobile_scanner
2 description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS. 2 description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS.
3 -version: 4.0.0 3 +version: 4.0.1
4 repository: https://github.com/juliansteenbakker/mobile_scanner 4 repository: https://github.com/juliansteenbakker/mobile_scanner
5 5
6 screenshots: 6 screenshots: