@scr2em/capacitor-scanner 6.0.2 → 6.0.4
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.
- package/README.md +1 -1
- package/android/src/main/java/com/leadliaion/capacitorscanner/CapacitorScannerPlugin.java +21 -9
- package/dist/docs.json +1 -1
- package/dist/esm/definitions.d.ts +1 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorScannerPlugin/CapacitorScannerPlugin.swift +350 -332
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ removeAllListeners() => Promise<void>
|
|
|
121
121
|
|
|
122
122
|
#### ScannerOptions
|
|
123
123
|
|
|
124
|
-
<code>{ formats?: BarcodeFormat[]; cameraDirection?: 'BACK' | 'FRONT'; }</code>
|
|
124
|
+
<code>{ formats?: BarcodeFormat[]; cameraDirection?: 'BACK' | 'FRONT'; debounceTimeInMilli?: number }</code>
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
#### CapturePhotoResult
|
|
@@ -169,7 +169,7 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
169
169
|
cameraProviderFuture.addListener(() -> {
|
|
170
170
|
try {
|
|
171
171
|
cameraProvider = cameraProviderFuture.get();
|
|
172
|
-
bindCamera(cameraProvider, previewView, lensFacing);
|
|
172
|
+
bindCamera(cameraProvider, previewView, lensFacing, call);
|
|
173
173
|
|
|
174
174
|
orientationEventListener = new OrientationEventListener(getContext()) {
|
|
175
175
|
@Override
|
|
@@ -209,7 +209,7 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
209
209
|
});
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
private void bindCamera(@NonNull ProcessCameraProvider cameraProvider, PreviewView previewView, int lensFacing) {
|
|
212
|
+
private void bindCamera(@NonNull ProcessCameraProvider cameraProvider, PreviewView previewView, int lensFacing, PluginCall call) {
|
|
213
213
|
cameraProvider.unbindAll();
|
|
214
214
|
|
|
215
215
|
DisplayMetrics metrics = new DisplayMetrics();
|
|
@@ -240,7 +240,7 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
240
240
|
.setTargetRotation(rotation)
|
|
241
241
|
.build();
|
|
242
242
|
|
|
243
|
-
imageAnalysis.setAnalyzer(executor, new BarcodeAnalyzer());
|
|
243
|
+
imageAnalysis.setAnalyzer(executor, new BarcodeAnalyzer(call));
|
|
244
244
|
|
|
245
245
|
imageCapture = new ImageCapture.Builder()
|
|
246
246
|
.setTargetResolution(targetResolution)
|
|
@@ -260,18 +260,30 @@ public class CapacitorScannerPlugin extends Plugin {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
@ExperimentalGetImage private class BarcodeAnalyzer implements ImageAnalysis.Analyzer {
|
|
263
|
+
final PluginCall call;
|
|
264
|
+
BarcodeAnalyzer(PluginCall call) {
|
|
265
|
+
this.call = call;
|
|
266
|
+
}
|
|
267
|
+
|
|
263
268
|
@Override
|
|
264
269
|
public void analyze(@NonNull ImageProxy imageProxy) {
|
|
265
270
|
@androidx.camera.core.ExperimentalGetImage
|
|
266
271
|
android.media.Image mediaImage = imageProxy.getImage();
|
|
267
272
|
if (mediaImage != null) {
|
|
268
273
|
InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
|
|
269
|
-
scanner
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
if (scanner != null) {
|
|
275
|
+
scanner.process(image)
|
|
276
|
+
.addOnSuccessListener(executor, CapacitorScannerPlugin.this::processBarcodes)
|
|
277
|
+
.addOnFailureListener(executor, e -> {
|
|
278
|
+
echo("Failed to process image: " + e.getMessage());
|
|
279
|
+
call.reject("Failed to process image: " + e.getMessage());
|
|
280
|
+
})
|
|
281
|
+
.addOnCompleteListener(task -> imageProxy.close());
|
|
282
|
+
} else {
|
|
283
|
+
imageProxy.close();
|
|
284
|
+
echo("Scanner is null, skipping analysis");
|
|
285
|
+
call.reject("Scanner is null, skipping analysis");
|
|
286
|
+
}
|
|
275
287
|
} else {
|
|
276
288
|
imageProxy.close();
|
|
277
289
|
}
|
package/dist/docs.json
CHANGED
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
"docs": "",
|
|
198
198
|
"types": [
|
|
199
199
|
{
|
|
200
|
-
"text": "{\n formats?: BarcodeFormat[];\n cameraDirection?: 'BACK' | 'FRONT';\n}",
|
|
200
|
+
"text": "{\n formats?: BarcodeFormat[];\n cameraDirection?: 'BACK' | 'FRONT';\n debounceTimeInMilli?: number\n}",
|
|
201
201
|
"complexTypes": [
|
|
202
202
|
"BarcodeFormat"
|
|
203
203
|
]
|
|
@@ -11,6 +11,7 @@ export interface CapacitorScannerPlugin {
|
|
|
11
11
|
export declare type ScannerOptions = {
|
|
12
12
|
formats?: BarcodeFormat[];
|
|
13
13
|
cameraDirection?: 'BACK' | 'FRONT';
|
|
14
|
+
debounceTimeInMilli?: number;
|
|
14
15
|
};
|
|
15
16
|
export declare type BarcodeScannedEvent = {
|
|
16
17
|
scannedCode: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAuBA,MAAM,CAAN,IAAY,aAYX;AAZD,WAAY,aAAa;IACvB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,qCAAoB,CAAA;IACpB,2CAA0B,CAAA;IAC1B,+BAAc,CAAA;IACd,iCAAgB,CAAA;IAChB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,+BAAc,CAAA;AAChB,CAAC,EAZW,aAAa,KAAb,aAAa,QAYxB","sourcesContent":["export interface CapacitorScannerPlugin {\n startScanning(options?: ScannerOptions): Promise<void>;\n stopScanning(): Promise<void>;\n openSettings(): Promise<void>;\n capturePhoto(): Promise<CapturePhotoResult>;\n checkPermissions(): Promise<PermissionsResult>;\n requestPermissions(): Promise<PermissionsResult>;\n addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;\n removeAllListeners(): Promise<void>;\n}\n\nexport type ScannerOptions = {\n formats?: BarcodeFormat[];\n cameraDirection?: 'BACK' | 'FRONT';\n debounceTimeInMilli?: number\n};\n\nexport type BarcodeScannedEvent = { scannedCode: string; format: string };\n\nexport type PermissionsResult = { camera: 'prompt' | 'denied' | 'granted' };\n\nexport type CapturePhotoResult = { imageBase64: string };\n\nexport enum BarcodeFormat {\n Aztec = 'AZTEC',\n Code39 = 'CODE_39',\n Code93 = 'CODE_93',\n Code128 = 'CODE_128',\n DataMatrix = 'DATA_MATRIX',\n Ean8 = 'EAN_8',\n Ean13 = 'EAN_13',\n Itf14 = 'ITF14',\n Pdf417 = 'PDF_417',\n QrCode = 'QR_CODE',\n UpcE = 'UPC_E',\n}\n\ndeclare global {\n interface PluginRegistry {\n QRScanner: CapacitorScannerPlugin;\n }\n}\n"]}
|
|
@@ -7,340 +7,358 @@ import Foundation
|
|
|
7
7
|
* here: https://capacitorjs.com/docs/plugins/ios
|
|
8
8
|
*/
|
|
9
9
|
struct VoteStatus {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
var votes: Int
|
|
11
|
+
var done: Bool
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
@objc(CapacitorScannerPlugin)
|
|
15
15
|
public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetadataOutputObjectsDelegate, AVCapturePhotoCaptureDelegate {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
16
|
+
public let identifier = "CapacitorScannerPlugin"
|
|
17
|
+
public let jsName = "CapacitorScanner"
|
|
18
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
19
|
+
CAPPluginMethod(name: "startScanning", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "stopScanning", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
22
|
+
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
|
|
23
|
+
CAPPluginMethod(name: "openSettings", returnType: CAPPluginReturnPromise),
|
|
24
|
+
CAPPluginMethod(name: "capturePhoto", returnType: CAPPluginReturnPromise)
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
private var captureSession: AVCaptureSession?
|
|
28
|
+
private var cameraView: UIView?
|
|
29
|
+
private var previewLayer: AVCaptureVideoPreviewLayer?
|
|
30
|
+
|
|
31
|
+
private let voteThreshold = 5
|
|
32
|
+
private var scannedCodesVotes: [String: VoteStatus] = [:]
|
|
33
|
+
|
|
34
|
+
// for capturing images
|
|
35
|
+
private var photoOutput: AVCapturePhotoOutput?
|
|
36
|
+
private var capturePhotoCall: CAPPluginCall?
|
|
37
|
+
|
|
38
|
+
private var orientationObserver: NSObjectProtocol?
|
|
39
|
+
|
|
40
|
+
@objc func capturePhoto(_ call: CAPPluginCall) {
|
|
41
|
+
guard let photoOutput = self.photoOutput, captureSession?.isRunning == true else {
|
|
42
|
+
call.reject("Camera is not set up or running")
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let photoSettings = AVCapturePhotoSettings()
|
|
47
|
+
self.capturePhotoCall = call
|
|
48
|
+
photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
|
|
52
|
+
if let captureError = error {
|
|
53
|
+
self.capturePhotoCall?.reject("Photo capture failed: \(captureError.localizedDescription)")
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
guard let imageData = photo.fileDataRepresentation() else {
|
|
58
|
+
self.capturePhotoCall?.reject("Unable to get image data")
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let base64String = imageData.base64EncodedString()
|
|
63
|
+
self.capturePhotoCall?.resolve(["imageBase64": "data:image/jpeg;base64, \(base64String)"])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@objc func startScanning(_ call: CAPPluginCall) {
|
|
67
|
+
self.stopScanning(call)
|
|
68
|
+
|
|
69
|
+
DispatchQueue.main.async {
|
|
70
|
+
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
|
+
let cameraDirection: AVCaptureDevice.Position = call.getString("cameraDirection", "BACK") == "BACK" ? .back : .front
|
|
84
|
+
|
|
85
|
+
guard let videoCaptureDevice = self.getCaptureDevice(position: cameraDirection) else {
|
|
86
|
+
call.reject("Unable to access the camera")
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
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
|
|
97
|
+
}
|
|
98
|
+
|
|
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()
|
|
107
|
+
|
|
108
|
+
if captureSession.canAddOutput(metadataOutput) {
|
|
109
|
+
captureSession.addOutput(metadataOutput)
|
|
110
|
+
|
|
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
|
+
}
|
|
120
|
+
|
|
121
|
+
// Add photo output
|
|
122
|
+
let photoOutput = AVCapturePhotoOutput()
|
|
123
|
+
self.photoOutput = photoOutput
|
|
124
|
+
if captureSession.canAddOutput(photoOutput) {
|
|
125
|
+
captureSession.addOutput(photoOutput)
|
|
126
|
+
} else {
|
|
127
|
+
call.reject("Unable to add photo output to capture session")
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
self.hideWebViewBackground()
|
|
132
|
+
|
|
133
|
+
if let webView = self.webView, let superView = webView.superview {
|
|
134
|
+
let cameraView = UIView(frame: superView.bounds)
|
|
135
|
+
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
|
136
|
+
previewLayer.videoGravity = .resizeAspectFill
|
|
137
|
+
previewLayer.frame = superView.bounds
|
|
138
|
+
cameraView.layer.addSublayer(previewLayer)
|
|
139
|
+
|
|
140
|
+
webView.superview?.insertSubview(cameraView, belowSubview: webView)
|
|
141
|
+
|
|
142
|
+
self.captureSession = captureSession
|
|
143
|
+
self.cameraView = cameraView
|
|
144
|
+
self.previewLayer = previewLayer
|
|
145
|
+
|
|
146
|
+
// Add orientation change observer
|
|
147
|
+
self.addOrientationChangeObserver()
|
|
148
|
+
|
|
149
|
+
// Initial orientation setup
|
|
150
|
+
self.updatePreviewOrientation()
|
|
151
|
+
|
|
152
|
+
DispatchQueue.global(qos: .background).async {
|
|
153
|
+
captureSession.startRunning()
|
|
154
|
+
call.resolve()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
} else {
|
|
158
|
+
call.reject("unknown")
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@objc func stopScanning(_ call: CAPPluginCall) {
|
|
164
|
+
DispatchQueue.main.async {
|
|
165
|
+
if let captureSession = self.captureSession {
|
|
166
|
+
captureSession.stopRunning()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
self.previewLayer?.removeFromSuperlayer()
|
|
170
|
+
self.cameraView?.removeFromSuperview()
|
|
171
|
+
|
|
172
|
+
self.captureSession = nil
|
|
173
|
+
self.cameraView = nil
|
|
174
|
+
self.previewLayer = nil
|
|
175
|
+
self.scannedCodesVotes = [:]
|
|
176
|
+
self.showWebViewBackground()
|
|
177
|
+
|
|
178
|
+
self.removeOrientationChangeObserver()
|
|
179
|
+
|
|
180
|
+
call.resolve()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private func addOrientationChangeObserver() {
|
|
185
|
+
self.orientationObserver = NotificationCenter.default.addObserver(
|
|
186
|
+
forName: UIDevice.orientationDidChangeNotification,
|
|
187
|
+
object: nil,
|
|
188
|
+
queue: .main
|
|
189
|
+
) { [weak self] _ in
|
|
190
|
+
self?.updatePreviewOrientation()
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private func removeOrientationChangeObserver() {
|
|
195
|
+
if let observer = self.orientationObserver {
|
|
196
|
+
NotificationCenter.default.removeObserver(observer)
|
|
197
|
+
self.orientationObserver = nil
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private func updatePreviewOrientation() {
|
|
202
|
+
guard let previewLayer = self.previewLayer,
|
|
203
|
+
let connection = previewLayer.connection,
|
|
204
|
+
let cameraView = self.cameraView
|
|
205
|
+
else {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let deviceOrientation = UIDevice.current.orientation
|
|
210
|
+
|
|
211
|
+
if deviceOrientation.isFlat == true || deviceOrientation == .portraitUpsideDown {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let newOrientation: AVCaptureVideoOrientation
|
|
216
|
+
|
|
217
|
+
switch deviceOrientation {
|
|
218
|
+
case .landscapeLeft:
|
|
219
|
+
newOrientation = .landscapeRight
|
|
220
|
+
case .landscapeRight:
|
|
221
|
+
newOrientation = .landscapeLeft
|
|
222
|
+
default:
|
|
223
|
+
newOrientation = .portrait
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
connection.videoOrientation = newOrientation
|
|
227
|
+
|
|
228
|
+
// Update camera view and preview layer frames
|
|
229
|
+
let screenBounds = UIScreen.main.bounds
|
|
230
|
+
let screenWidth = screenBounds.width
|
|
231
|
+
let screenHeight = screenBounds.height
|
|
232
|
+
|
|
233
|
+
// Determine the correct dimensions based on orientation
|
|
234
|
+
let width: CGFloat
|
|
235
|
+
let height: CGFloat
|
|
236
|
+
if newOrientation == .portrait {
|
|
237
|
+
width = min(screenWidth, screenHeight)
|
|
238
|
+
height = max(screenWidth, screenHeight)
|
|
239
|
+
} else {
|
|
240
|
+
width = max(screenWidth, screenHeight)
|
|
241
|
+
height = min(screenWidth, screenHeight)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Update frames
|
|
245
|
+
cameraView.frame = CGRect(x: 0, y: 0, width: width, height: height)
|
|
246
|
+
previewLayer.frame = cameraView.bounds
|
|
247
|
+
}
|
|
248
|
+
|
|
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] {
|
|
298
|
+
if formats.isEmpty {
|
|
299
|
+
return CapacitorScannerHelpers.getAllSupportedFormats()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return formats.compactMap { format in
|
|
303
|
+
CapacitorScannerHelpers.convertStringToBarcodeScannerFormat(format)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Must run on UI thread.
|
|
309
|
+
*/
|
|
310
|
+
private func hideWebViewBackground() {
|
|
311
|
+
guard let webView = self.webView else {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
webView.isOpaque = false
|
|
315
|
+
webView.backgroundColor = UIColor.clear
|
|
316
|
+
webView.scrollView.backgroundColor = UIColor.clear
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Must run on UI thread.
|
|
321
|
+
*/
|
|
322
|
+
private func showWebViewBackground() {
|
|
323
|
+
guard let webView = self.webView else {
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
webView.isOpaque = true
|
|
327
|
+
webView.backgroundColor = UIColor.white
|
|
328
|
+
webView.scrollView.backgroundColor = UIColor.white
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
332
|
+
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
333
|
+
|
|
334
|
+
var stringStatus = "prompt"
|
|
335
|
+
|
|
336
|
+
if status == .denied || status == .restricted {
|
|
337
|
+
stringStatus = "denied"
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if status == .authorized {
|
|
341
|
+
stringStatus = "granted"
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
call.resolve(["camera": stringStatus])
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
348
|
+
AVCaptureDevice.requestAccess(for: .video) { _ in
|
|
349
|
+
self.checkPermissions(call)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@objc func openSettings(_ call: CAPPluginCall) {
|
|
354
|
+
let url = URL(string: UIApplication.openSettingsURLString)
|
|
355
|
+
DispatchQueue.main.async {
|
|
356
|
+
if let url = url, UIApplication.shared.canOpenURL(url) {
|
|
357
|
+
UIApplication.shared.open(url)
|
|
358
|
+
call.resolve()
|
|
359
|
+
} else {
|
|
360
|
+
call.reject("unknown")
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
346
364
|
}
|