@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 =
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 =
|
|
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
|
-
|
|
84
|
+
print("No camera available")
|
|
87
85
|
return
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
110
|
+
let formats = call.getArray("formats", String.self) ?? []
|
|
112
111
|
|
|
113
|
-
|
|
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
|
-
|
|
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
|
}
|