@scr2em/capacitor-scanner 6.0.4 → 6.0.6

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 {
@@ -68,55 +79,37 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
68
79
 
69
80
  DispatchQueue.main.async {
70
81
  let captureSession = AVCaptureSession()
71
-
72
- // Always check if the device supports 4K first
73
- if captureSession.canSetSessionPreset(.hd4K3840x2160) {
74
- captureSession.sessionPreset = .hd4K3840x2160
75
- } else if captureSession.canSetSessionPreset(.hd1920x1080) {
76
- captureSession.sessionPreset = .hd1920x1080
77
- } else {
78
- captureSession.sessionPreset = .hd1280x720
79
- }
80
-
81
- captureSession.sessionPreset = AVCaptureSession.Preset.hd4K3840x2160
82
-
83
82
  let cameraDirection: AVCaptureDevice.Position = call.getString("cameraDirection", "BACK") == "BACK" ? .back : .front
84
-
85
83
  guard let videoCaptureDevice = self.getCaptureDevice(position: cameraDirection) else {
86
- call.reject("Unable to access the camera")
84
+ print("No camera available")
87
85
  return
88
86
  }
89
87
 
90
- let videoInput: AVCaptureDeviceInput
91
-
92
- do {
93
- videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
94
- } catch {
95
- call.reject("Unable to initialize video input")
96
- return
88
+ // Check if the selected camera supports the desired preset
89
+ let desiredPreset = AVCaptureSession.Preset.hd1920x1080
90
+ if videoCaptureDevice.supportsSessionPreset(desiredPreset) && captureSession.canSetSessionPreset(desiredPreset) {
91
+ captureSession.sessionPreset = desiredPreset
92
+ } else if captureSession.canSetSessionPreset(.hd1280x720) {
93
+ captureSession.sessionPreset = .hd1280x720
94
+ } else {
95
+ captureSession.sessionPreset = .medium // Fallback to a lower resolution
97
96
  }
98
97
 
99
- if captureSession.canAddInput(videoInput) {
100
- captureSession.addInput(videoInput)
101
- } else {
102
- call.reject("Unable to add video input to capture session")
98
+ guard let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else {
99
+ print("Could not create video input")
103
100
  return
104
101
  }
102
+ captureSession.addInput(videoInput)
105
103
 
106
- let metadataOutput = AVCaptureMetadataOutput()
107
-
108
- if captureSession.canAddOutput(metadataOutput) {
109
- captureSession.addOutput(metadataOutput)
104
+ // 2. Setup video output for Vision
105
+ let videoOutput = AVCaptureVideoDataOutput()
106
+ videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: .userInitiated))
107
+ captureSession.addOutput(videoOutput)
108
+ self.videoDataOutput = videoOutput
110
109
 
111
- metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
110
+ let formats = call.getArray("formats", String.self) ?? []
112
111
 
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
- }
112
+ self.barcodeDetectionRequest.symbologies = self.getSupportedFormats(from: formats)
120
113
 
121
114
  // Add photo output
122
115
  let photoOutput = AVCapturePhotoOutput()
@@ -168,10 +161,12 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
168
161
 
169
162
  self.previewLayer?.removeFromSuperlayer()
170
163
  self.cameraView?.removeFromSuperview()
164
+ self.videoDataOutput?.setSampleBufferDelegate(nil, queue: nil)
171
165
 
172
166
  self.captureSession = nil
173
167
  self.cameraView = nil
174
168
  self.previewLayer = nil
169
+ self.videoDataOutput = nil
175
170
  self.scannedCodesVotes = [:]
176
171
  self.showWebViewBackground()
177
172
 
@@ -246,55 +241,7 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
246
241
  previewLayer.frame = cameraView.bounds
247
242
  }
248
243
 
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] {
244
+ private func getSupportedFormats(from formats: [String]) -> [BarcodeFormat] {
298
245
  if formats.isEmpty {
299
246
  return CapacitorScannerHelpers.getAllSupportedFormats()
300
247
  }
@@ -350,6 +297,24 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
350
297
  }
351
298
  }
