@biso_gmbh/capacitor-plugin-ml-kit-barcode-scanner 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CapacitorPluginMlKitBarcodeScanner.podspec +20 -0
  2. package/README.md +231 -0
  3. package/android/build.gradle +65 -0
  4. package/android/src/main/AndroidManifest.xml +8 -0
  5. package/android/src/main/assets/beep.mp3 +0 -0
  6. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/BarcodeAnalyzer.java +119 -0
  7. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/BarcodeFormat.java +40 -0
  8. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/BarcodeFormats.java +38 -0
  9. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/BarcodeType.java +37 -0
  10. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/BarcodesListener.java +8 -0
  11. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/CameraOverlay.java +161 -0
  12. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/CaptureActivity.java +164 -0
  13. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/DetectedBarcode.java +174 -0
  14. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/MlKitBarcodeScannerPlugin.java +142 -0
  15. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/ScannerSettings.java +348 -0
  16. package/android/src/main/java/com/biso/capacitor/plugins/mlkit/barcode/scanner/Utils.java +118 -0
  17. package/android/src/main/res/.gitkeep +0 -0
  18. package/android/src/main/res/drawable/torch_active.xml +12 -0
  19. package/android/src/main/res/drawable/torch_inactive.xml +12 -0
  20. package/android/src/main/res/drawable-mdpi/flashlight.png +0 -0
  21. package/android/src/main/res/layout/capture_activity.xml +28 -0
  22. package/dist/docs.json +279 -0
  23. package/dist/esm/definitions.d.ts +72 -0
  24. package/dist/esm/definitions.js +2 -0
  25. package/dist/esm/definitions.js.map +1 -0
  26. package/dist/esm/index.d.ts +4 -0
  27. package/dist/esm/index.js +7 -0
  28. package/dist/esm/index.js.map +1 -0
  29. package/dist/esm/web.d.ts +5 -0
  30. package/dist/esm/web.js +8 -0
  31. package/dist/esm/web.js.map +1 -0
  32. package/dist/plugin.cjs.js +22 -0
  33. package/dist/plugin.cjs.js.map +1 -0
  34. package/dist/plugin.js +25 -0
  35. package/dist/plugin.js.map +1 -0
  36. package/ios/Plugin/Assets.xcassets/Contents.json +6 -0
  37. package/ios/Plugin/Assets.xcassets/beep.dataset/Contents.json +12 -0
  38. package/ios/Plugin/Assets.xcassets/beep.dataset/beep.mp3 +0 -0
  39. package/ios/Plugin/Assets.xcassets/flashlight.imageset/Contents.json +12 -0
  40. package/ios/Plugin/Assets.xcassets/flashlight.imageset/flashlight.png +0 -0
  41. package/ios/Plugin/BarcodeFormat.swift +45 -0
  42. package/ios/Plugin/BarcodeFormats.swift +26 -0
  43. package/ios/Plugin/BarcodeType.swift +45 -0
  44. package/ios/Plugin/BarcodesAnalyzer.swift +79 -0
  45. package/ios/Plugin/CameraOverlay.swift +119 -0
  46. package/ios/Plugin/CameraViewController.swift +301 -0
  47. package/ios/Plugin/DetectedBarcode.swift +70 -0
  48. package/ios/Plugin/Info.plist +24 -0
  49. package/ios/Plugin/MlKitBarcodeScannerPlugin.h +10 -0
  50. package/ios/Plugin/MlKitBarcodeScannerPlugin.m +8 -0
  51. package/ios/Plugin/MlKitBarcodeScannerPlugin.swift +69 -0
  52. package/ios/Plugin/PrivacyInfo.xcprivacy +27 -0
  53. package/ios/Plugin/ScannerSettings.swift +171 -0
  54. package/ios/Plugin/Utils.swift +145 -0
  55. package/package.json +84 -0
