@scr2em/capacitor-scanner 6.0.4 → 6.0.5

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.
@@ -1,71 +1,69 @@
1
1
 
2
- import Foundation
3
2
  import AVFoundation
3
+ import Vision
4
4
 
5
- public typealias BarcodeFormat = AVMetadataObject.ObjectType
5
+ public typealias BarcodeFormat = VNBarcodeSymbology
6
6
 
7
7
  public class CapacitorScannerHelpers {
8
+ public static func getAllSupportedFormats() -> [BarcodeFormat] {
9
+ return [.aztec, .code39, .code93, .code128, .dataMatrix, .ean8, .ean13, .itf14, .pdf417, .qr, .upce]
10
+ }
8
11
 
9
- public static func getAllSupportedFormats() -> [BarcodeFormat] {
10
- return [.aztec, .code39, .code93, .code128, .dataMatrix, .ean8, .ean13, .itf14, .pdf417, .qr, .upce]
11
- }
12
-
13
- public static func convertStringToBarcodeScannerFormat(_ value: String) -> BarcodeFormat? {
14
- switch value {
15
- case "AZTEC":
16
- return BarcodeFormat.aztec
17
- case "CODE_39":
18
- return BarcodeFormat.code39
19
- case "CODE_93":
20
- return BarcodeFormat.code93
21
- case "CODE_128":
22
- return BarcodeFormat.code128
23
- case "DATA_MATRIX":
24
- return BarcodeFormat.dataMatrix
25
- case "EAN_8":
26
- return BarcodeFormat.ean8
27
- case "EAN_13":
28
- return BarcodeFormat.ean13
29
- case "ITF14":
30
- return BarcodeFormat.itf14
31
- case "PDF_417":
32
- return BarcodeFormat.pdf417
33
- case "QR_CODE":
34
- return BarcodeFormat.qr
35
- case "UPC_E":
36
- return BarcodeFormat.upce
37
- default:
38
- return nil
39
- }
40
- }
41
-
42
- public static func convertBarcodeScannerFormatToString(_ format: BarcodeFormat) -> String? {
43
- switch format {
44
- case BarcodeFormat.aztec:
45
- return "AZTEC"
46
- case BarcodeFormat.code39:
47
- return "CODE_39"
48
- case BarcodeFormat.code93:
49
- return "CODE_93"
50
- case BarcodeFormat.code128:
51
- return "CODE_128"
52
- case BarcodeFormat.dataMatrix:
53
- return "DATA_MATRIX"
54
- case BarcodeFormat.ean8:
55
- return "EAN_8"
56
- case BarcodeFormat.ean13:
57
- return "EAN_13"
58
- case BarcodeFormat.itf14:
59
- return "ITF14"
60
- case BarcodeFormat.pdf417:
61
- return "PDF_417"
62
- case BarcodeFormat.qr:
63
- return "QR_CODE"
64
- case BarcodeFormat.upce:
65
- return "UPC_E"
66
- default:
67
- return nil
68
- }
69
- }
12
+ public static func convertStringToBarcodeScannerFormat(_ value: String) -> BarcodeFormat? {
13
+ switch value {
14
+ case "AZTEC":
15
+ return BarcodeFormat.aztec
16
+ case "CODE_39":
17
+ return BarcodeFormat.code39
18
+ case "CODE_93":
19
+ return BarcodeFormat.code93
20
+ case "CODE_128":
21
+ return BarcodeFormat.code128
22
+ case "DATA_MATRIX":
23
+ return BarcodeFormat.dataMatrix
24
+ case "EAN_8":
25
+ return BarcodeFormat.ean8
26
+ case "EAN_13":
27
+ return BarcodeFormat.ean13
28
+ case "ITF14":
29
+ return BarcodeFormat.itf14
30
+ case "PDF_417":
31
+ return BarcodeFormat.pdf417
32
+ case "QR_CODE":
33
+ return BarcodeFormat.qr
34
+ case "UPC_E":
35
+ return BarcodeFormat.upce
36
+ default:
37
+ return nil
38
+ }
39
+ }
70
40
 
41
+ public static func convertBarcodeScannerFormatToString(_ format: BarcodeFormat) -> String? {
42
+ switch format {
43
+ case BarcodeFormat.aztec:
44
+ return "AZTEC"
45
+ case BarcodeFormat.code39:
46
+ return "CODE_39"
47
+ case BarcodeFormat.code93:
48
+ return "CODE_93"
49
+ case BarcodeFormat.code128:
50
+ return "CODE_128"
51
+ case BarcodeFormat.dataMatrix:
52
+ return "DATA_MATRIX"
53
+ case BarcodeFormat.ean8:
54
+ return "EAN_8"
55
+ case BarcodeFormat.ean13:
56
+ return "EAN_13"
57
+ case BarcodeFormat.itf14:
58
+ return "ITF14"
59
+ case BarcodeFormat.pdf417:
60
+ return "PDF_417"
61
+ case BarcodeFormat.qr:
62
+ return "QR_CODE"
63
+ case BarcodeFormat.upce:
64
+ return "UPC_E"
65
+ default:
66
+ return nil
67
+ }
68
+ }
71
69
  }
