@momo-kits/camerakit 0.152.3 → 0.152.4-sp.2
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.
|
@@ -10,6 +10,9 @@ import UIKit
|
|
|
10
10
|
import CoreMotion
|
|
11
11
|
import Vision
|
|
12
12
|
|
|
13
|
+
// Global OCR queue (so OCR never blocks sessionQueue)
|
|
14
|
+
let globalOCRQueue = DispatchQueue(label: "com.tesla.react-native-camera-kit.ocr", qos: .userInitiated)
|
|
15
|
+
|
|
13
16
|
/*
|
|
14
17
|
* Real camera implementation that uses AVFoundation
|
|
15
18
|
*/
|
|
@@ -21,16 +24,16 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
21
24
|
private let session = AVCaptureSession()
|
|
22
25
|
// Communicate with the session and other session objects on this queue.
|
|
23
26
|
private let sessionQueue = DispatchQueue(label: "com.tesla.react-native-camera-kit")
|
|
24
|
-
|
|
27
|
+
|
|
25
28
|
// utilities
|
|
26
29
|
private var setupResult: SetupResult = .notStarted
|
|
27
30
|
private var isSessionRunning: Bool = false
|
|
28
31
|
private var backgroundRecordingId: UIBackgroundTaskIdentifier = .invalid
|
|
29
|
-
|
|
32
|
+
|
|
30
33
|
private var videoDeviceInput: AVCaptureDeviceInput?
|
|
31
34
|
private let photoOutput = AVCapturePhotoOutput()
|
|
32
35
|
private let metadataOutput = AVCaptureMetadataOutput()
|
|
33
|
-
|
|
36
|
+
|
|
34
37
|
private var resizeMode: ResizeMode = .cover
|
|
35
38
|
private var flashMode: FlashMode = .auto
|
|
36
39
|
private var torchMode: TorchMode = .off
|
|
@@ -38,6 +41,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
38
41
|
private var resetFocus: (() -> Void)?
|
|
39
42
|
private var focusFinished: (() -> Void)?
|
|
40
43
|
private var onBarcodeRead: ((_ barcode: String,_ codeFormat : CodeFormat) -> Void)?
|
|
44
|
+
private var supportedBarcodeTypes: [CodeFormat] = []
|
|
41
45
|
private var scannerFrameSize: CGRect? = nil
|
|
42
46
|
private var barcodeFrameSize: CGSize? = nil
|
|
43
47
|
private var onOrientationChange: RCTDirectEventBlock?
|
|
@@ -45,23 +49,25 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
45
49
|
private var lastOnZoom: Double?
|
|
46
50
|
private var zoom: Double?
|
|
47
51
|
private var maxZoom: Double?
|
|
48
|
-
|
|
52
|
+
|
|
49
53
|
private var deviceOrientation = UIDeviceOrientation.unknown
|
|
50
54
|
private var motionManager: CMMotionManager?
|
|
51
|
-
|
|
55
|
+
|
|
52
56
|
// KVO observation
|
|
53
57
|
private var adjustingFocusObservation: NSKeyValueObservation?
|
|
54
58
|
|
|
55
59
|
// Keep delegate objects in memory to avoid collecting them before photo capturing finishes
|
|
56
60
|
private var inProgressPhotoCaptureDelegates = [Int64: PhotoCaptureDelegate]()
|
|
57
|
-
|
|
61
|
+
|
|
58
62
|
private var onTextRead: ((_ text: String) -> Void)?
|
|
59
63
|
private let videoDataOutput = AVCaptureVideoDataOutput()
|
|
60
64
|
private var textRequest: VNRecognizeTextRequest?
|
|
61
65
|
private var textDetectionEnabled = false
|
|
62
66
|
private var lastTextProcess = Date.distantPast
|
|
63
67
|
private let textThrottle: TimeInterval = 0.35 // seconds
|
|
64
|
-
|
|
68
|
+
|
|
69
|
+
private var zoomStartedAt: Double = 1.0
|
|
70
|
+
|
|
65
71
|
// MARK: - Lifecycle
|
|
66
72
|
|
|
67
73
|
func cameraRemovedFromSuperview() {
|
|
@@ -87,15 +93,16 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
87
93
|
UIDevice.current.endGeneratingDeviceOrientationNotifications()
|
|
88
94
|
#endif
|
|
89
95
|
}
|
|
90
|
-
|
|
96
|
+
|
|
91
97
|
deinit {
|
|
92
98
|
removeObservers()
|
|
93
99
|
}
|
|
94
|
-
|
|
100
|
+
|
|
95
101
|
// MARK: - Public
|
|
96
102
|
|
|
97
103
|
func setup(cameraType: CameraType, supportedBarcodeType: [CodeFormat]) {
|
|
98
|
-
|
|
104
|
+
self.supportedBarcodeTypes = supportedBarcodeType
|
|
105
|
+
|
|
99
106
|
// Setup the capture session with priority on basic video preview first
|
|
100
107
|
sessionQueue.async {
|
|
101
108
|
self.setupResult = self.setupBasicVideoInput(cameraType: cameraType)
|
|
@@ -117,7 +124,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
117
124
|
self.setVideoOrientationToInterfaceOrientation()
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
|
-
|
|
127
|
+
|
|
121
128
|
DispatchQueue.global(qos: .utility).async {
|
|
122
129
|
self.initializeMotionManager()
|
|
123
130
|
}
|
|
@@ -125,37 +132,36 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
// MARK: - Private optimization methods
|
|
128
|
-
|
|
135
|
+
|
|
129
136
|
private func setupBasicVideoInput(cameraType: CameraType) -> SetupResult {
|
|
130
137
|
guard let videoDevice = self.getBestDevice(for: cameraType),
|
|
131
138
|
let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else {
|
|
132
139
|
return .sessionConfigurationFailed
|
|
133
140
|
}
|
|
134
|
-
|
|
141
|
+
|
|
135
142
|
session.beginConfiguration()
|
|
136
143
|
defer { session.commitConfiguration() }
|
|
137
|
-
|
|
144
|
+
|
|
138
145
|
if session.canAddInput(videoDeviceInput) {
|
|
139
146
|
session.addInput(videoDeviceInput)
|
|
140
147
|
self.videoDeviceInput = videoDeviceInput
|
|
141
148
|
self.resetZoom(forDevice: videoDevice)
|
|
142
149
|
return .success
|
|
143
|
-
} else {
|
|
144
|
-
return .sessionConfigurationFailed
|
|
145
150
|
}
|
|
151
|
+
return .sessionConfigurationFailed
|
|
146
152
|
}
|
|
147
|
-
|
|
153
|
+
|
|
148
154
|
private func setupAdditionalOutputs(supportedBarcodeType: [CodeFormat]) {
|
|
149
155
|
session.beginConfiguration()
|
|
150
156
|
defer { session.commitConfiguration() }
|
|
151
|
-
|
|
157
|
+
|
|
152
158
|
// Add photo output
|
|
153
159
|
if #available(iOS 13.0, *) {
|
|
154
|
-
if let maxPhotoQualityPrioritization {
|
|
160
|
+
if let maxPhotoQualityPrioritization = maxPhotoQualityPrioritization {
|
|
155
161
|
photoOutput.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization.avQualityPrioritization
|
|
156
162
|
}
|
|
157
163
|
}
|
|
158
|
-
|
|
164
|
+
|
|
159
165
|
if session.canAddOutput(photoOutput) {
|
|
160
166
|
session.addOutput(photoOutput)
|
|
161
167
|
|
|
@@ -165,12 +171,12 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
165
171
|
}
|
|
166
172
|
}
|
|
167
173
|
}
|
|
168
|
-
|
|
174
|
+
|
|
169
175
|
// Add metadata output for barcode scanning
|
|
170
176
|
if self.session.canAddOutput(metadataOutput) {
|
|
171
177
|
self.session.addOutput(metadataOutput)
|
|
172
178
|
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
|
173
|
-
|
|
179
|
+
|
|
174
180
|
let availableTypes = self.metadataOutput.availableMetadataObjectTypes
|
|
175
181
|
let filteredTypes = supportedBarcodeType
|
|
176
182
|
.map { $0.toAVMetadataObjectType() }
|
|
@@ -178,21 +184,37 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
178
184
|
|
|
179
185
|
metadataOutput.metadataObjectTypes = filteredTypes
|
|
180
186
|
}
|
|
181
|
-
|
|
187
|
+
|
|
182
188
|
// add for text detections
|
|
183
189
|
if (textRequest != nil && self.session.canAddOutput(self.videoDataOutput)) {
|
|
184
190
|
self.session.addOutput(self.videoDataOutput)
|
|
185
191
|
}
|
|
186
192
|
}
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
|
|
194
|
+
// MARK: - Pause / Resume non-essential outputs
|
|
195
|
+
private func pauseNonEssentialOutputs() {
|
|
196
|
+
videoDataOutput.setSampleBufferDelegate(nil, queue: nil)
|
|
197
|
+
metadataOutput.metadataObjectTypes = []
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private func resumeNonEssentialOutputs() {
|
|
201
|
+
if textDetectionEnabled {
|
|
202
|
+
videoDataOutput.setSampleBufferDelegate(self, queue: globalOCRQueue)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let available = metadataOutput.availableMetadataObjectTypes
|
|
206
|
+
let filtered = supportedBarcodeTypes.map { $0.toAVMetadataObjectType() }.filter { available.contains($0) }
|
|
207
|
+
metadataOutput.metadataObjectTypes = filtered
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// MARK: - Zoom
|
|
189
211
|
func zoomPinchStart() {
|
|
190
212
|
sessionQueue.async {
|
|
191
213
|
guard let videoDevice = self.videoDeviceInput?.device else { return }
|
|
192
214
|
self.zoomStartedAt = videoDevice.videoZoomFactor
|
|
193
215
|
}
|
|
194
216
|
}
|
|
195
|
-
|
|
217
|
+
|
|
196
218
|
func zoomPinchChange(pinchScale: CGFloat) {
|
|
197
219
|
guard !pinchScale.isNaN else { return }
|
|
198
220
|
|
|
@@ -201,7 +223,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
201
223
|
|
|
202
224
|
let desiredZoomFactor = (self.zoomStartedAt / self.defaultZoomFactor(for: videoDevice)) * pinchScale
|
|
203
225
|
let zoomForDevice = self.getValidZoom(forDevice: videoDevice, zoom: desiredZoomFactor)
|
|
204
|
-
|
|
226
|
+
|
|
205
227
|
if zoomForDevice != self.normalizedZoom(for: videoDevice) {
|
|
206
228
|
// Only trigger zoom changes if it's an uncontrolled component (zoom isn't manually set)
|
|
207
229
|
// otherwise it's likely to cause issues inf. loops
|
|
@@ -212,14 +234,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
212
234
|
}
|
|
213
235
|
}
|
|
214
236
|
}
|
|
215
|
-
|
|
237
|
+
|
|
216
238
|
func update(maxZoom: Double?) {
|
|
217
239
|
self.maxZoom = maxZoom
|
|
218
240
|
|
|
219
241
|
// Re-update zoom value in case the max was increased
|
|
220
242
|
self.update(zoom: self.zoom)
|
|
221
243
|
}
|
|
222
|
-
|
|
244
|
+
|
|
223
245
|
func update(zoom: Double?) {
|
|
224
246
|
sessionQueue.async {
|
|
225
247
|
self.zoom = zoom
|
|
@@ -230,7 +252,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
230
252
|
self.setZoomFor(videoDevice, to: zoomForDevice)
|
|
231
253
|
}
|
|
232
254
|
}
|
|
233
|
-
|
|
255
|
+
|
|
234
256
|
/**
|
|
235
257
|
`desiredZoom` can be nil when we want to notify what the zoom factor really is
|
|
236
258
|
*/
|
|
@@ -251,11 +273,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
251
273
|
lastOnZoom = desiredOrCameraZoom
|
|
252
274
|
self.onZoomCallback?(["zoom": desiredOrCameraZoom])
|
|
253
275
|
}
|
|
254
|
-
|
|
276
|
+
|
|
255
277
|
func update(onZoom: RCTDirectEventBlock?) {
|
|
256
278
|
self.onZoomCallback = onZoom
|
|
257
279
|
}
|
|
258
|
-
|
|
280
|
+
|
|
259
281
|
func focus(at touchPoint: CGPoint, focusBehavior: FocusBehavior) {
|
|
260
282
|
DispatchQueue.main.async {
|
|
261
283
|
let devicePoint = self.cameraPreview.previewLayer.captureDevicePointConverted(fromLayerPoint: touchPoint)
|
|
@@ -270,7 +292,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
270
292
|
self.resetFocus = nil
|
|
271
293
|
self.focusFinished = nil
|
|
272
294
|
}
|
|
273
|
-
|
|
295
|
+
|
|
274
296
|
do {
|
|
275
297
|
try videoDevice.lockForConfiguration()
|
|
276
298
|
|
|
@@ -293,11 +315,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
293
315
|
}
|
|
294
316
|
}
|
|
295
317
|
}
|
|
296
|
-
|
|
318
|
+
|
|
297
319
|
func update(onOrientationChange: RCTDirectEventBlock?) {
|
|
298
320
|
self.onOrientationChange = onOrientationChange
|
|
299
321
|
}
|
|
300
|
-
|
|
322
|
+
|
|
301
323
|
func update(torchMode: TorchMode) {
|
|
302
324
|
sessionQueue.async {
|
|
303
325
|
self.torchMode = torchMode
|
|
@@ -314,11 +336,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
314
336
|
}
|
|
315
337
|
}
|
|
316
338
|
}
|
|
317
|
-
|
|
339
|
+
|
|
318
340
|
func update(flashMode: FlashMode) {
|
|
319
341
|
self.flashMode = flashMode
|
|
320
342
|
}
|
|
321
|
-
|
|
343
|
+
|
|
322
344
|
func update(maxPhotoQualityPrioritization: MaxPhotoQualityPrioritization?) {
|
|
323
345
|
guard #available(iOS 13.0, *) else { return }
|
|
324
346
|
guard maxPhotoQualityPrioritization != self.maxPhotoQualityPrioritization else { return }
|
|
@@ -329,7 +351,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
329
351
|
self.photoOutput.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization?.avQualityPrioritization ?? .balanced
|
|
330
352
|
}
|
|
331
353
|
}
|
|
332
|
-
|
|
354
|
+
|
|
333
355
|
func update(cameraType: CameraType) {
|
|
334
356
|
sessionQueue.async {
|
|
335
357
|
if self.videoDeviceInput?.device.position == cameraType.avPosition {
|
|
@@ -343,14 +365,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
343
365
|
let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else {
|
|
344
366
|
return
|
|
345
367
|
}
|
|
346
|
-
|
|
368
|
+
|
|
347
369
|
self.removeObservers()
|
|
348
370
|
self.session.beginConfiguration()
|
|
349
371
|
defer { self.session.commitConfiguration() }
|
|
350
372
|
|
|
351
373
|
// Remove the existing device input first, since using the front and back camera simultaneously is not supported.
|
|
352
374
|
self.session.removeInput(currentViewDeviceInput)
|
|
353
|
-
|
|
375
|
+
|
|
354
376
|
if self.session.canAddInput(videoDeviceInput) {
|
|
355
377
|
self.session.addInput(videoDeviceInput)
|
|
356
378
|
self.resetZoom(forDevice: videoDevice)
|
|
@@ -359,14 +381,15 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
359
381
|
// If it fails, put back current camera
|
|
360
382
|
self.session.addInput(currentViewDeviceInput)
|
|
361
383
|
}
|
|
362
|
-
|
|
384
|
+
|
|
385
|
+
// self.session.commitConfiguration()
|
|
363
386
|
self.addObservers()
|
|
364
387
|
|
|
365
388
|
// We need to reapply the configuration after reloading the camera
|
|
366
389
|
self.update(torchMode: self.torchMode)
|
|
367
390
|
}
|
|
368
391
|
}
|
|
369
|
-
|
|
392
|
+
|
|
370
393
|
func update(resizeMode: ResizeMode) {
|
|
371
394
|
DispatchQueue.main.async {
|
|
372
395
|
switch resizeMode {
|
|
@@ -377,24 +400,28 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
377
400
|
}
|
|
378
401
|
}
|
|
379
402
|
}
|
|
380
|
-
|
|
403
|
+
|
|
381
404
|
func capturePicture(onWillCapture: @escaping () -> Void,
|
|
382
|
-
|
|
383
|
-
|
|
405
|
+
onSuccess: @escaping (_ imageData: Data, _ thumbnailData: Data?, _ dimensions: CMVideoDimensions) -> Void,
|
|
406
|
+
onError: @escaping (_ message: String) -> Void) {
|
|
384
407
|
/*
|
|
385
408
|
Retrieve the video preview layer's video orientation on the main queue before
|
|
386
409
|
entering the session queue. Do this to ensure that UI elements are accessed on
|
|
387
410
|
the main thread and session configuration is done on the session queue.
|
|
388
411
|
*/
|
|
412
|
+
|
|
413
|
+
// 🚀 NEW: Pause OCR + barcode before capturing
|
|
414
|
+
self.pauseNonEssentialOutputs()
|
|
415
|
+
|
|
389
416
|
DispatchQueue.main.async { [weak self] in
|
|
390
417
|
guard let self = self else {
|
|
391
418
|
onError("Camera was deallocated")
|
|
392
419
|
return
|
|
393
420
|
}
|
|
394
|
-
|
|
421
|
+
|
|
395
422
|
let videoPreviewLayerOrientation =
|
|
396
423
|
self.videoOrientation(from: self.deviceOrientation) ?? self.cameraPreview.previewLayer.connection?.videoOrientation
|
|
397
|
-
|
|
424
|
+
|
|
398
425
|
self.sessionQueue.async { [weak self] in
|
|
399
426
|
guard let self = self else {
|
|
400
427
|
onError("Camera was deallocated")
|
|
@@ -407,14 +434,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
407
434
|
onError("Camera session is not running")
|
|
408
435
|
return
|
|
409
436
|
}
|
|
410
|
-
|
|
437
|
+
|
|
411
438
|
// Ensure photo output has an active video connection
|
|
412
439
|
guard let photoOutputConnection = self.photoOutput.connection(with: .video) else {
|
|
413
440
|
print("Cannot capture photo: no video connection available")
|
|
414
441
|
onError("Camera connection is not available")
|
|
415
442
|
return
|
|
416
443
|
}
|
|
417
|
-
|
|
444
|
+
|
|
418
445
|
// Verify the connection is active and enabled
|
|
419
446
|
guard photoOutputConnection.isActive && photoOutputConnection.isEnabled else {
|
|
420
447
|
print("Cannot capture photo: video connection is not active or enabled")
|
|
@@ -426,7 +453,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
426
453
|
if let videoPreviewLayerOrientation {
|
|
427
454
|
photoOutputConnection.videoOrientation = videoPreviewLayerOrientation
|
|
428
455
|
}
|
|
429
|
-
|
|
456
|
+
|
|
430
457
|
let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
|
|
431
458
|
if #available(iOS 13.0, *) {
|
|
432
459
|
settings.photoQualityPrioritization = self.photoOutput.maxPhotoQualityPrioritization
|
|
@@ -435,7 +462,34 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
435
462
|
if self.videoDeviceInput?.device.isFlashAvailable == true {
|
|
436
463
|
settings.flashMode = self.flashMode.avFlashMode
|
|
437
464
|
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
// --- FIX DARK IMAGES ON OLD DEVICES ---
|
|
468
|
+
|
|
469
|
+
// Force AE/AF recalibration before capture
|
|
470
|
+
if let device = self.videoDeviceInput?.device {
|
|
471
|
+
do {
|
|
472
|
+
try device.lockForConfiguration()
|
|
473
|
+
|
|
474
|
+
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
475
|
+
device.exposureMode = .continuousAutoExposure
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
479
|
+
device.focusMode = .continuousAutoFocus
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
device.isSubjectAreaChangeMonitoringEnabled = false
|
|
483
|
+
|
|
484
|
+
device.unlockForConfiguration()
|
|
485
|
+
} catch {}
|
|
486
|
+
}
|
|
438
487
|
|
|
488
|
+
// Improve exposure and stability
|
|
489
|
+
settings.isAutoStillImageStabilizationEnabled = true
|
|
490
|
+
settings.isHighResolutionPhotoEnabled = true
|
|
491
|
+
//-----------------------------------------
|
|
492
|
+
|
|
439
493
|
let photoCaptureDelegate = PhotoCaptureDelegate(
|
|
440
494
|
with: settings,
|
|
441
495
|
onWillCapture: onWillCapture,
|
|
@@ -443,30 +497,34 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
443
497
|
// Use weak self to prevent crash if camera is deallocated during capture
|
|
444
498
|
self?.inProgressPhotoCaptureDelegates[uniqueID] = nil
|
|
445
499
|
onSuccess(imageData, thumbnailData, dimensions)
|
|
500
|
+
self?.resumeNonEssentialOutputs()
|
|
446
501
|
},
|
|
447
502
|
onCaptureError: { [weak self] uniqueID, errorMessage in
|
|
448
503
|
// Use weak self to prevent crash if camera is deallocated during capture
|
|
449
504
|
self?.inProgressPhotoCaptureDelegates[uniqueID] = nil
|
|
450
505
|
onError(errorMessage)
|
|
506
|
+
self?.resumeNonEssentialOutputs()
|
|
451
507
|
}
|
|
452
508
|
)
|
|
453
|
-
|
|
509
|
+
|
|
454
510
|
self.inProgressPhotoCaptureDelegates[photoCaptureDelegate.requestedPhotoSettings.uniqueID] = photoCaptureDelegate
|
|
455
511
|
self.photoOutput.capturePhoto(with: settings, delegate: photoCaptureDelegate)
|
|
456
512
|
}
|
|
457
513
|
}
|
|
458
514
|
}
|
|
459
|
-
|
|
515
|
+
|
|
516
|
+
// MARK: - Barcode scanning
|
|
460
517
|
func isBarcodeScannerEnabled(_ isEnabled: Bool,
|
|
461
518
|
supportedBarcodeTypes supportedBarcodeType: [CodeFormat],
|
|
462
519
|
onBarcodeRead: ((_ barcode: String,_ codeFormat:CodeFormat) -> Void)?) {
|
|
463
520
|
sessionQueue.async {
|
|
464
521
|
self.onBarcodeRead = onBarcodeRead
|
|
522
|
+
self.supportedBarcodeTypes = supportedBarcodeType
|
|
523
|
+
|
|
524
|
+
let availableTypes = self.metadataOutput.availableMetadataObjectTypes
|
|
465
525
|
let newTypes: [AVMetadataObject.ObjectType]
|
|
466
526
|
if isEnabled && onBarcodeRead != nil {
|
|
467
|
-
|
|
468
|
-
newTypes = supportedBarcodeType.map { $0.toAVMetadataObjectType() }
|
|
469
|
-
.filter { availableTypes.contains($0) }
|
|
527
|
+
newTypes = supportedBarcodeType.map { $0.toAVMetadataObjectType() }.filter { availableTypes.contains($0) }
|
|
470
528
|
} else {
|
|
471
529
|
newTypes = []
|
|
472
530
|
}
|
|
@@ -479,11 +537,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
479
537
|
}
|
|
480
538
|
}
|
|
481
539
|
}
|
|
482
|
-
|
|
540
|
+
|
|
483
541
|
func update(barcodeFrameSize: CGSize?) {
|
|
484
542
|
self.barcodeFrameSize = barcodeFrameSize
|
|
485
543
|
}
|
|
486
|
-
|
|
544
|
+
|
|
487
545
|
func update(scannerFrameSize: CGRect?) {
|
|
488
546
|
guard self.scannerFrameSize != scannerFrameSize else { return }
|
|
489
547
|
self.sessionQueue.async {
|
|
@@ -491,7 +549,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
491
549
|
if !self.session.isRunning {
|
|
492
550
|
return
|
|
493
551
|
}
|
|
494
|
-
|
|
552
|
+
|
|
495
553
|
DispatchQueue.main.async {
|
|
496
554
|
var visibleRect: CGRect?
|
|
497
555
|
if scannerFrameSize != nil && scannerFrameSize != .zero {
|
|
@@ -510,7 +568,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
510
568
|
}
|
|
511
569
|
}
|
|
512
570
|
}
|
|
513
|
-
|
|
571
|
+
|
|
514
572
|
|
|
515
573
|
func isTextDetectionEnabled(_ isEnabled: Bool, onTextRead: ((String) -> Void)?) {
|
|
516
574
|
sessionQueue.async {
|
|
@@ -525,37 +583,39 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
525
583
|
self.textRequest?.recognitionLanguages = ["en", "fr", "de", "es", "vi"]
|
|
526
584
|
self.textRequest?.recognitionLevel = .accurate
|
|
527
585
|
self.textRequest?.usesLanguageCorrection = false
|
|
528
|
-
|
|
586
|
+
|
|
529
587
|
self.videoDataOutput.alwaysDiscardsLateVideoFrames = true
|
|
530
|
-
self.videoDataOutput.setSampleBufferDelegate(self, queue:
|
|
588
|
+
self.videoDataOutput.setSampleBufferDelegate(self, queue: globalOCRQueue)
|
|
531
589
|
self.videoDataOutput.videoSettings = [
|
|
532
590
|
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
|
533
591
|
]
|
|
534
|
-
|
|
592
|
+
|
|
535
593
|
} else {
|
|
536
594
|
self.textRequest = nil
|
|
537
595
|
}
|
|
538
596
|
}
|
|
539
597
|
}
|
|
540
|
-
|
|
598
|
+
|
|
541
599
|
// AVCaptureVideoDataOutputSampleBufferDelegate
|
|
542
600
|
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
|
|
543
601
|
guard textDetectionEnabled, let request = textRequest else { return }
|
|
544
602
|
let now = Date()
|
|
545
603
|
if now.timeIntervalSince(lastTextProcess) < textThrottle { return }
|
|
546
604
|
lastTextProcess = now
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
605
|
+
|
|
606
|
+
globalOCRQueue.async {
|
|
607
|
+
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
|
|
608
|
+
var requestOptions: [VNImageOption: Any] = [:]
|
|
609
|
+
|
|
610
|
+
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: .up, options: requestOptions)
|
|
611
|
+
do {
|
|
612
|
+
try handler.perform([request])
|
|
613
|
+
} catch {
|
|
614
|
+
// ignore OCR errors; don't crash the pipeline
|
|
615
|
+
}
|
|
556
616
|
}
|
|
557
617
|
}
|
|
558
|
-
|
|
618
|
+
|
|
559
619
|
// MARK: - AVCaptureMetadataOutputObjectsDelegate
|
|
560
620
|
|
|
561
621
|
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
|
@@ -569,7 +629,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
569
629
|
|
|
570
630
|
onBarcodeRead?(codeStringValue,barcodeType)
|
|
571
631
|
}
|
|
572
|
-
|
|
632
|
+
|
|
573
633
|
// MARK: - Private
|
|
574
634
|
|
|
575
635
|
private func videoOrientation(from deviceOrientation: UIDeviceOrientation) -> AVCaptureVideoOrientation? {
|
|
@@ -588,7 +648,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
588
648
|
@unknown default: return nil
|
|
589
649
|
}
|
|
590
650
|
}
|
|
591
|
-
|
|
651
|
+
|
|
592
652
|
private func videoOrientation(from interfaceOrientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
|
|
593
653
|
switch interfaceOrientation {
|
|
594
654
|
case .portrait:
|
|
@@ -603,14 +663,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
603
663
|
@unknown default: return .portrait
|
|
604
664
|
}
|
|
605
665
|
}
|
|
606
|
-
|
|
666
|
+
|
|
607
667
|
private func getBestDevice(for cameraType: CameraType) -> AVCaptureDevice? {
|
|
608
668
|
if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: cameraType.avPosition) {
|
|
609
669
|
return device // single-lens/physical device
|
|
610
670
|
}
|
|
611
671
|
return nil
|
|
612
672
|
}
|
|
613
|
-
|
|
673
|
+
|
|
614
674
|
private func defaultZoomFactor(for videoDevice: AVCaptureDevice) -> CGFloat {
|
|
615
675
|
let fallback = 1.0
|
|
616
676
|
guard #available(iOS 13.0, *) else { return fallback }
|