@@ -0,0 +1,79 @@
1
+ import MLKitBarcodeScanning
2
+ import MLKitVision
3
+
4
+ protocol BarcodesListener: NSObjectProtocol {
5
+ func onBarcodesFound(_ barcodes: [DetectedBarcode])
6
+ }
7
+
8
+ class BarcodeAnalyzer {
9
+ private var scanner: BarcodeScanner
10
+ private var cameraOverlay: CameraOverlay
11
+ private var barcodesListener: BarcodesListener
12
+ private var settings: ScannerSettings
13
+
14
+ private var lastBarcodes: [DetectedBarcode] = []
15
+ private var stableCounter: Int = 0
16
+
17
+ init(settings: ScannerSettings, barcodesListener: BarcodesListener, cameraOverlay:CameraOverlay) {
18
+ let barcodeFormats = MLKitBarcodeScanning.BarcodeFormat(rawValue: settings.barcodeFormats)
19
+ scanner = BarcodeScanner.barcodeScanner(options: BarcodeScannerOptions(formats: barcodeFormats))
20
+ self.cameraOverlay = cameraOverlay
21
+ self.barcodesListener = barcodesListener
22
+ self.settings = settings
23
+ }
24
+
25
+ func analyze(in image: VisionImage, width: CGFloat, height: CGFloat) {
26
+ var barcodes: [Barcode] = []
27
+ do {
28
+ barcodes = try scanner.results(in: image)
29
+ } catch let error {
30
+ print(error.localizedDescription)
31
+ }
32
+
33
+ var detectedBarcodes: [DetectedBarcode] = []
34
+ for barcode in barcodes {
35
+ let normalizedRect = CGRect(
36
+ x: barcode.frame.origin.x / width,
37
+ y: barcode.frame.origin.y / height,
38
+ width: barcode.frame.size.width / width,
39
+ height: barcode.frame.size.height / height
40
+ )
41
+ let convertedRect = cameraOverlay.previewLayer.layerRectConverted(fromMetadataOutputRect: normalizedRect)
42
+
43
+ detectedBarcodes.append(DetectedBarcode(barcode: barcode, bounds: convertedRect, centerX: cameraOverlay.previewLayer.bounds.midX, centerY: cameraOverlay.previewLayer.bounds.midY))
44
+ }
45
+
46
+ if (settings.debugOverlay) {
47
+ cameraOverlay.drawDebugOverlay(barcodes: detectedBarcodes)
48
+ }
49
+
50
+ if (areBarcodesStable(barcodes: detectedBarcodes) && stableCounter >= settings.stableThreshold) {
51
+ var barcodesInScanArea: [DetectedBarcode] = []
52
+ for barcode in detectedBarcodes {
53
+ if (barcode.isInScanArea(scanArea: cameraOverlay.scanArea, ignoreRotated: settings.ignoreRotatedBarcodes)) {
54
+ barcodesInScanArea.append(barcode)
55
+ }
56
+ }
57
+ barcodesInScanArea.sort {
58
+ $0.distanceToCenter < $1.distanceToCenter
59
+ }
60
+ if (!barcodesInScanArea.isEmpty) {
61
+ barcodesListener.onBarcodesFound(barcodesInScanArea)
62
+ }
63
+ }
64
+ }
65
+
66
+ private func areBarcodesStable(barcodes: [DetectedBarcode]) -> Bool {
67
+ let barcodesSet = Set(barcodes)
68
+ let lastBarcodesSet = Set(lastBarcodes)
69
+ let differences = barcodesSet.subtracting(lastBarcodesSet)
70
+ if (!barcodes.isEmpty && differences.isEmpty) {
71
+ stableCounter += 1
72
+ print("barcodes stable for \(stableCounter)/\(settings.stableThreshold)")
73
+ return true
74
+ }
75
+ stableCounter = 0
76
+ lastBarcodes = barcodes
77
+ return false
78
+ }
79
+ }
@@ -0,0 +1,119 @@
1
+ import AVFoundation
2
+ import CoreVideo
3
+ import UIKit
4
+
5
+ class CameraOverlay: UIView {
6
+ private var lastFrame: CMSampleBuffer?
7
+ public private(set) var scanArea: CGRect
8
+ private var settings: ScannerSettings
9
+ public private(set) var previewLayer: AVCaptureVideoPreviewLayer!
10
+
11
+ init(settings: ScannerSettings, parentView: UIView) {
12
+
13
+ self.scanArea = Utils.calculateCGRect(height: parentView.bounds.height, width: parentView.bounds.width, scaleFactor: settings.detectorSize, aspectRatio: settings.aspectRatioF)
14
+ self.settings = settings
15
+
16
+ super.init(frame: .zero)
17
+
18
+ parentView.addSubview(self)
19
+ NSLayoutConstraint.activate([
20
+ self.topAnchor.constraint(equalTo: parentView.topAnchor),
21
+ self.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
22
+ self.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
23
+ self.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
24
+ ])
25
+
26
+ self.translatesAutoresizingMaskIntoConstraints = false
27
+ self.contentMode = UIView.ContentMode.scaleAspectFill
28
+ self.isOpaque = false
29
+ self.backgroundColor = UIColor.clear
30
+ }
31
+
32
+ required init?(coder: NSCoder) {
33
+ fatalError("init(coder:) has not been implemented")
34
+ }
35
+
36
+ override func layoutSubviews() {
37
+ super.layoutSubviews()
38
+ self.setNeedsDisplay()
39
+ }
40
+
41
+ override func draw(_ rect: CGRect) {
42
+ super.draw(rect)
43
+ self.scanArea = Utils.calculateCGRect(height: bounds.height, width: bounds.width, scaleFactor: settings.detectorSize, aspectRatio: settings.aspectRatioF)
44
+ if let context = UIGraphicsGetCurrentContext() {
45
+ drawScanArea(context: context)
46
+ }
47
+ }
48
+
49
+ public func setPreviewLayer(_ previewLayer: AVCaptureVideoPreviewLayer) {
50
+ self.previewLayer = previewLayer
51
+ }
52
+
53
+ public func drawDebugOverlay(barcodes: [DetectedBarcode]) {
54
+ weak var weakSelf = self
55
+ DispatchQueue.main.sync {
56
+ guard weakSelf != nil else {
57
+ return
58
+ }
59
+ for annotationView in self.subviews {
60
+ annotationView.removeFromSuperview()
61
+ }
62
+ for barcode in barcodes {
63
+ let rectangleView = UIView(frame: barcode.bounds)
64
+ rectangleView.layer.borderColor = UIColor.blue.cgColor
65
+ rectangleView.layer.borderWidth = 2
66
+ addSubview(rectangleView)
67
+
68
+ let lineView = UIView(frame: barcode.getCenterLine())
69
+
70
+ lineView.layer.borderColor = UIColor.red.cgColor
71
+ lineView.layer.borderWidth = 2
72
+ addSubview(lineView)
73
+ }
74
+ }
75
+ }
76
+
77
+ private func drawScanArea(context: CGContext) {
78
+ if (settings.drawFocusBackground){
79
+ drawFocusBackground(context: context, color: settings.focusBackgroundUIColor, radius: settings.focusRectBorderRadius)
80
+ }
81
+ if (settings.drawFocusLine) {
82
+ drawFocusLine(context: context, color: settings.focusLineUIColor, thickness: settings.focusLineThickness)
83
+ }
84
+ if (settings.drawFocusRect) {
85
+ drawScanAreaOutline(context: context, color: settings.focusRectUIColor, thickness: settings.focusRectBorderThickness, radius: settings.focusRectBorderRadius)
86
+ }
87
+ }
88
+
89
+ private func drawFocusLine(context: CGContext, color: UIColor, thickness: Int) {
90
+ context.setLineWidth(CGFloat(thickness))
91
+ context.setStrokeColor(color.cgColor)
92
+ context.beginPath()
93
+ context.move(to: CGPointMake(scanArea.minX, CGFloat(bounds.height/2)))
94
+ context.addLine(to: CGPointMake(scanArea.maxX, CGFloat(bounds.height/2)))
95
+ context.strokePath()
96
+ context.saveGState()
97
+ }
98
+
99
+ private func drawScanAreaOutline(context: CGContext, color: UIColor, thickness: Int, radius: Int) {
100
+ let rounded = UIBezierPath(roundedRect: scanArea, cornerRadius: CGFloat(radius))
101
+ context.addPath(rounded.cgPath)
102
+ context.setLineWidth(CGFloat(thickness))
103
+ context.setStrokeColor(color.cgColor)
104
+ context.strokePath()
105
+ context.saveGState()
106
+ }
107
+
108
+ private func drawFocusBackground(context: CGContext, color: UIColor, radius: Int) {
109
+ let rounded = UIBezierPath(roundedRect: scanArea, cornerRadius: CGFloat(radius))
110
+ color.setFill()
111
+ context.fill(bounds)
112
+ context.saveGState()
113
+ context.setBlendMode(.destinationOut)
114
+ context.addPath(rounded.cgPath)
115
+ context.fillPath()
116
+ context.saveGState()
117
+ context.setBlendMode(.normal)
118
+ }
119
+ }
@@ -0,0 +1,301 @@
1
+ import UIKit
2
+ import SwiftUI
3
+ import AVFoundation
4
+ import MLImage
5
+ import MLKitVision
6
+
7
+ protocol CameraViewControllerDelegate: NSObjectProtocol {
8
+ func onComplete(_ result: [DetectedBarcode])
9
+ func onError(_ error: String)
10
+ }
11
+
12
+ class CameraViewController: UIViewController, BarcodesListener {
13
+ weak var delegate: CameraViewControllerDelegate?
14
+ private let captureSession = AVCaptureSession()
15
+ private let sessionQueue = DispatchQueue(label: "sessionQueue")
16
+ private var previewLayer = AVCaptureVideoPreviewLayer()
17
+ private var settings: ScannerSettings
18
+ private var cameraOverlay: CameraOverlay!
19
+ private var barcodeAnalyzer : BarcodeAnalyzer!
20
+ private var torchButton: UIButton?
21
+ private var finishedAlready: Bool = false // ensure we only actually finish once
22
+
23
+ func onBarcodesFound(_ barcodes: [DetectedBarcode]) {
24
+ finishWithResult(barcodes)
25
+ }
26
+
27
+ init(settings: ScannerSettings) {
28
+ self.settings = settings
29
+ super.init(nibName: nil, bundle: nil)
30
+ }
31
+
32
+ required init?(coder: NSCoder) {
33
+ self.settings = ScannerSettings()
34
+ super.init(coder: coder)
35
+ }
36
+
37
+ override func viewDidLoad() {
38
+ cameraOverlay = CameraOverlay(settings: settings, parentView: view)
39
+ barcodeAnalyzer = BarcodeAnalyzer(settings: settings, barcodesListener: self, cameraOverlay: cameraOverlay)
40
+
41
+ if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
42
+ setupCaptureSession()
43
+ setupUI()
44
+ setOrientation()
45
+ } else {
46
+ AVCaptureDevice.requestAccess(for: .video, completionHandler: { (authorized) in
47
+ DispatchQueue.main.async {
48
+ if authorized {
49
+ self.setupCaptureSession()
50
+ self.setupUI()
51
+ self.setOrientation()
52
+ } else {
53
+ self.finishWithError("NO_CAMERA_PERMISSION")
54
+ }
55
+ }
56
+ })
57
+ }
58
+ }
59
+
60
+ override func viewWillAppear(_ animated: Bool) {
61
+ startSession()
62
+ // slight delay so the camera has time to load before we show the modal
63
+ while (!captureSession.isRunning) {
64
+ usleep(100)
65
+ }
66
+ usleep(150000)
67
+ }
68
+
69
+ override func viewDidDisappear(_ animated: Bool) {
70
+ stopSession()
71
+ }
72
+
73
+ override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
74
+ super.viewWillTransition(to: size, with: coordinator)
75
+ setOrientation()
76
+ }
77
+
78
+ private func setOrientation() {
79
+ previewLayer.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
80
+
81
+ if #available(iOS 17.0, *) {
82
+ switch UIDevice.current.orientation {
83
+ case UIDeviceOrientation.portraitUpsideDown:
84
+ previewLayer.connection?.videoRotationAngle = 270
85
+ case UIDeviceOrientation.landscapeLeft:
86
+ previewLayer.connection?.videoRotationAngle = 0
87
+ case UIDeviceOrientation.landscapeRight:
88
+ previewLayer.connection?.videoRotationAngle = 180
89
+ case UIDeviceOrientation.portrait:
90
+ previewLayer.connection?.videoRotationAngle = 90
91
+ default:
92
+ break
93
+ }
94
+ } else {
95
+ switch UIDevice.current.orientation {
96
+ case UIDeviceOrientation.portraitUpsideDown:
97
+ previewLayer.connection?.videoOrientation = .portraitUpsideDown
98
+ case UIDeviceOrientation.landscapeLeft:
99
+ previewLayer.connection?.videoOrientation = .landscapeRight
100
+ case UIDeviceOrientation.landscapeRight:
101
+ previewLayer.connection?.videoOrientation = .landscapeLeft
102
+ case UIDeviceOrientation.portrait:
103
+ previewLayer.connection?.videoOrientation = .portrait
104
+ default:
105
+ break
106
+ }
107
+ }
108
+ }
109
+
110
+ private func setupCaptureSession() {
111
+ guard let videoDevice = captureDevice() else {
112
+ finishWithError("NO_CAMERA")
113
+ return
114
+
115
+ }
116
+ guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else {
117
+ finishWithError("NO_CAMERA")
118
+ return
119
+
120
+ }
121
+ guard captureSession.canAddInput(videoDeviceInput) else { return }
122
+
123
+ captureSession.addInput(videoDeviceInput)
124
+
125
+ previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
126
+ previewLayer.backgroundColor = UIColor.black.cgColor
127
+ previewLayer.frame = view.bounds
128
+ previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
129
+
130
+ if #available(iOS 17.0, *) {
131
+ previewLayer.connection?.videoRotationAngle = 90
132
+ } else {
133
+ previewLayer.connection?.videoOrientation = .portrait
134
+ }
135
+
136
+ let output = AVCaptureVideoDataOutput()
137
+ output.alwaysDiscardsLateVideoFrames = true
138
+ output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "sampleQueue"))
139
+
140
+ captureSession.addOutput(output)
141
+ }
142
+
143
+ private func setupUI() {
144
+ view.layer.addSublayer(previewLayer)
145
+ cameraOverlay.setPreviewLayer(previewLayer)
146
+ view.bringSubviewToFront(cameraOverlay)
147
+
148
+ torchButton = createTorchButton()
149
+ }
150
+
151
+ private func createTorchButton() -> UIButton? {
152
+ if let device = AVCaptureDevice.default(for: AVMediaType.video) {
153
+ if device.hasTorch {
154
+ var conf = UIButton.Configuration.filled()
155
+ conf.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
156
+ conf.background.cornerRadius = 25
157
+ conf.baseForegroundColor = UIColor.black
158
+ conf.baseBackgroundColor = UIColor.white
159
+
160
+ if let image = UIImage(named: "flashlight")
161
+ {
162
+ conf.image = image.scalePreservingAspectRatio(targetSize: CGSize(width: 30, height: 30))
163
+ }
164
+ let torchButton = UIButton(configuration: conf)
165
+ torchButton.alpha = 0.5
166
+ torchButton.addTarget(self, action: #selector(toggleFlash), for: UIControl.Event.touchUpInside)
167
+
168
+ view.addSubview(torchButton)
169
+ torchButton.translatesAutoresizingMaskIntoConstraints = false
170
+ NSLayoutConstraint.activate([
171
+ torchButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
172
+ torchButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20),
173
+ torchButton.widthAnchor.constraint(equalToConstant: 50),
174
+ torchButton.heightAnchor.constraint(equalToConstant: 50)
175
+ ])
176
+ return torchButton
177
+ }
178
+ }
179
+ return nil
180
+ }
181
+
182
+ @objc
183
+ private func toggleFlash() {
184
+ guard let device = AVCaptureDevice.default(for: AVMediaType.video) else { return }
185
+ guard device.hasTorch else { print("Torch isn't available"); return }
186
+
187
+ do {
188
+ try device.lockForConfiguration()
189
+
190
+ if (device.torchMode == AVCaptureDevice.TorchMode.on) {
191
+ device.torchMode = AVCaptureDevice.TorchMode.off
192
+ torchButton?.alpha = 0.5
193
+ } else {
194
+ do {
195
+ try device.setTorchModeOn(level: 1.0)
196
+ torchButton?.alpha = 1
197
+ } catch {
198
+ print(error)
199
+ }
200
+ }
201
+
202
+ device.unlockForConfiguration()
203
+ } catch {
204
+ print(error)
205
+ }
206
+ }
207
+
208
+ private func captureDevice() -> AVCaptureDevice? {
209
+ let discoverySession = AVCaptureDevice.DiscoverySession(
210
+ deviceTypes: [.builtInWideAngleCamera],
211
+ mediaType: .video,
212
+ position: .unspecified
213
+ )
214
+ return discoverySession.devices.first { $0.position == .back }
215
+ }
216
+
217
+ private func startSession() {
218
+ weak var weakSelf = self
219
+ sessionQueue.async {
220
+ guard let strongSelf = weakSelf else {return}
221
+ strongSelf.captureSession.startRunning()
222
+ }
223
+ }
224
+
225
+ private func stopSession() {
226
+ weak var weakSelf = self
227
+ sessionQueue.async {
228
+ guard let strongSelf = weakSelf else {return}
229
+ strongSelf.captureSession.stopRunning()
230
+ }
231
+ }
232
+
233
+ private func finishWithError(_ error: String) {
234
+ if (!finishedAlready) {
235
+ finishedAlready = true
236
+ delegate?.onError(error)
237
+ }
238
+ }
239
+
240
+ private func finishWithResult(_ result: [DetectedBarcode]){
241
+ if (!finishedAlready) {
242
+ finishedAlready = true
243
+ delegate?.onComplete(result)
244
+ }
245
+ }
246
+ }
247
+
248
+ extension UIImage {
249
+ func scalePreservingAspectRatio(targetSize: CGSize) -> UIImage {
250
+ // Determine the scale factor that preserves aspect ratio
251
+ let widthRatio = targetSize.width / size.width
252
+ let heightRatio = targetSize.height / size.height
253
+
254
+ let scaleFactor = min(widthRatio, heightRatio)
255
+
256
+ // Compute the new image size that preserves aspect ratio
257
+ let scaledImageSize = CGSize(
258
+ width: size.width * scaleFactor,
259
+ height: size.height * scaleFactor
260
+ )
261
+
262
+ // Draw and return the resized UIImage
263
+ let renderer = UIGraphicsImageRenderer(
264
+ size: scaledImageSize
265
+ )
266
+
267
+ let scaledImage = renderer.image { _ in
268
+ self.draw(in: CGRect(
269
+ origin: .zero,
270
+ size: scaledImageSize
271
+ ))
272
+ }
273
+ return scaledImage
274
+ }
275
+ }
276
+
277
+ extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
278
+
279
+ func captureOutput(_ output: AVCaptureOutput,didOutput sampleBuffer: CMSampleBuffer,from connection: AVCaptureConnection) {
280
+ guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
281
+ print("Failed to get image buffer from sample buffer.")
282
+ return
283
+ }
284
+
285
+ let visionImage = VisionImage(buffer: sampleBuffer)
286
+ let orientation = Utils.imageOrientation(fromDevicePosition: .back)
287
+
288
+ visionImage.orientation = orientation
289
+
290
+ guard let inputImage = MLImage(sampleBuffer: sampleBuffer) else {
291
+ print("Failed to create MLImage from sample buffer.")
292
+ return
293
+ }
294
+ inputImage.orientation = orientation
295
+
296
+ let imageWidth = CGFloat(CVPixelBufferGetWidth(imageBuffer))
297
+ let imageHeight = CGFloat(CVPixelBufferGetHeight(imageBuffer))
298
+
299
+ barcodeAnalyzer.analyze(in: visionImage, width: imageWidth, height: imageHeight)
300
+ }
301
+ }
@@ -0,0 +1,70 @@
1
+ import MLKitBarcodeScanning
2
+
3
+ class DetectedBarcode: Hashable, Equatable, CustomDebugStringConvertible {
4
+
5
+ var debugDescription: String {
6
+ var des: String = "\(type(of: self)) {"
7
+ for child in Mirror(reflecting: self).children {
8
+ if let propName = child.label {
9
+ des += "\n\t\(propName): \(child.value)"
10
+ }
11
+ }
12
+
13
+ return des + "\n}"
14
+ }
15
+
16
+ func hash(into hasher: inout Hasher) {
17
+ hasher.combine(value)
18
+ hasher.combine(barcodeType)
19
+ hasher.combine(format)
20
+ let _ = hasher.finalize()
21
+ }
22
+
23
+ static func == (lhs: DetectedBarcode, rhs: DetectedBarcode) -> Bool {
24
+ return lhs.value.elementsEqual(rhs.value) && lhs.barcodeType == rhs.barcodeType && lhs.format == rhs.format
25
+ }
26
+
27
+ public private(set) var bounds: CGRect
28
+ public private(set) var value: String
29
+ public private(set) var format: Int
30
+ public private(set) var barcodeType: Int
31
+ public private(set) var distanceToCenter: CGFloat
32
+ public private(set) var isPortrait: Bool
33
+
34
+ init(barcode: Barcode, bounds: CGRect, centerX: CGFloat, centerY: CGFloat) {
35
+ format = barcode.format.rawValue
36
+ barcodeType = barcode.valueType.rawValue
37
+ self.bounds = bounds
38
+ if let rawValue = barcode.rawValue {
39
+ value = rawValue
40
+ } else {
41
+ value = String(data: barcode.rawData!, encoding: .ascii)!;
42
+ }
43
+ self.isPortrait = bounds.height > bounds.width
44
+ distanceToCenter = hypot((centerX - bounds.midX), (centerY - bounds.midY));
45
+ }
46
+
47
+ public func isInScanArea(scanArea: CGRect, ignoreRotated: Bool) -> Bool {
48
+ if (ignoreRotated && isPortrait) {
49
+ return false
50
+ }
51
+
52
+ return scanArea.contains(getCenterLine(forceScreenOrientation: ignoreRotated))
53
+ }
54
+
55
+ public func getCenterLine(forceScreenOrientation: Bool = false) -> CGRect {
56
+ if (!forceScreenOrientation && isPortrait) {
57
+ return CGRect(x: bounds.midX, y: bounds.minY, width: 1, height: bounds.height)
58
+ }
59
+ return CGRect(x: bounds.minX, y: bounds.midY, width: bounds.width, height: 1)
60
+ }
61
+
62
+ public func outputAsDictionary() -> [String: Any] {
63
+ return [
64
+ "value": value,
65
+ "type": BarcodeType.getFromInt(intValue: barcodeType)!.rawValue,
66
+ "format": BarcodeFormat.getFromInt(intValue: format)!.rawValue,
67
+ "distanceToCenter": round(distanceToCenter*100)/100.0
68
+ ]
69
+ }
70
+ }
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
7
+ <key>CFBundleExecutable</key>
8
+ <string>$(EXECUTABLE_NAME)</string>
9
+ <key>CFBundleIdentifier</key>
10
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11
+ <key>CFBundleInfoDictionaryVersion</key>
12
+ <string>6.0</string>
13
+ <key>CFBundleName</key>
14
+ <string>$(PRODUCT_NAME)</string>
15
+ <key>CFBundlePackageType</key>
16
+ <string>FMWK</string>
17
+ <key>CFBundleShortVersionString</key>
18
+ <string>1.0</string>
19
+ <key>CFBundleVersion</key>
20
+ <string>$(CURRENT_PROJECT_VERSION)</string>
21
+ <key>NSPrincipalClass</key>
22
+ <string></string>
23
+ </dict>
24
+ </plist>
@@ -0,0 +1,10 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ //! Project version number for Plugin.
4
+ FOUNDATION_EXPORT double PluginVersionNumber;
5
+
6
+ //! Project version string for Plugin.
7
+ FOUNDATION_EXPORT const unsigned char PluginVersionString[];
8
+
9
+ // In this header, you should import all the public headers of your framework using statements like #import <Plugin/PublicHeader.h>
10
+
@@ -0,0 +1,8 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <Capacitor/Capacitor.h>
3
+
4
+ // Define the plugin using the CAP_PLUGIN Macro, and
5
+ // each method the plugin supports using the CAP_PLUGIN_METHOD macro.
6
+ CAP_PLUGIN(MlKitBarcodeScannerPlugin, "MlKitBarcodeScanner",
7
+ CAP_PLUGIN_METHOD(scan, CAPPluginReturnPromise);
8
+ )
@@ -0,0 +1,69 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import AVFoundation
4
+
5
+ @objc(MlKitBarcodeScannerPlugin)
6
+ public class MlKitBarcodeScannerPlugin: CAPPlugin, CameraViewControllerDelegate {
7
+ private var call: CAPPluginCall?
8
+ private var settings: ScannerSettings!
9
+ private var player: AVAudioPlayer?
10
+
11
+ @objc func scan(_ call: CAPPluginCall) {
12
+ let options = call.jsObjectRepresentation
13
+ self.call = call
14
+ settings = ScannerSettings(options: options)
15
+
16
+ DispatchQueue.main.async {
17
+ let cameraViewController = CameraViewController(settings: self.settings)
18
+ cameraViewController.delegate = self
19
+ self.bridge!.viewController!.present(cameraViewController, animated: true)
20
+ }
21
+ }
22
+
23
+ func onComplete(_ result: [DetectedBarcode]) {
24
+ weak var weakSelf = self
25
+ DispatchQueue.main.sync {
26
+ guard weakSelf != nil else {
27
+ return
28
+ }
29
+ self.bridge!.viewController!.dismiss(animated: true)
30
+ }
31
+ if (result.isEmpty) {
32
+ self.call!.reject("NO_BARCODE")
33
+ return
34
+ }
35
+ if (settings.vibrateOnSuccess) {
36
+ AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) { }
37
+ }
38
+ if (settings.beepOnSuccess) {
39
+ if let audioData = NSDataAsset(name: "beep")?.data {
40
+ do {
41
+ try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
42
+ try AVAudioSession.sharedInstance().setActive(true)
43
+
44
+ player = try AVAudioPlayer(data: audioData)
45
+
46
+ if let unwrappedPlayer = player {
47
+ unwrappedPlayer.play()
48
+ }
49
+ } catch let error {
50
+ print(error.localizedDescription)
51
+ }
52
+ }
53
+ }
54
+ var resultBarcodes: [[String: Any]] = []
55
+ for detectedBarcode in result {
56
+ resultBarcodes.append(detectedBarcode.outputAsDictionary())
57
+ }
58
+ var output = JSObject()
59
+ output.updateValue(resultBarcodes, forKey: "barcodes")
60
+ self.call!.resolve(output)
61
+ }
62
+
63
+ func onError(_ error: String) {
64
+ print("error occurred")
65
+ self.bridge!.viewController!.dismiss(animated: false)
66
+ print("controller dismissed")
67
+ self.call!.reject(error)
68
+ }
69
+ }