Julian Steenbakker

ios: fix scanning, torch on boot and camera facing

@@ -53,11 +53,11 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -53,11 +53,11 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
53 case "start": 53 case "start":
54 start(call, result) 54 start(call, result)
55 case "torch": 55 case "torch":
56 - torchNative(call, result) 56 + switchTorch(call, result)
57 case "analyze": 57 case "analyze":
58 - analyzeNative(call, result) 58 + switchAnalyzeMode(call, result)
59 case "stop": 59 case "stop":
60 - stopNative(result) 60 + stop(result)
61 default: 61 default:
62 result(FlutterMethodNotImplemented) 62 result(FlutterMethodNotImplemented)
63 } 63 }
@@ -83,58 +83,59 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -83,58 +83,59 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
83 return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer) 83 return Unmanaged<CVPixelBuffer>.passRetained(latestBuffer)
84 } 84 }
85 85
86 -// public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {  
87 -//  
88 -// latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)  
89 -// registry.textureFrameAvailable(textureId)  
90 -//  
91 -// switch analyzeMode {  
92 -// case 1: // barcode  
93 -// if analyzing {  
94 -// break  
95 -// }  
96 -// analyzing = true  
97 -// let buffer = CMSampleBufferGetImageBuffer(sampleBuffer)  
98 -// let image = VisionImage(image: buffer!.image)  
99 -// image.orientation = imageOrientation(  
100 -// deviceOrientation: UIDevice.current.orientation,  
101 -// defaultOrientation: .portrait  
102 -// )  
103 -//  
104 -// let scanner = BarcodeScanner.barcodeScanner()  
105 -// scanner.process(image) { [self] barcodes, error in  
106 -// if error == nil && barcodes != nil {  
107 -// for barcode in barcodes! {  
108 -// let event: [String: Any?] = ["name": "barcode", "data": barcode.data]  
109 -// sink?(event)  
110 -// }  
111 -// }  
112 -// analyzing = false  
113 -// }  
114 -// default: // none  
115 -// break  
116 -// }  
117 -// }  
118 -  
119 -// func imageOrientation(  
120 -// deviceOrientation: UIDeviceOrientation,  
121 -// defaultOrientation: UIDeviceOrientation  
122 -// ) -> UIImage.Orientation {  
123 -// switch deviceOrientation {  
124 -// case .portrait:  
125 -// return position == .front ? .leftMirrored : .right  
126 -// case .landscapeLeft:  
127 -// return position == .front ? .downMirrored : .up  
128 -// case .portraitUpsideDown:  
129 -// return position == .front ? .rightMirrored : .left  
130 -// case .landscapeRight:  
131 -// return position == .front ? .upMirrored : .down  
132 -// case .faceDown, .faceUp, .unknown:  
133 -// return .up  
134 -// @unknown default:  
135 -// return imageOrientation(deviceOrientation: defaultOrientation, defaultOrientation: .portrait)  
136 -// }  
137 -// } 86 + // Gets called when a new image is added to the buffer
  87 + public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
  88 +
  89 + latestBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
  90 + registry.textureFrameAvailable(textureId)
  91 +
  92 + switch analyzeMode {
  93 + case 1: // barcode
  94 + if analyzing {
  95 + break
  96 + }
  97 + analyzing = true
  98 + let buffer = CMSampleBufferGetImageBuffer(sampleBuffer)
  99 + let image = VisionImage(image: buffer!.image)
  100 + image.orientation = imageOrientation(
  101 + deviceOrientation: UIDevice.current.orientation,
  102 + defaultOrientation: .portrait
  103 + )
  104 +
  105 + let scanner = BarcodeScanner.barcodeScanner()
  106 + scanner.process(image) { [self] barcodes, error in
  107 + if error == nil && barcodes != nil {
  108 + for barcode in barcodes! {
  109 + let event: [String: Any?] = ["name": "barcode", "data": barcode.data]
  110 + sink?(event)
  111 + }
  112 + }
  113 + analyzing = false
  114 + }
  115 + default: // none
  116 + break
  117 + }
  118 + }
  119 +
  120 + func imageOrientation(
  121 + deviceOrientation: UIDeviceOrientation,
  122 + defaultOrientation: UIDeviceOrientation
  123 + ) -> UIImage.Orientation {
  124 + switch deviceOrientation {
  125 + case .portrait:
  126 + return position == .front ? .leftMirrored : .right
  127 + case .landscapeLeft:
  128 + return position == .front ? .downMirrored : .up
  129 + case .portraitUpsideDown:
  130 + return position == .front ? .rightMirrored : .left
  131 + case .landscapeRight:
  132 + return position == .front ? .upMirrored : .down
  133 + case .faceDown, .faceUp, .unknown:
  134 + return .up
  135 + @unknown default:
  136 + return imageOrientation(deviceOrientation: defaultOrientation, defaultOrientation: .portrait)
  137 + }
  138 + }