@@ -1,6 +1,7 @@
1
1
  import AVFoundation
2
2
  import Capacitor
3
3
  import Foundation
4
+ import Vision // For barcode detection
4
5
 
5
6
  /**
6
7
  * Please read the Capacitor iOS Plugin Development Guide
@@ -21,14 +22,14 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
21
22
  CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
22
23
  CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
23
24
  CAPPluginMethod(name: "openSettings", returnType: CAPPluginReturnPromise),
24
- CAPPluginMethod(name: "capturePhoto", returnType: CAPPluginReturnPromise)
25
+ CAPPluginMethod(name: "capturePhoto", returnType: CAPPluginReturnPromise),
25
26
  ]
26
27
 
27
28
  private var captureSession: AVCaptureSession?
28
29
  private var cameraView: UIView?
29
30
  private var previewLayer: AVCaptureVideoPreviewLayer?
30
31
 
31
- private let voteThreshold = 5
32
+ private let voteThreshold = 3
32
33
  private var scannedCodesVotes: [String: VoteStatus] = [:]
33
34
 
34
35
  // for capturing images
@@ -36,6 +37,16 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
36
37
  private var capturePhotoCall: CAPPluginCall?
37
38
 
38
39
  private var orientationObserver: NSObjectProtocol?
40
+ private var videoDataOutput: AVCaptureVideoDataOutput? // For getting video frames
41
+
42
+ // This is how we create a Vision request for detecting barcodes
43
+ private lazy var barcodeDetectionRequest: VNDetectBarcodesRequest = {
44
+ let request = VNDetectBarcodesRequest { [weak self] request, error in
45
+ // This closure is called when Vision finds barcodes
46
+ self?.processClassification(request, error: error)
47
+ }
48
+ return request
49
+ }()
39
50
 
40
51
  @objc func capturePhoto(_ call: CAPPluginCall) {
41
52
  guard let photoOutput = self.photoOutput, captureSession?.isRunning == true else {
@@ -69,54 +80,33 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
69
80
  DispatchQueue.main.async {
70
81
  let captureSession = AVCaptureSession()
71
82
 
72
- // Always check if the device supports 4K first
73
- if captureSession.canSetSessionPreset(.hd4K3840x2160) {
74
- captureSession.sessionPreset = .hd4K3840x2160
75
- } else if captureSession.canSetSessionPreset(.hd1920x1080) {
83
+ if captureSession.canSetSessionPreset(.hd1920x1080) {
76
84
  captureSession.sessionPreset = .hd1920x1080
77
85
  } else {
78
86
  captureSession.sessionPreset = .hd1280x720
79
87
  }
80
88
 
81
- captureSession.sessionPreset = AVCaptureSession.Preset.hd4K3840x2160
82
-
83
89
  let cameraDirection: AVCaptureDevice.Position = call.getString("cameraDirection", "BACK") == "BACK" ? .back : .front
84
90
 
85
91
  guard let videoCaptureDevice = self.getCaptureDevice(position: cameraDirection) else {
86
- call.reject("Unable to access the camera")
92
+ print("No camera available")
87
93
  return
88
94
  }
89
-
90
- let videoInput: AVCaptureDeviceInput
91
-
92
- do {
93
- videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
94
- } catch {
95
- call.reject("Unable to initialize video input")
95
+ guard let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else {
96
+ print("Could not create video input")
96
97
  return
97
98
  }
99
+ captureSession.addInput(videoInput)
98
100
 
99
- if captureSession.canAddInput(videoInput) {
100
- captureSession.addInput(videoInput)
101
- } else {
102
- call.reject("Unable to add video input to capture session")
103
- return
104
- }
105
-
106
- let metadataOutput = AVCaptureMetadataOutput()
101
+ // 2. Setup video output for Vision
102
+ let videoOutput = AVCaptureVideoDataOutput()
103
+ videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: .userInitiated))
104
+ captureSession.addOutput(videoOutput)
105
+ self.videoDataOutput = videoOutput
107
106
 
108
- if captureSession.canAddOutput(metadataOutput) {
109
- captureSession.addOutput(metadataOutput)
107
+ let formats = call.getArray("formats", String.self) ?? []
110
108
 
111
- metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
112
-
113
- let formats = call.getArray("formats", String.self) ?? []
114
- let metadataObjectTypes = self.getMetadataObjectTypes(from: formats)
115
- metadataOutput.metadataObjectTypes = metadataObjectTypes
116
- } else {
117
- call.reject("Unable to add metadata output to capture session")
118
- return
119
- }
109
+ self.barcodeDetectionRequest.symbologies = self.getSupportedFormats(from: formats)
120
110
 
121
111
  // Add photo output
122
112
  let photoOutput = AVCapturePhotoOutput()
@@ -168,10 +158,12 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
168
158
 
169
159
  self.previewLayer?.removeFromSuperlayer()
170
160
  self.cameraView?.removeFromSuperview()
161
+ self.videoDataOutput?.setSampleBufferDelegate(nil, queue: nil)
171
162
 
172
163
  self.captureSession = nil
173
164
  self.cameraView = nil
174
165
  self.previewLayer = nil
166
+ self.videoDataOutput = nil
175
167
  self.scannedCodesVotes = [:]
176
168
  self.showWebViewBackground()
177
169
 
@@ -246,55 +238,7 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
246
238
  previewLayer.frame = cameraView.bounds
247
239
  }
248
240
 
249
- public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
250
- guard let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
251
- let stringValue = metadataObject.stringValue
252
- else {
253
- return
254
- }
255
-
256
- /*
257
- this is a voting system to
258
- 1. avoid scanning the same code
259
- 2. avoid scanning any code that doesn't appear for at least in 10 frames of the video stream to reduce the number of false positives
260
- */
261
-
262
- var voteStatus = self.scannedCodesVotes[stringValue] ?? VoteStatus(votes: 0, done: false)
263
-
264
- if !voteStatus.done {
265
- voteStatus.votes += 1
266
-
267
- if voteStatus.votes >= self.voteThreshold {
268
- voteStatus.done = true
269
-
270
- self.notifyListeners("barcodeScanned", data: [
271
- "scannedCode": stringValue,
272
- "format": CapacitorScannerHelpers.convertBarcodeScannerFormatToString(metadataObject.type)
273
- ])
274
- }
275
- }
276
-
277
- self.scannedCodesVotes[stringValue] = voteStatus
278
- }
279
-
280
- private func getCaptureDevice(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
281
- let discoverySession = AVCaptureDevice.DiscoverySession(
282
- deviceTypes: [.builtInDualCamera, .builtInTripleCamera, .builtInWideAngleCamera],
283
- mediaType: .video,
284
- position: position
285
- )
286
- // Prioritize higher quality cameras first
287
- if let device = discoverySession.devices.first(where: { $0.deviceType == .builtInTripleCamera }) ??
288
- discoverySession.devices.first(where: { $0.deviceType == .builtInDualCamera }) ??
289
- discoverySession.devices.first(where: { $0.deviceType == .builtInWideAngleCamera })
290
- {
291
- return device
292
- }
293
-
294
- return nil
295
- }
296
-
297
- private func getMetadataObjectTypes(from formats: [String]) -> [BarcodeFormat] {
241
+ private func getSupportedFormats(from formats: [String]) -> [BarcodeFormat] {
298
242
  if formats.isEmpty {
299
243
  return CapacitorScannerHelpers.getAllSupportedFormats()
300
244
  }
@@ -350,6 +294,24 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
350
294
  }
351
295
  }
