Skip to content

Commit

Permalink
Merge pull request #965 from navaronbracke/fix_ios_camera_session_bug
Browse files Browse the repository at this point in the history
fix: Fix nil capture session crash on iOS / MacOS
  • Loading branch information
navaronbracke authored Feb 28, 2024
2 parents 91bb85b + a427f8d commit c9ab22d
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 35 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.0.1
Bugs fixed:
* [iOS] Fixed a crash with a nil capture session when starting the camera. (thanks @navaronbracke !)

## 4.0.0
BREAKING CHANGES:
* [Android] compileSdk has been upgraded to version 34.
Expand Down
4 changes: 2 additions & 2 deletions example/lib/barcode_list_scanner_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,15 @@ class _BarcodeListScannerWithControllerState
);
if (image != null) {
if (await controller.analyzeImage(image.path)) {
if (!mounted) return;
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Barcode found!'),
backgroundColor: Colors.green,
),
);
} else {
if (!mounted) return;
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('No barcode found!'),
Expand Down
4 changes: 2 additions & 2 deletions example/lib/barcode_scanner_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,15 @@ class _BarcodeScannerWithControllerState
);
if (image != null) {
if (await controller.analyzeImage(image.path)) {
if (!mounted) return;
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Barcode found!'),
backgroundColor: Colors.green,
),
);
} else {
if (!mounted) return;
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('No barcode found!'),
Expand Down
4 changes: 2 additions & 2 deletions example/lib/barcode_scanner_zoom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,15 @@ class _BarcodeScannerWithZoomState extends State<BarcodeScannerWithZoom>
);
if (image != null) {
if (await controller.analyzeImage(image.path)) {
if (!mounted) return;
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Barcode found!'),
backgroundColor: Colors.green,
),
);
} else {
if (!mounted) return;
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('No barcode found!'),
Expand Down
33 changes: 19 additions & 14 deletions ios/Classes/MobileScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ typealias ZoomScaleChangeCallback = ((Double?) -> ())

public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, FlutterTexture {
/// Capture session of the camera
var captureSession: AVCaptureSession!
var captureSession: AVCaptureSession?

/// The selected camera
var device: AVCaptureDevice!
Expand Down Expand Up @@ -173,7 +173,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
/// Start scanning for barcodes
func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws {
self.detectionSpeed = detectionSpeed
if (device != nil) {
if (device != nil || captureSession != nil) {
throw MobileScannerError.alreadyStarted
}

Expand Down Expand Up @@ -216,17 +216,17 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
device.unlockForConfiguration()
} catch {}

captureSession.beginConfiguration()
captureSession!.beginConfiguration()

// Add device input
do {
let input = try AVCaptureDeviceInput(device: device)
captureSession.addInput(input)
captureSession!.addInput(input)
} catch {
throw MobileScannerError.cameraError(error)
}

captureSession.sessionPreset = AVCaptureSession.Preset.photo
captureSession!.sessionPreset = AVCaptureSession.Preset.photo
// Add video output.
let videoOutput = AVCaptureVideoDataOutput()

Expand All @@ -237,17 +237,21 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
// calls captureOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)

captureSession.addOutput(videoOutput)
captureSession!.addOutput(videoOutput)
for connection in videoOutput.connections {
connection.videoOrientation = .portrait
if cameraPosition == .front && connection.isVideoMirroringSupported {
connection.isVideoMirrored = true
}
}
captureSession.commitConfiguration()
captureSession!.commitConfiguration()

backgroundQueue.async {
self.captureSession.startRunning()
guard let captureSession = self.captureSession else {
return
}

captureSession.startRunning()

// After the capture session started, turn on the torch (if requested)
// and reset the zoom scale back to the default.
Expand Down Expand Up @@ -299,15 +303,16 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega

/// Stop scanning for barcodes
func stop() throws {
if (device == nil) {
if (device == nil || captureSession == nil) {
throw MobileScannerError.alreadyStopped
}
captureSession.stopRunning()
for input in captureSession.inputs {
captureSession.removeInput(input)

captureSession!.stopRunning()
for input in captureSession!.inputs {
captureSession!.removeInput(input)
}
for output in captureSession.outputs {
captureSession.removeOutput(output)
for output in captureSession!.outputs {
captureSession!.removeOutput(output)
}

latestBuffer = nil
Expand Down
28 changes: 14 additions & 14 deletions macos/Classes/MobileScannerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
var textureId: Int64!

// Capture session of the camera
var captureSession: AVCaptureSession!
var captureSession: AVCaptureSession?

// The selected camera
weak var device: AVCaptureDevice!
Expand Down Expand Up @@ -239,7 +239,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
}

func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
if (device != nil) {
if (device != nil || captureSession != nil) {
result(FlutterError(code: "MobileScanner",
message: "Called start() while already started!",
details: nil))
Expand Down Expand Up @@ -289,33 +289,33 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
}

device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode), options: .new, context: nil)
captureSession.beginConfiguration()
captureSession!.beginConfiguration()

// Add device input
do {
let input = try AVCaptureDeviceInput(device: device)
captureSession.addInput(input)
captureSession!.addInput(input)
} catch {
result(FlutterError(code: "MobileScanner", message: error.localizedDescription, details: nil))
return
}
captureSession.sessionPreset = AVCaptureSession.Preset.photo
captureSession!.sessionPreset = AVCaptureSession.Preset.photo
// Add video output.
let videoOutput = AVCaptureVideoDataOutput()

videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
videoOutput.alwaysDiscardsLateVideoFrames = true

videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
captureSession.addOutput(videoOutput)
captureSession!.addOutput(videoOutput)
for connection in videoOutput.connections {
// connection.videoOrientation = .portrait
if position == .front && connection.isVideoMirroringSupported {
connection.isVideoMirrored = true
}
}
captureSession.commitConfiguration()
captureSession.startRunning()
captureSession!.commitConfiguration()
captureSession!.startRunning()
let dimensions = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription)
let size = ["width": Double(dimensions.width), "height": Double(dimensions.height)]
let answer: [String : Any?] = ["textureId": textureId, "size": size, "torchable": device.hasTorch]
Expand Down Expand Up @@ -374,17 +374,17 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
// }

func stop(_ result: FlutterResult) {
if (device == nil) {
if (device == nil || captureSession == nil) {
result(nil)

return
}
captureSession.stopRunning()
for input in captureSession.inputs {
captureSession.removeInput(input)
captureSession!.stopRunning()
for input in captureSession!.inputs {
captureSession!.removeInput(input)
}
for output in captureSession.outputs {
captureSession.removeOutput(output)
for output in captureSession!.outputs {
captureSession!.removeOutput(output)
}
device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode))
registry.unregisterTexture(textureId)
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: mobile_scanner
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.
version: 4.0.0
version: 4.0.1
repository: https://github.com/juliansteenbakker/mobile_scanner

screenshots:
Expand Down

0 comments on commit c9ab22d

Please sign in to comment.