138 139
139 func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 140 func checkPermission(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
140 let status = AVCaptureDevice.authorizationStatus(for: .video) 141 let status = AVCaptureDevice.authorizationStatus(for: .video)
@@ -158,22 +159,35 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -158,22 +159,35 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
158 159
159 let argReader = MapArgumentReader(call.arguments as? [String: Any]) 160 let argReader = MapArgumentReader(call.arguments as? [String: Any])
160 161
161 - guard let ratio = argReader.int(key: "ratio"),  
162 - let torch = argReader.int(key: "torch"),  
163 - let facing = argReader.int(key: "facing") else {  
164 - result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing a required argument", details: "Expecting targetWidth, targetHeight, formats, and optionally heartbeatTimeout"))  
165 - return  
166 - } 162 +// let ratio: Int = argReader.int(key: "ratio")
  163 + let torch: Bool = argReader.bool(key: "torch") ?? false
  164 + let facing: Int = argReader.int(key: "facing") ?? 1
167 165
  166 + // Set the camera to use
168 position = facing == 0 ? AVCaptureDevice.Position.front : .back 167 position = facing == 0 ? AVCaptureDevice.Position.front : .back
  168 +
  169 + // Open the camera device
169 if #available(iOS 10.0, *) { 170 if #available(iOS 10.0, *) {
170 device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position).devices.first 171 device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position).devices.first
171 } else { 172 } else {
172 device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first 173 device = AVCaptureDevice.devices(for: .video).filter({$0.position == position}).first
173 } 174 }
  175 +
  176 + // Enable the torch if parameter is set and torch is available
  177 + if (device.hasTorch && device.isTorchAvailable) {
  178 + do {
  179 + try device.lockForConfiguration()
  180 + device.torchMode = torch ? .on : .off
  181 + device.unlockForConfiguration()
  182 + } catch {
  183 + error.throwNative(result)
  184 + }
  185 + }
  186 +
174 device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode), options: .new, context: nil) 187 device.addObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode), options: .new, context: nil)
175 captureSession.beginConfiguration() 188 captureSession.beginConfiguration()
176 - // Add device input. 189 +
  190 + // Add device input
177 do { 191 do {
178 let input = try AVCaptureDeviceInput(device: device) 192 let input = try AVCaptureDeviceInput(device: device)
179 captureSession.addInput(input) 193 captureSession.addInput(input)
@@ -205,7 +219,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -205,7 +219,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
205 result(answer) 219 result(answer)
206 } 220 }
207 221
208 - func torchNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 222 + func switchTorch(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
209 do { 223 do {
210 try device.lockForConfiguration() 224 try device.lockForConfiguration()
211 device.torchMode = call.arguments as! Int == 1 ? .on : .off 225 device.torchMode = call.arguments as! Int == 1 ? .on : .off
@@ -216,12 +230,12 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -216,12 +230,12 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
216 } 230 }
217 } 231 }
218 232
219 - func analyzeNative(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 233 + func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
220 analyzeMode = call.arguments as! Int 234 analyzeMode = call.arguments as! Int
221 result(nil) 235 result(nil)
222 } 236 }
223 237
224 - func stopNative(_ result: FlutterResult) { 238 + func stop(_ result: FlutterResult) {
225 captureSession.stopRunning() 239 captureSession.stopRunning()
226 for input in captureSession.inputs { 240 for input in captureSession.inputs {
227 captureSession.removeInput(input) 241 captureSession.removeInput(input)
@@ -241,6 +255,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan @@ -241,6 +255,7 @@ public class SwiftMobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHan
241 result(nil) 255 result(nil)
242 } 256 }
243 257
  258 + // Observer for torch state
244 public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 259 public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
245 switch keyPath { 260 switch keyPath {
246 case "torchMode": 261 case "torchMode":
@@ -270,6 +285,10 @@ class MapArgumentReader { @@ -270,6 +285,10 @@ class MapArgumentReader {
270 return (args?[key] as? NSNumber)?.intValue 285 return (args?[key] as? NSNumber)?.intValue
271 } 286 }
272 287
  288 + func bool(key: String) -> Bool? {
  289 + return (args?[key] as? NSNumber)?.boolValue
  290 + }
  291 +
273 func stringArray(key: String) -> [String]? { 292 func stringArray(key: String) -> [String]? {
274 return args?[key] as? [String] 293 return args?[key] as? [String]
275 } 294 }