352
299
 
300
+ // Keep the enhanced device selection method
301
+ private func getCaptureDevice(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
302
+ let discoverySession = AVCaptureDevice.DiscoverySession(
303
+ deviceTypes: [.builtInDualCamera, .builtInTripleCamera, .builtInWideAngleCamera],
304
+ mediaType: .video,
305
+ position: position
306
+ )
307
+ // Prioritize higher quality cameras first
308
+ if let device = discoverySession.devices.first(where: { $0.deviceType == .builtInTripleCamera }) ??
309
+ discoverySession.devices.first(where: { $0.deviceType == .builtInDualCamera }) ??
310
+ discoverySession.devices.first(where: { $0.deviceType == .builtInWideAngleCamera })
311
+ {
312
+ return device
313
+ }
314
+
315
+ return nil
316
+ }
317
+
353
318
  @objc func openSettings(_ call: CAPPluginCall) {
354
319
  let url = URL(string: UIApplication.openSettingsURLString)
355
320
  DispatchQueue.main.async {
@@ -361,4 +326,80 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
361
326
  }
362
327
  }
363
328
  }
329
+
330
+ private func processClassification(_ request: VNRequest, error: Error?) {
331
+ // Handle any errors
332
+ if let error = error {
333
+ print("Vision error: \(error)")
334
+ return
335
+ }
336
+
337
+ // Get the barcode observations
338
+ guard let observations = request.results as? [VNBarcodeObservation] else {
339
+ return
340
+ }
341
+
342
+ // First find the barcode with highest confidence
343
+ let highestConfidenceBarcode = observations
344
+ .filter { $0.payloadStringValue != nil }
345
+ .max(by: { $0.confidence < $1.confidence })
346
+
347
+ // Then process only that barcode if found
348
+ if let bestObservation = highestConfidenceBarcode,
349
+ let payload = bestObservation.payloadStringValue
350
+ {
351
+ /*
352
+ this is a voting system to
353
+ 1. avoid scanning the same code
354
+ 2. avoid scanning any code that doesn't appear for at least in 10 frames
355
+ of the video stream to reduce the number of false positives
356
+ */
357
+
358
+ var voteStatus = self.scannedCodesVotes[payload] ?? VoteStatus(votes: 0, done: false)
359
+
360
+ if !voteStatus.done {
361
+ voteStatus.votes += 1
362
+
363
+ if voteStatus.votes >= self.voteThreshold {
364
+ voteStatus.done = true
365
+
366
+ self.notifyListeners("barcodeScanned", data: [
367
+ "scannedCode": payload,
368
+ "format": CapacitorScannerHelpers.convertBarcodeScannerFormatToString(bestObservation.symbology),
369
+ ])
370
+
371
+ // Reset votes after successful scan
372
+ self.scannedCodesVotes = [:]
373
+ }
374
+ }
375
+
376
+ self.scannedCodesVotes[payload] = voteStatus
377
+ }
378
+ }
379
+ }
380
+
381
+ extension CapacitorScannerPlugin: AVCaptureVideoDataOutputSampleBufferDelegate {
382
+ public func captureOutput(_ output: AVCaptureOutput,
383
+ didOutput sampleBuffer: CMSampleBuffer,
384
+ from connection: AVCaptureConnection)
385
+ {
386
+ // This is called for every frame from the camera
387
+
388
+ // 1. Get the pixel buffer from the frame
389
+ guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
390
+ return
391
+ }
392
+
393
+ // 2. Create a Vision image handler
394
+ let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer,
395
+ orientation: .up,
396
+ options: [:])
397
+
398
+ // 3. Perform the barcode detection
399
+ do {
400
+ try imageRequestHandler.perform([self.barcodeDetectionRequest])
401
+ } catch {
402
+ print("Failed to perform Vision request: \(error)")
403
+ }
404
+ }
364
405
  }
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.6",
4
4
  "description": "scan codes",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",