Committed by
GitHub
Merge pull request #965 from navaronbracke/fix_ios_camera_session_bug
fix: Fix nil capture session crash on iOS / MacOS
Showing
7 changed files
with
44 additions
and
35 deletions
| 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. |
| @@ -154,7 +154,7 @@ class _BarcodeListScannerWithControllerState | @@ -154,7 +154,7 @@ class _BarcodeListScannerWithControllerState | ||
| 154 | ); | 154 | ); |
| 155 | if (image != null) { | 155 | if (image != null) { |
| 156 | if (await controller.analyzeImage(image.path)) { | 156 | if (await controller.analyzeImage(image.path)) { |
| 157 | - if (!mounted) return; | 157 | + if (!context.mounted) return; |
| 158 | ScaffoldMessenger.of(context).showSnackBar( | 158 | ScaffoldMessenger.of(context).showSnackBar( |
| 159 | const SnackBar( | 159 | const SnackBar( |
| 160 | content: Text('Barcode found!'), | 160 | content: Text('Barcode found!'), |
| @@ -162,7 +162,7 @@ class _BarcodeListScannerWithControllerState | @@ -162,7 +162,7 @@ class _BarcodeListScannerWithControllerState | ||
| 162 | ), | 162 | ), |
| 163 | ); | 163 | ); |
| 164 | } else { | 164 | } else { |
| 165 | - if (!mounted) return; | 165 | + if (!context.mounted) return; |
| 166 | ScaffoldMessenger.of(context).showSnackBar( | 166 | ScaffoldMessenger.of(context).showSnackBar( |
| 167 | const SnackBar( | 167 | const SnackBar( |
| 168 | content: Text('No barcode found!'), | 168 | content: Text('No barcode found!'), |
| @@ -170,7 +170,7 @@ class _BarcodeScannerWithControllerState | @@ -170,7 +170,7 @@ class _BarcodeScannerWithControllerState | ||
| 170 | ); | 170 | ); |
| 171 | if (image != null) { | 171 | if (image != null) { |
| 172 | if (await controller.analyzeImage(image.path)) { | 172 | if (await controller.analyzeImage(image.path)) { |
| 173 | - if (!mounted) return; | 173 | + if (!context.mounted) return; |
| 174 | ScaffoldMessenger.of(context).showSnackBar( | 174 | ScaffoldMessenger.of(context).showSnackBar( |
| 175 | const SnackBar( | 175 | const SnackBar( |
| 176 | content: Text('Barcode found!'), | 176 | content: Text('Barcode found!'), |
| @@ -178,7 +178,7 @@ class _BarcodeScannerWithControllerState | @@ -178,7 +178,7 @@ class _BarcodeScannerWithControllerState | ||
| 178 | ), | 178 | ), |
| 179 | ); | 179 | ); |
| 180 | } else { | 180 | } else { |
| 181 | - if (!mounted) return; | 181 | + if (!context.mounted) return; |
| 182 | ScaffoldMessenger.of(context).showSnackBar( | 182 | ScaffoldMessenger.of(context).showSnackBar( |
| 183 | const SnackBar( | 183 | const SnackBar( |
| 184 | content: Text('No barcode found!'), | 184 | content: Text('No barcode found!'), |
| @@ -171,7 +171,7 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom> | @@ -171,7 +171,7 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom> | ||
| 171 | ); | 171 | ); |
| 172 | if (image != null) { | 172 | if (image != null) { |
| 173 | if (await controller.analyzeImage(image.path)) { | 173 | if (await controller.analyzeImage(image.path)) { |
| 174 | - if (!mounted) return; | 174 | + if (!context.mounted) return; |
| 175 | ScaffoldMessenger.of(context).showSnackBar( | 175 | ScaffoldMessenger.of(context).showSnackBar( |
| 176 | const SnackBar( | 176 | const SnackBar( |
| 177 | content: Text('Barcode found!'), | 177 | content: Text('Barcode found!'), |
| @@ -179,7 +179,7 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom> | @@ -179,7 +179,7 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom> | ||
| 179 | ), | 179 | ), |
| 180 | ); | 180 | ); |
| 181 | } else { | 181 | } else { |
| 182 | - if (!mounted) return; | 182 | + if (!context.mounted) return; |
| 183 | ScaffoldMessenger.of(context).showSnackBar( | 183 | ScaffoldMessenger.of(context).showSnackBar( |
| 184 | const SnackBar( | 184 | const SnackBar( |
| 185 | content: Text('No barcode found!'), | 185 | content: Text('No barcode found!'), |
| @@ -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: |
-
Please register or login to post a comment