352
296
 
297
+ // Keep the enhanced device selection method
298
+ private func getCaptureDevice(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
299
+ let discoverySession = AVCaptureDevice.DiscoverySession(
300
+ deviceTypes: [.builtInDualCamera, .builtInTripleCamera, .builtInWideAngleCamera],
301
+ mediaType: .video,
302
+ position: position
303
+ )
304
+ // Prioritize higher quality cameras first
305
+ if let device = discoverySession.devices.first(where: { $0.deviceType == .builtInTripleCamera }) ??
306
+ discoverySession.devices.first(where: { $0.deviceType == .builtInDualCamera }) ??
307
+ discoverySession.devices.first(where: { $0.deviceType == .builtInWideAngleCamera })
308
+ {
309
+ return device
310
+ }
311
+
312
+ return nil
313
+ }
314
+
353
315
  @objc func openSettings(_ call: CAPPluginCall) {
354
316
  let url = URL(string: UIApplication.openSettingsURLString)
355
317
  DispatchQueue.main.async {
@@ -361,4 +323,80 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
361
323
  }
362
324
  }
363
325
  }
326
+
327
+ private func processClassification(_ request: VNRequest, error: Error?) {
328
+ // Handle any errors
329
+ if let error = error {
330
+ print("Vision error: \(error)")
331
+ return
332
+ }
333
+
334
+ // Get the barcode observations
335
+ guard let observations = request.results as? [VNBarcodeObservation] else {
336
+ return
337
+ }
338
+
339
+ // First find the barcode with highest confidence
340
+ let highestConfidenceBarcode = observations
341
+ .filter { $0.payloadStringValue != nil }
342
+ .max(by: { $0.confidence < $1.confidence })
343
+
344
+ // Then process only that barcode if found
345
+ if let bestObservation = highestConfidenceBarcode,
346
+ let payload = bestObservation.payloadStringValue
347
+ {
348
+ /*
349
+ this is a voting system to
350
+ 1. avoid scanning the same code
351
+ 2. avoid scanning any code that doesn't appear for at least in 10 frames
352
+ of the video stream to reduce the number of false positives
353
+ */
354
+
355
+ var voteStatus = self.scannedCodesVotes[payload] ?? VoteStatus(votes: 0, done: false)
356
+
357
+ if !voteStatus.done {
358
+ voteStatus.votes += 1
359
+
360
+ if voteStatus.votes >= self.voteThreshold {
361
+ voteStatus.done = true
362
+
363
+ self.notifyListeners("barcodeScanned", data: [
364
+ "scannedCode": payload,
365
+ "format": CapacitorScannerHelpers.convertBarcodeScannerFormatToString(bestObservation.symbology),
366
+ ])
367
+
368
+ // Reset votes after successful scan
369
+ self.scannedCodesVotes = [:]
370
+ }
371
+ }
372
+
373
+ self.scannedCodesVotes[payload] = voteStatus
374
+ }
375
+ }
376
+ }
377
+
378
+ extension CapacitorScannerPlugin: AVCaptureVideoDataOutputSampleBufferDelegate {
379
+ public func captureOutput(_ output: AVCaptureOutput,
380
+ didOutput sampleBuffer: CMSampleBuffer,
381
+ from connection: AVCaptureConnection)
382
+ {
383
+ // This is called for every frame from the camera
384
+
385
+ // 1. Get the pixel buffer from the frame
386
+ guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
387
+ return
388
+ }
389
+
390
+ // 2. Create a Vision image handler
391
+ let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer,
392
+ orientation: .up,
393
+ options: [:])
394
+
395
+ // 3. Perform the barcode detection
396
+ do {
397
+ try imageRequestHandler.perform([self.barcodeDetectionRequest])
398
+ } catch {
399
+ print("Failed to perform Vision request: \(error)")
400
+ }
401
+ }
364
402
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scr2em/capacitor-scanner",
3
- "version": "6.0.4",
3
+ "version": "6.0.5",
4
4
  "description": "scan codes",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",