@momo-kits/camerakit 0.161.2-beta.2 → 0.161.2-beta.7
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.
|
@@ -84,10 +84,10 @@ static id CKConvertFollyDynamicToId(const folly::dynamic &dyn)
|
|
|
84
84
|
- (void)prepareView
|
|
85
85
|
{
|
|
86
86
|
_view = [[CKCameraView alloc] init];
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
// just need to pass something, it won't really be used on fabric, but it's used to create events (it won't impact sending them)
|
|
89
89
|
_view.reactTag = @-1;
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
__weak __typeof__(self) weakSelf = self;
|
|
92
92
|
|
|
93
93
|
[_view setOnReadCode:^(NSDictionary* event) {
|
|
@@ -137,7 +137,7 @@ static id CKConvertFollyDynamicToId(const folly::dynamic &dyn)
|
|
|
137
137
|
std::dynamic_pointer_cast<const facebook::react::CKCameraEventEmitter>(strongSelf->_eventEmitter)->onMRZ({.docMRZ = docMRZ});
|
|
138
138
|
}
|
|
139
139
|
}];
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
self.contentView = _view;
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -232,7 +232,7 @@ static id CKConvertFollyDynamicToId(const folly::dynamic &dyn)
|
|
|
232
232
|
}
|
|
233
233
|
id zoomMode = CKConvertFollyDynamicToId(newProps.zoomMode);
|
|
234
234
|
if (zoomMode != nil) {
|
|
235
|
-
_view.zoomMode = [
|
|
235
|
+
_view.zoomMode = [focusMode isEqualToString:@"on"] ? CKZoomModeOn : CKZoomModeOff;
|
|
236
236
|
[changedProps addObject:@"zoomMode"];
|
|
237
237
|
}
|
|
238
238
|
id zoom = CKConvertFollyDynamicToId(newProps.zoom);
|
|
@@ -251,8 +251,8 @@ static id CKConvertFollyDynamicToId(const folly::dynamic &dyn)
|
|
|
251
251
|
_view.barcodeFrameSize = @{@"width": @(barcodeWidth), @"height": @(barcodeHeight)};
|
|
252
252
|
[changedProps addObject:@"barcodeFrameSize"];
|
|
253
253
|
}
|
|
254
|
-
|
|
255
|
-
|
|
254
|
+
|
|
255
|
+
|
|
256
256
|
[super updateProps:props oldProps:oldProps];
|
|
257
257
|
[_view didSetProps:changedProps];
|
|
258
258
|
}
|
|
@@ -260,12 +260,6 @@ static id CKConvertFollyDynamicToId(const folly::dynamic &dyn)
|
|
|
260
260
|
- (void)prepareForRecycle
|
|
261
261
|
{
|
|
262
262
|
[super prepareForRecycle];
|
|
263
|
-
|
|
264
|
-
if (_view != nil) {
|
|
265
|
-
[_view removeFromSuperview];
|
|
266
|
-
_view = nil;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
263
|
[self prepareView];
|
|
270
264
|
}
|
|
271
265
|
|
|
@@ -18,8 +18,12 @@ import Foundation
|
|
|
18
18
|
return CameraView()
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
@objc public static func capture(camera: CameraView,
|
|
22
|
-
|
|
21
|
+
@objc public static func capture(camera: CameraView,
|
|
22
|
+
options: NSDictionary,
|
|
23
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
24
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
25
|
+
camera.capture(options as! [String: Any], onSuccess: { resolve($0) },
|
|
26
|
+
onError: { reject("capture_error", $0, nil) })
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
@objc public static func checkDeviceCameraAuthorizationStatus(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
@@ -361,8 +361,8 @@ public class CameraView: UIView {
|
|
|
361
361
|
let features = detector?.features(in: ciImage)
|
|
362
362
|
|
|
363
363
|
if let firstFeature = features?.first as? CIQRCodeFeature {
|
|
364
|
-
if
|
|
365
|
-
self.onBarcodeRead(barcode: messageString
|
|
364
|
+
if (firstFeature.messageString != nil ) {
|
|
365
|
+
self.onBarcodeRead(barcode: firstFeature.messageString!, codeFormat: CodeFormat.qr)
|
|
366
366
|
}
|
|
367
367
|
return
|
|
368
368
|
}
|
|
@@ -22,19 +22,19 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
22
22
|
|
|
23
23
|
private let cameraPreview = RealPreviewView(frame: .zero)
|
|
24
24
|
private let session = AVCaptureSession()
|
|
25
|
-
|
|
26
|
-
private let sessionQueue =
|
|
27
|
-
|
|
25
|
+
// Communicate with the session and other session objects on this queue.
|
|
26
|
+
private let sessionQueue = DispatchQueue(label: "com.tesla.react-native-camera-kit")
|
|
27
|
+
|
|
28
28
|
// utilities
|
|
29
29
|
private var setupResult: SetupResult = .notStarted
|
|
30
30
|
private var configurationDepth: Int = 0 // Tracks nested beginConfiguration/commitConfiguration calls
|
|
31
31
|
private var pendingStop: Bool = false // Queues stop request during configuration
|
|
32
32
|
private var backgroundRecordingId: UIBackgroundTaskIdentifier = .invalid
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
private var videoDeviceInput: AVCaptureDeviceInput?
|
|
35
35
|
private let photoOutput = AVCapturePhotoOutput()
|
|
36
36
|
private let metadataOutput = AVCaptureMetadataOutput()
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
private var resizeMode: ResizeMode = .cover
|
|
39
39
|
private var flashMode: FlashMode = .auto
|
|
40
40
|
private var torchMode: TorchMode = .off
|
|
@@ -49,40 +49,44 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
49
49
|
private var lastOnZoom: Double?
|
|
50
50
|
private var zoom: Double?
|
|
51
51
|
private var maxZoom: Double?
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
private var deviceOrientation = UIDeviceOrientation.unknown
|
|
54
54
|
private var motionManager: CMMotionManager?
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
// KVO observation
|
|
57
57
|
private var adjustingFocusObservation: NSKeyValueObservation?
|
|
58
58
|
|
|
59
|
-
private var notificationObservers: [NSObjectProtocol] = []
|
|
60
|
-
|
|
61
59
|
// Keep delegate objects in memory to avoid collecting them before photo capturing finishes
|
|
62
60
|
private var inProgressPhotoCaptureDelegates = [Int64: PhotoCaptureDelegate]()
|
|
63
|
-
|
|
61
|
+
|
|
64
62
|
private var onTextRead: ((_ text: String) -> Void)?
|
|
65
63
|
private let videoDataOutput = AVCaptureVideoDataOutput()
|
|
66
64
|
private var textRequest: VNRecognizeTextRequest?
|
|
67
65
|
private var textDetectionEnabled = false
|
|
68
66
|
private var lastTextProcess = Date.distantPast
|
|
69
67
|
private let textThrottle: TimeInterval = 0.35 // seconds
|
|
70
|
-
|
|
68
|
+
|
|
71
69
|
private var zoomStartedAt: Double = 1.0
|
|
72
|
-
|
|
70
|
+
|
|
73
71
|
// MARK: - Lifecycle
|
|
74
72
|
|
|
75
73
|
func cameraRemovedFromSuperview() {
|
|
76
|
-
sessionQueue.async {
|
|
77
|
-
|
|
74
|
+
sessionQueue.async {
|
|
75
|
+
if self.setupResult == .success {
|
|
76
|
+
// Only stop if session is running
|
|
77
|
+
guard self.session.isRunning else {
|
|
78
|
+
self.removeObservers()
|
|
79
|
+
return
|
|
80
|
+
}
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
// If we're currently configuring the session, mark pending stop
|
|
83
|
+
// and let the configuration completion handle it
|
|
84
|
+
if self.configurationDepth > 0 {
|
|
85
|
+
self.pendingStop = true
|
|
86
|
+
return
|
|
87
|
+
}
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
// observers once the transaction commits; otherwise remove them now.
|
|
85
|
-
if !self.pendingStop {
|
|
89
|
+
self.session.stopRunning()
|
|
86
90
|
self.removeObservers()
|
|
87
91
|
}
|
|
88
92
|
}
|
|
@@ -95,120 +99,99 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
95
99
|
UIDevice.current.endGeneratingDeviceOrientationNotifications()
|
|
96
100
|
#endif
|
|
97
101
|
}
|
|
98
|
-
|
|
102
|
+
|
|
99
103
|
deinit {
|
|
100
104
|
removeObservers()
|
|
101
|
-
|
|
102
|
-
// Ensure the capture hardware is released even if cameraRemovedFromSuperview()
|
|
103
|
-
// was never delivered (e.g. a forced React surface cleanup during rapid
|
|
104
|
-
// mount/unmount). Capture only the session object so the block does not
|
|
105
|
-
// resurrect `self`.
|
|
106
|
-
let session = self.session
|
|
107
|
-
sessionQueue.async {
|
|
108
|
-
if session.isRunning {
|
|
109
|
-
session.stopRunning()
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
105
|
}
|
|
113
|
-
|
|
106
|
+
|
|
114
107
|
// MARK: - Public
|
|
115
108
|
|
|
116
109
|
func setup(cameraType: CameraType, supportedBarcodeType: [CodeFormat]) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// and never on the main queue, so the startRunning() below can no longer
|
|
129
|
-
// observe the session mid-configuration from another thread.
|
|
130
|
-
//
|
|
131
|
-
// This is the root-cause fix for:
|
|
132
|
-
// "[AVCaptureSession startRunning] startRunning may not be called between
|
|
133
|
-
// calls to beginConfiguration and commitConfiguration".
|
|
134
|
-
self.setupResult = self.configureSession(cameraType: cameraType,
|
|
135
|
-
supportedBarcodeType: supportedBarcodeType)
|
|
136
|
-
|
|
137
|
-
guard self.setupResult == .success else { return }
|
|
138
|
-
|
|
139
|
-
// Order matches the previous behaviour: start, then observe, then torch.
|
|
140
|
-
self.startSessionIfNeeded()
|
|
141
|
-
self.addObservers()
|
|
142
|
-
self.update(torchMode: self.torchMode)
|
|
110
|
+
|
|
111
|
+
// Setup the capture session with priority on basic video preview first
|
|
112
|
+
sessionQueue.async {
|
|
113
|
+
self.setupResult = self.setupBasicVideoInput(cameraType: cameraType)
|
|
114
|
+
|
|
115
|
+
if self.setupResult == .success {
|
|
116
|
+
self.session.startRunning()
|
|
117
|
+
|
|
118
|
+
self.setupAdditionalOutputs(supportedBarcodeType: supportedBarcodeType)
|
|
119
|
+
|
|
120
|
+
self.addObservers()
|
|
143
121
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
DispatchQueue.main.async { [weak self] in
|
|
149
|
-
guard let self else { return }
|
|
122
|
+
self.update(torchMode: self.torchMode)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
DispatchQueue.main.async {
|
|
150
126
|
self.cameraPreview.session = self.session
|
|
151
127
|
self.cameraPreview.previewLayer.videoGravity = .resizeAspectFill
|
|
128
|
+
self.session.sessionPreset = .photo
|
|
152
129
|
self.setVideoOrientationToInterfaceOrientation()
|
|
153
130
|
}
|
|
154
131
|
}
|
|
155
|
-
|
|
156
|
-
DispatchQueue.global(qos: .utility).async {
|
|
157
|
-
self
|
|
132
|
+
|
|
133
|
+
DispatchQueue.global(qos: .utility).async {
|
|
134
|
+
self.initializeMotionManager()
|
|
158
135
|
}
|
|
136
|
+
|
|
159
137
|
}
|
|
160
138
|
|
|
161
139
|
// MARK: - Private optimization methods
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
/// transaction. Must be called on `sessionQueue`. Order is significant:
|
|
165
|
-
/// beginConfiguration -> addInput -> set preset -> addOutputs -> commitConfiguration.
|
|
166
|
-
private func configureSession(cameraType: CameraType, supportedBarcodeType: [CodeFormat]) -> SetupResult {
|
|
167
|
-
assertOnSessionQueue()
|
|
140
|
+
|
|
141
|
+
private func setupBasicVideoInput(cameraType: CameraType) -> SetupResult {
|
|
168
142
|
guard let videoDevice = self.getBestDevice(for: cameraType),
|
|
169
143
|
let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else {
|
|
170
144
|
return .sessionConfigurationFailed
|
|
171
145
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
146
|
+
|
|
147
|
+
configurationDepth += 1
|
|
148
|
+
session.beginConfiguration()
|
|
149
|
+
defer {
|
|
150
|
+
session.commitConfiguration()
|
|
151
|
+
configurationDepth -= 1
|
|
152
|
+
handlePendingStopIfNeeded()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if session.canAddInput(videoDeviceInput) {
|
|
156
|
+
session.addInput(videoDeviceInput)
|
|
157
|
+
self.videoDeviceInput = videoDeviceInput
|
|
158
|
+
self.resetZoom(forDevice: videoDevice)
|
|
159
|
+
return .success
|
|
160
|
+
}
|
|
161
|
+
return .sessionConfigurationFailed
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private func setupAdditionalOutputs(supportedBarcodeType: [CodeFormat]) {
|
|
165
|
+
configurationDepth += 1
|
|
166
|
+
session.beginConfiguration()
|
|
167
|
+
defer {
|
|
168
|
+
session.commitConfiguration()
|
|
169
|
+
configurationDepth -= 1
|
|
170
|
+
handlePendingStopIfNeeded()
|
|
190
171
|
}
|
|
191
|
-
|
|
192
|
-
//
|
|
172
|
+
|
|
173
|
+
// Add photo output
|
|
193
174
|
if #available(iOS 13.0, *) {
|
|
194
175
|
if let maxPhotoQualityPrioritization = maxPhotoQualityPrioritization {
|
|
195
176
|
photoOutput.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization.avQualityPrioritization
|
|
196
177
|
}
|
|
197
178
|
}
|
|
179
|
+
|
|
198
180
|
if session.canAddOutput(photoOutput) {
|
|
199
181
|
session.addOutput(photoOutput)
|
|
200
|
-
|
|
201
|
-
if let photoOutputConnection = self.photoOutput.connection(with: .video)
|
|
202
|
-
|
|
203
|
-
|
|
182
|
+
|
|
183
|
+
if let photoOutputConnection = self.photoOutput.connection(with: .video) {
|
|
184
|
+
if photoOutputConnection.isVideoStabilizationSupported {
|
|
185
|
+
photoOutputConnection.preferredVideoStabilizationMode = .auto
|
|
186
|
+
}
|
|
204
187
|
}
|
|
205
188
|
}
|
|
206
|
-
|
|
207
|
-
//
|
|
189
|
+
|
|
190
|
+
// Add metadata output for barcode scanning
|
|
208
191
|
if self.session.canAddOutput(metadataOutput) {
|
|
209
192
|
self.session.addOutput(metadataOutput)
|
|
210
193
|
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
|
211
|
-
|
|
194
|
+
|
|
212
195
|
let availableTypes = self.metadataOutput.availableMetadataObjectTypes
|
|
213
196
|
let filteredTypes = supportedBarcodeType
|
|
214
197
|
.map { $0.toAVMetadataObjectType() }
|
|
@@ -216,59 +199,39 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
216
199
|
|
|
217
200
|
metadataOutput.metadataObjectTypes = filteredTypes
|
|
218
201
|
}
|
|
219
|
-
|
|
220
|
-
//
|
|
221
|
-
if textRequest != nil && self.session.canAddOutput(self.videoDataOutput) {
|
|
202
|
+
|
|
203
|
+
// add for text detections
|
|
204
|
+
if (textRequest != nil && self.session.canAddOutput(self.videoDataOutput)) {
|
|
222
205
|
self.session.addOutput(self.videoDataOutput)
|
|
223
206
|
}
|
|
224
|
-
|
|
225
|
-
return .success
|
|
226
207
|
}
|
|
227
|
-
|
|
208
|
+
|
|
228
209
|
// MARK: - Pause / Resume non-essential outputs
|
|
229
|
-
|
|
230
|
-
/// Disables OCR + barcode work while a photo is captured.
|
|
231
|
-
/// MUST be called on `sessionQueue` (these are capture-output mutations).
|
|
232
210
|
private func pauseNonEssentialOutputs() {
|
|
233
|
-
assertOnSessionQueue()
|
|
234
211
|
videoDataOutput.setSampleBufferDelegate(nil, queue: nil)
|
|
235
212
|
metadataOutput.rectOfInterest = CGRect(x: 0, y: 0, width: 0, height: 0)
|
|
236
213
|
}
|
|
237
|
-
|
|
238
|
-
/// Re-enables OCR + barcode work after a capture. May be called from any
|
|
239
|
-
/// thread (photo-capture completion handlers run on an arbitrary queue), so it
|
|
240
|
-
/// hops to the main queue only to read preview-layer geometry and applies all
|
|
241
|
-
/// output mutations back on `sessionQueue`.
|
|
214
|
+
|
|
242
215
|
private func resumeNonEssentialOutputs() {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// metadataOutputRectConverted must be read on the main thread.
|
|
255
|
-
DispatchQueue.main.async { [weak self] in
|
|
256
|
-
guard let self else { return }
|
|
257
|
-
let rect = self.cameraPreview.previewLayer.metadataOutputRectConverted(fromLayerRect: scanner)
|
|
258
|
-
self.sessionQueue.async { [weak self] in
|
|
259
|
-
self?.metadataOutput.rectOfInterest = rect
|
|
260
|
-
}
|
|
261
|
-
}
|
|
216
|
+
if textDetectionEnabled {
|
|
217
|
+
videoDataOutput.setSampleBufferDelegate(self, queue: globalOCRQueue)
|
|
218
|
+
}
|
|
219
|
+
// Restore real rect of interest
|
|
220
|
+
if let scanner = scannerFrameSize, scanner != .zero {
|
|
221
|
+
metadataOutput.rectOfInterest =
|
|
222
|
+
cameraPreview.previewLayer.metadataOutputRectConverted(fromLayerRect: scanner)
|
|
223
|
+
} else {
|
|
224
|
+
metadataOutput.rectOfInterest = CGRect(x: 0, y: 0, width: 1, height: 1)
|
|
262
225
|
}
|
|
263
226
|
}
|
|
264
|
-
|
|
227
|
+
|
|
265
228
|
func zoomPinchStart() {
|
|
266
229
|
sessionQueue.async {
|
|
267
230
|
guard let videoDevice = self.videoDeviceInput?.device else { return }
|
|
268
231
|
self.zoomStartedAt = videoDevice.videoZoomFactor
|
|
269
232
|
}
|
|
270
233
|
}
|
|
271
|
-
|
|
234
|
+
|
|
272
235
|
func zoomPinchChange(pinchScale: CGFloat) {
|
|
273
236
|
guard !pinchScale.isNaN else { return }
|
|
274
237
|
|
|
@@ -277,7 +240,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
277
240
|
|
|
278
241
|
let desiredZoomFactor = (self.zoomStartedAt / self.defaultZoomFactor(for: videoDevice)) * pinchScale
|
|
279
242
|
let zoomForDevice = self.getValidZoom(forDevice: videoDevice, zoom: desiredZoomFactor)
|
|
280
|
-
|
|
243
|
+
|
|
281
244
|
if zoomForDevice != self.normalizedZoom(for: videoDevice) {
|
|
282
245
|
// Only trigger zoom changes if it's an uncontrolled component (zoom isn't manually set)
|
|
283
246
|
// otherwise it's likely to cause issues inf. loops
|
|
@@ -288,14 +251,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
288
251
|
}
|
|
289
252
|
}
|
|
290
253
|
}
|
|
291
|
-
|
|
254
|
+
|
|
292
255
|
func update(maxZoom: Double?) {
|
|
293
256
|
self.maxZoom = maxZoom
|
|
294
257
|
|
|
295
258
|
// Re-update zoom value in case the max was increased
|
|
296
259
|
self.update(zoom: self.zoom)
|
|
297
260
|
}
|
|
298
|
-
|
|
261
|
+
|
|
299
262
|
func update(zoom: Double?) {
|
|
300
263
|
sessionQueue.async {
|
|
301
264
|
self.zoom = zoom
|
|
@@ -306,7 +269,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
306
269
|
self.setZoomFor(videoDevice, to: zoomForDevice)
|
|
307
270
|
}
|
|
308
271
|
}
|
|
309
|
-
|
|
272
|
+
|
|
310
273
|
/**
|
|
311
274
|
`desiredZoom` can be nil when we want to notify what the zoom factor really is
|
|
312
275
|
*/
|
|
@@ -327,11 +290,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
327
290
|
lastOnZoom = desiredOrCameraZoom
|
|
328
291
|
self.onZoomCallback?(["zoom": desiredOrCameraZoom])
|
|
329
292
|
}
|
|
330
|
-
|
|
293
|
+
|
|
331
294
|
func update(onZoom: RCTDirectEventBlock?) {
|
|
332
295
|
self.onZoomCallback = onZoom
|
|
333
296
|
}
|
|
334
|
-
|
|
297
|
+
|
|
335
298
|
func focus(at touchPoint: CGPoint, focusBehavior: FocusBehavior) {
|
|
336
299
|
DispatchQueue.main.async {
|
|
337
300
|
let devicePoint = self.cameraPreview.previewLayer.captureDevicePointConverted(fromLayerPoint: touchPoint)
|
|
@@ -346,7 +309,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
346
309
|
self.resetFocus = nil
|
|
347
310
|
self.focusFinished = nil
|
|
348
311
|
}
|
|
349
|
-
|
|
312
|
+
|
|
350
313
|
do {
|
|
351
314
|
try videoDevice.lockForConfiguration()
|
|
352
315
|
|
|
@@ -369,11 +332,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
369
332
|
}
|
|
370
333
|
}
|
|
371
334
|
}
|
|
372
|
-
|
|
335
|
+
|
|
373
336
|
func update(onOrientationChange: RCTDirectEventBlock?) {
|
|
374
337
|
self.onOrientationChange = onOrientationChange
|
|
375
338
|
}
|
|
376
|
-
|
|
339
|
+
|
|
377
340
|
func update(torchMode: TorchMode) {
|
|
378
341
|
sessionQueue.async {
|
|
379
342
|
self.torchMode = torchMode
|
|
@@ -390,26 +353,29 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
390
353
|
}
|
|
391
354
|
}
|
|
392
355
|
}
|
|
393
|
-
|
|
356
|
+
|
|
394
357
|
func update(flashMode: FlashMode) {
|
|
395
358
|
self.flashMode = flashMode
|
|
396
359
|
}
|
|
397
|
-
|
|
360
|
+
|
|
398
361
|
func update(maxPhotoQualityPrioritization: MaxPhotoQualityPrioritization?) {
|
|
399
362
|
guard #available(iOS 13.0, *) else { return }
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
self.beginConfiguration()
|
|
404
|
-
defer {
|
|
363
|
+
guard maxPhotoQualityPrioritization != self.maxPhotoQualityPrioritization else { return }
|
|
364
|
+
sessionQueue.async {
|
|
365
|
+
self.configurationDepth += 1
|
|
366
|
+
self.session.beginConfiguration()
|
|
367
|
+
defer {
|
|
368
|
+
self.session.commitConfiguration()
|
|
369
|
+
self.configurationDepth -= 1
|
|
370
|
+
self.handlePendingStopIfNeeded()
|
|
371
|
+
}
|
|
405
372
|
self.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization
|
|
406
373
|
self.photoOutput.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization?.avQualityPrioritization ?? .balanced
|
|
407
374
|
}
|
|
408
375
|
}
|
|
409
|
-
|
|
376
|
+
|
|
410
377
|
func update(cameraType: CameraType) {
|
|
411
|
-
sessionQueue.async {
|
|
412
|
-
guard let self else { return }
|
|
378
|
+
sessionQueue.async {
|
|
413
379
|
if self.videoDeviceInput?.device.position == cameraType.avPosition {
|
|
414
380
|
return
|
|
415
381
|
}
|
|
@@ -421,14 +387,19 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
421
387
|
let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else {
|
|
422
388
|
return
|
|
423
389
|
}
|
|
424
|
-
|
|
390
|
+
|
|
425
391
|
self.removeObservers()
|
|
426
|
-
self.
|
|
427
|
-
|
|
392
|
+
self.configurationDepth += 1
|
|
393
|
+
self.session.beginConfiguration()
|
|
394
|
+
defer {
|
|
395
|
+
self.session.commitConfiguration()
|
|
396
|
+
self.configurationDepth -= 1
|
|
397
|
+
self.handlePendingStopIfNeeded()
|
|
398
|
+
}
|
|
428
399
|
|
|
429
400
|
// Remove the existing device input first, since using the front and back camera simultaneously is not supported.
|
|
430
401
|
self.session.removeInput(currentViewDeviceInput)
|
|
431
|
-
|
|
402
|
+
|
|
432
403
|
if self.session.canAddInput(videoDeviceInput) {
|
|
433
404
|
self.session.addInput(videoDeviceInput)
|
|
434
405
|
self.resetZoom(forDevice: videoDevice)
|
|
@@ -437,14 +408,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
437
408
|
// If it fails, put back current camera
|
|
438
409
|
self.session.addInput(currentViewDeviceInput)
|
|
439
410
|
}
|
|
440
|
-
|
|
411
|
+
|
|
441
412
|
self.addObservers()
|
|
442
413
|
|
|
443
414
|
// We need to reapply the configuration after reloading the camera
|
|
444
415
|
self.update(torchMode: self.torchMode)
|
|
445
416
|
}
|
|
446
417
|
}
|
|
447
|
-
|
|
418
|
+
|
|
448
419
|
func update(resizeMode: ResizeMode) {
|
|
449
420
|
DispatchQueue.main.async {
|
|
450
421
|
switch resizeMode {
|
|
@@ -455,7 +426,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
455
426
|
}
|
|
456
427
|
}
|
|
457
428
|
}
|
|
458
|
-
|
|
429
|
+
|
|
459
430
|
func capturePicture(onWillCapture: @escaping () -> Void,
|
|
460
431
|
onSuccess: @escaping (_ imageData: Data, _ thumbnailData: Data?, _ dimensions: CMVideoDimensions) -> Void,
|
|
461
432
|
onError: @escaping (_ message: String) -> Void) {
|
|
@@ -464,41 +435,39 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
464
435
|
entering the session queue. Do this to ensure that UI elements are accessed on
|
|
465
436
|
the main thread and session configuration is done on the session queue.
|
|
466
437
|
*/
|
|
467
|
-
|
|
438
|
+
|
|
439
|
+
// Pause OCR + barcode before capturing
|
|
440
|
+
self.pauseNonEssentialOutputs()
|
|
441
|
+
|
|
468
442
|
DispatchQueue.main.async { [weak self] in
|
|
469
443
|
guard let self = self else {
|
|
470
444
|
onError("Camera was deallocated")
|
|
471
445
|
return
|
|
472
446
|
}
|
|
473
|
-
|
|
447
|
+
|
|
474
448
|
let videoPreviewLayerOrientation =
|
|
475
449
|
self.videoOrientation(from: self.deviceOrientation) ?? self.cameraPreview.previewLayer.connection?.videoOrientation
|
|
476
|
-
|
|
450
|
+
|
|
477
451
|
self.sessionQueue.async { [weak self] in
|
|
478
452
|
guard let self = self else {
|
|
479
453
|
onError("Camera was deallocated")
|
|
480
454
|
return
|
|
481
455
|
}
|
|
482
456
|
|
|
483
|
-
guard self.inProgressPhotoCaptureDelegates.isEmpty else {
|
|
484
|
-
onError("Capture already in progress")
|
|
485
|
-
return
|
|
486
|
-
}
|
|
487
|
-
|
|
488
457
|
// Validate that the session is ready for photo capture
|
|
489
458
|
guard self.session.isRunning else {
|
|
490
459
|
print("Cannot capture photo: session is not running")
|
|
491
460
|
onError("Camera session is not running")
|
|
492
461
|
return
|
|
493
462
|
}
|
|
494
|
-
|
|
463
|
+
|
|
495
464
|
// Ensure photo output has an active video connection
|
|
496
465
|
guard let photoOutputConnection = self.photoOutput.connection(with: .video) else {
|
|
497
466
|
print("Cannot capture photo: no video connection available")
|
|
498
467
|
onError("Camera connection is not available")
|
|
499
468
|
return
|
|
500
469
|
}
|
|
501
|
-
|
|
470
|
+
|
|
502
471
|
// Verify the connection is active and enabled
|
|
503
472
|
guard photoOutputConnection.isActive && photoOutputConnection.isEnabled else {
|
|
504
473
|
print("Cannot capture photo: video connection is not active or enabled")
|
|
@@ -511,9 +480,6 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
511
480
|
photoOutputConnection.videoOrientation = videoPreviewLayerOrientation
|
|
512
481
|
}
|
|
513
482
|
|
|
514
|
-
// Pause OCR + barcode work on the session queue, right before capture.
|
|
515
|
-
self.pauseNonEssentialOutputs()
|
|
516
|
-
|
|
517
483
|
let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
|
|
518
484
|
if #available(iOS 13.0, *) {
|
|
519
485
|
settings.photoQualityPrioritization = self.photoOutput.maxPhotoQualityPrioritization
|
|
@@ -527,30 +493,32 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
527
493
|
with: settings,
|
|
528
494
|
onWillCapture: onWillCapture,
|
|
529
495
|
onCaptureSuccess: { [weak self] uniqueID, imageData, thumbnailData, dimensions in
|
|
530
|
-
|
|
496
|
+
// Use weak self to prevent crash if camera is deallocated during capture
|
|
497
|
+
self?.inProgressPhotoCaptureDelegates[uniqueID] = nil
|
|
531
498
|
onSuccess(imageData, thumbnailData, dimensions)
|
|
532
499
|
self?.resumeNonEssentialOutputs()
|
|
533
500
|
},
|
|
534
501
|
onCaptureError: { [weak self] uniqueID, errorMessage in
|
|
535
|
-
|
|
502
|
+
// Use weak self to prevent crash if camera is deallocated during capture
|
|
503
|
+
self?.inProgressPhotoCaptureDelegates[uniqueID] = nil
|
|
536
504
|
onError(errorMessage)
|
|
537
505
|
self?.resumeNonEssentialOutputs()
|
|
538
506
|
}
|
|
539
507
|
)
|
|
540
|
-
|
|
508
|
+
|
|
541
509
|
self.inProgressPhotoCaptureDelegates[photoCaptureDelegate.requestedPhotoSettings.uniqueID] = photoCaptureDelegate
|
|
542
510
|
self.photoOutput.capturePhoto(with: settings, delegate: photoCaptureDelegate)
|
|
543
511
|
}
|
|
544
512
|
}
|
|
545
513
|
}
|
|
546
|
-
|
|
514
|
+
|
|
547
515
|
// MARK: - Barcode scanning
|
|
548
516
|
func isBarcodeScannerEnabled(_ isEnabled: Bool,
|
|
549
517
|
supportedBarcodeTypes supportedBarcodeType: [CodeFormat],
|
|
550
518
|
onBarcodeRead: ((_ barcode: String,_ codeFormat:CodeFormat) -> Void)?) {
|
|
551
519
|
sessionQueue.async {
|
|
552
520
|
self.onBarcodeRead = onBarcodeRead
|
|
553
|
-
|
|
521
|
+
|
|
554
522
|
let availableTypes = self.metadataOutput.availableMetadataObjectTypes
|
|
555
523
|
let newTypes: [AVMetadataObject.ObjectType]
|
|
556
524
|
if isEnabled && onBarcodeRead != nil {
|
|
@@ -567,11 +535,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
567
535
|
}
|
|
568
536
|
}
|
|
569
537
|
}
|
|
570
|
-
|
|
538
|
+
|
|
571
539
|
func update(barcodeFrameSize: CGSize?) {
|
|
572
540
|
self.barcodeFrameSize = barcodeFrameSize
|
|
573
541
|
}
|
|
574
|
-
|
|
542
|
+
|
|
575
543
|
func update(scannerFrameSize: CGRect?) {
|
|
576
544
|
guard self.scannerFrameSize != scannerFrameSize else { return }
|
|
577
545
|
self.sessionQueue.async {
|
|
@@ -579,11 +547,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
579
547
|
if !self.session.isRunning {
|
|
580
548
|
return
|
|
581
549
|
}
|
|
582
|
-
|
|
550
|
+
|
|
583
551
|
DispatchQueue.main.async {
|
|
584
552
|
var visibleRect: CGRect?
|
|
585
|
-
if
|
|
586
|
-
visibleRect = self.cameraPreview.previewLayer.metadataOutputRectConverted(fromLayerRect: scannerFrameSize)
|
|
553
|
+
if scannerFrameSize != nil && scannerFrameSize != .zero {
|
|
554
|
+
visibleRect = self.cameraPreview.previewLayer.metadataOutputRectConverted(fromLayerRect: scannerFrameSize!)
|
|
587
555
|
}
|
|
588
556
|
|
|
589
557
|
self.sessionQueue.async {
|
|
@@ -598,7 +566,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
598
566
|
}
|
|
599
567
|
}
|
|
600
568
|
}
|
|
601
|
-
|
|
569
|
+
|
|
602
570
|
|
|
603
571
|
func isTextDetectionEnabled(_ isEnabled: Bool, onTextRead: ((String) -> Void)?) {
|
|
604
572
|
sessionQueue.async {
|
|
@@ -613,26 +581,26 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
613
581
|
self.textRequest?.recognitionLanguages = ["en", "fr", "de", "es", "vi"]
|
|
614
582
|
self.textRequest?.recognitionLevel = .accurate
|
|
615
583
|
self.textRequest?.usesLanguageCorrection = false
|
|
616
|
-
|
|
584
|
+
|
|
617
585
|
self.videoDataOutput.alwaysDiscardsLateVideoFrames = true
|
|
618
586
|
self.videoDataOutput.setSampleBufferDelegate(self, queue: globalOCRQueue)
|
|
619
587
|
self.videoDataOutput.videoSettings = [
|
|
620
588
|
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
|
621
589
|
]
|
|
622
|
-
|
|
590
|
+
|
|
623
591
|
} else {
|
|
624
592
|
self.textRequest = nil
|
|
625
593
|
}
|
|
626
594
|
}
|
|
627
595
|
}
|
|
628
|
-
|
|
596
|
+
|
|
629
597
|
// AVCaptureVideoDataOutputSampleBufferDelegate
|
|
630
598
|
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
|
|
631
599
|
guard textDetectionEnabled, let request = textRequest else { return }
|
|
632
600
|
let now = Date()
|
|
633
601
|
if now.timeIntervalSince(lastTextProcess) < textThrottle { return }
|
|
634
602
|
lastTextProcess = now
|
|
635
|
-
|
|
603
|
+
|
|
636
604
|
globalOCRQueue.async {
|
|
637
605
|
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
|
|
638
606
|
var requestOptions: [VNImageOption: Any] = [:]
|
|
@@ -645,7 +613,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
645
613
|
}
|
|
646
614
|
}
|
|
647
615
|
}
|
|
648
|
-
|
|
616
|
+
|
|
649
617
|
// MARK: - AVCaptureMetadataOutputObjectsDelegate
|
|
650
618
|
|
|
651
619
|
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
|
@@ -659,7 +627,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
659
627
|
|
|
660
628
|
onBarcodeRead?(codeStringValue,barcodeType)
|
|
661
629
|
}
|
|
662
|
-
|
|
630
|
+
|
|
663
631
|
// MARK: - Private
|
|
664
632
|
|
|
665
633
|
private func videoOrientation(from deviceOrientation: UIDeviceOrientation) -> AVCaptureVideoOrientation? {
|
|
@@ -678,7 +646,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
678
646
|
@unknown default: return nil
|
|
679
647
|
}
|
|
680
648
|
}
|
|
681
|
-
|
|
649
|
+
|
|
682
650
|
private func videoOrientation(from interfaceOrientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
|
|
683
651
|
switch interfaceOrientation {
|
|
684
652
|
case .portrait:
|
|
@@ -693,14 +661,14 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
693
661
|
@unknown default: return .portrait
|
|
694
662
|
}
|
|
695
663
|
}
|
|
696
|
-
|
|
664
|
+
|
|
697
665
|
private func getBestDevice(for cameraType: CameraType) -> AVCaptureDevice? {
|
|
698
666
|
if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: cameraType.avPosition) {
|
|
699
667
|
return device // single-lens/physical device
|
|
700
668
|
}
|
|
701
669
|
return nil
|
|
702
670
|
}
|
|
703
|
-
|
|
671
|
+
|
|
704
672
|
private func defaultZoomFactor(for videoDevice: AVCaptureDevice) -> CGFloat {
|
|
705
673
|
let fallback = 1.0
|
|
706
674
|
guard #available(iOS 13.0, *) else { return fallback }
|
|
@@ -761,9 +729,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
761
729
|
motionManager = CMMotionManager()
|
|
762
730
|
motionManager?.accelerometerUpdateInterval = 0.2
|
|
763
731
|
motionManager?.gyroUpdateInterval = 0.2
|
|
764
|
-
|
|
765
|
-
motionManager?.startAccelerometerUpdates(to: OperationQueue(), withHandler: { [weak self] (accelerometerData, error) -> Void in
|
|
766
|
-
guard let self else { return }
|
|
732
|
+
motionManager?.startAccelerometerUpdates(to: OperationQueue(), withHandler: { (accelerometerData, error) -> Void in
|
|
767
733
|
guard error == nil else {
|
|
768
734
|
print("\(error!)")
|
|
769
735
|
return
|
|
@@ -774,13 +740,12 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
774
740
|
}
|
|
775
741
|
|
|
776
742
|
guard let newOrientation = self.deviceOrientation(from: accelerometerData.acceleration),
|
|
777
|
-
newOrientation != self.deviceOrientation
|
|
778
|
-
let orientation = Orientation(from: newOrientation) else {
|
|
743
|
+
newOrientation != self.deviceOrientation else {
|
|
779
744
|
return
|
|
780
745
|
}
|
|
781
746
|
|
|
782
747
|
self.deviceOrientation = newOrientation
|
|
783
|
-
self.onOrientationChange?(["orientation":
|
|
748
|
+
self.onOrientationChange?(["orientation": Orientation.init(from: newOrientation)!.rawValue])
|
|
784
749
|
})
|
|
785
750
|
#endif
|
|
786
751
|
}
|
|
@@ -805,34 +770,30 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
805
770
|
// MARK: Private observers
|
|
806
771
|
|
|
807
772
|
private func addObservers() {
|
|
808
|
-
|
|
809
|
-
adjustingFocusObservation = videoDeviceInput?.device.observe(\.isAdjustingFocus,
|
|
810
|
-
options: .new,
|
|
811
|
-
changeHandler: { [weak self] _, change in
|
|
812
|
-
guard let self, let isFocusing = change.newValue else { return }
|
|
773
|
+
guard adjustingFocusObservation == nil else { return }
|
|
813
774
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
775
|
+
adjustingFocusObservation = videoDeviceInput?.device.observe(\.isAdjustingFocus,
|
|
776
|
+
options: .new,
|
|
777
|
+
changeHandler: { [weak self] _, change in
|
|
778
|
+
guard let self, let isFocusing = change.newValue else { return }
|
|
817
779
|
|
|
818
|
-
|
|
780
|
+
self.isAdjustingFocus(isFocusing: isFocusing)
|
|
781
|
+
})
|
|
782
|
+
|
|
783
|
+
NotificationCenter.default.addObserver(forName: .AVCaptureDeviceSubjectAreaDidChange,
|
|
784
|
+
object: videoDeviceInput?.device,
|
|
785
|
+
queue: nil,
|
|
786
|
+
using: { [weak self] notification in self?.subjectAreaDidChange(notification: notification) })
|
|
787
|
+
NotificationCenter.default.addObserver(forName: .AVCaptureSessionRuntimeError,
|
|
788
|
+
object: session,
|
|
789
|
+
queue: nil,
|
|
790
|
+
using: { [weak self] notification in self?.sessionRuntimeError(notification: notification) })
|
|
819
791
|
|
|
820
|
-
let subjectAreaToken = NotificationCenter.default.addObserver(
|
|
821
|
-
forName: .AVCaptureDeviceSubjectAreaDidChange,
|
|
822
|
-
object: videoDeviceInput?.device,
|
|
823
|
-
queue: nil,
|
|
824
|
-
using: { [weak self] notification in self?.subjectAreaDidChange(notification: notification) })
|
|
825
|
-
let runtimeErrorToken = NotificationCenter.default.addObserver(
|
|
826
|
-
forName: .AVCaptureSessionRuntimeError,
|
|
827
|
-
object: session,
|
|
828
|
-
queue: nil,
|
|
829
|
-
using: { [weak self] notification in self?.sessionRuntimeError(notification: notification) })
|
|
830
|
-
notificationObservers = [subjectAreaToken, runtimeErrorToken]
|
|
831
792
|
}
|
|
832
793
|
|
|
833
794
|
private func removeObservers() {
|
|
834
|
-
|
|
835
|
-
|
|
795
|
+
// swiftlint:disable:next notification_center_detachment
|
|
796
|
+
NotificationCenter.default.removeObserver(self)
|
|
836
797
|
|
|
837
798
|
adjustingFocusObservation?.invalidate()
|
|
838
799
|
adjustingFocusObservation = nil
|
|
@@ -868,99 +829,39 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
868
829
|
|
|
869
830
|
print("Capture session runtime error: \(error)")
|
|
870
831
|
|
|
871
|
-
// Automatically try to restart the session if media services were reset
|
|
872
|
-
// (After a reset the session is NOT running, so the restart is routed
|
|
873
|
-
// through the guarded helper, which only starts when it is safe to.)
|
|
832
|
+
// Automatically try to restart the session running if media services were reset
|
|
874
833
|
if error.code == .mediaServicesWereReset {
|
|
875
|
-
sessionQueue.async {
|
|
876
|
-
self
|
|
834
|
+
sessionQueue.async {
|
|
835
|
+
if self.session.isRunning {
|
|
836
|
+
self.session.startRunning()
|
|
837
|
+
}
|
|
877
838
|
}
|
|
878
839
|
}
|
|
879
840
|
}
|
|
880
841
|
|
|
881
842
|
func startCamera() {
|
|
882
|
-
sessionQueue.async {
|
|
883
|
-
self
|
|
843
|
+
self.sessionQueue.async {
|
|
844
|
+
if !self.session.isRunning {
|
|
845
|
+
self.session.startRunning()
|
|
846
|
+
}
|
|
884
847
|
}
|
|
885
848
|
}
|
|
886
849
|
|
|
887
850
|
func stopCamera() {
|
|
888
|
-
sessionQueue.async {
|
|
889
|
-
self
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
// MARK: - Session configuration transaction helpers
|
|
894
|
-
|
|
895
|
-
/// Debug-only tripwire enforcing the core invariant of this class: EVERY
|
|
896
|
-
/// AVCaptureSession mutation and start/stop happens on `sessionQueue`. The whole
|
|
897
|
-
/// "startRunning may not be called between beginConfiguration and
|
|
898
|
-
/// commitConfiguration" crash class is prevented precisely because nothing
|
|
899
|
-
/// touches the session off this serial queue. If a future change — or an
|
|
900
|
-
/// external caller such as a mini-app native bridge — ever reaches one of these
|
|
901
|
-
/// methods on the wrong queue, this fails loudly in DEBUG/QA instead of
|
|
902
|
-
/// crashing randomly in production. Compiled out of release builds, so it can
|
|
903
|
-
/// never add a production crash.
|
|
904
|
-
private func assertOnSessionQueue() {
|
|
905
|
-
#if DEBUG
|
|
906
|
-
dispatchPrecondition(condition: .onQueue(sessionQueue))
|
|
907
|
-
#endif
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
/// Opens a session configuration transaction and tracks nesting depth.
|
|
911
|
-
/// MUST be balanced by `commitConfiguration()` (use `defer`) and MUST run on
|
|
912
|
-
/// `sessionQueue`.
|
|
913
|
-
private func beginConfiguration() {
|
|
914
|
-
assertOnSessionQueue()
|
|
915
|
-
configurationDepth += 1
|
|
916
|
-
session.beginConfiguration()
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
/// Closes a session configuration transaction and flushes a stop that was
|
|
920
|
-
/// requested while the transaction was open. MUST run on `sessionQueue`.
|
|
921
|
-
private func commitConfiguration() {
|
|
922
|
-
assertOnSessionQueue()
|
|
923
|
-
session.commitConfiguration()
|
|
924
|
-
configurationDepth -= 1
|
|
925
|
-
handlePendingStopIfNeeded()
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
/// Starts the session only when it is safe to do so. MUST run on `sessionQueue`.
|
|
929
|
-
/// Because every begin/commit pair is synchronous on the serial session queue,
|
|
930
|
-
/// `configurationDepth` is always 0 at the start of a fresh queue block — the
|
|
931
|
-
/// depth check is therefore defence-in-depth against ever calling this from
|
|
932
|
-
/// inside an open transaction.
|
|
933
|
-
private func startSessionIfNeeded() {
|
|
934
|
-
assertOnSessionQueue()
|
|
935
|
-
guard setupResult == .success,
|
|
936
|
-
configurationDepth == 0,
|
|
937
|
-
!pendingStop,
|
|
938
|
-
!session.isRunning else {
|
|
939
|
-
return
|
|
940
|
-
}
|
|
941
|
-
session.startRunning()
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/// Stops the session, deferring until the current transaction commits if one
|
|
945
|
-
/// is open. MUST run on `sessionQueue`.
|
|
946
|
-
private func stopSessionIfNeeded() {
|
|
947
|
-
assertOnSessionQueue()
|
|
948
|
-
guard session.isRunning else { return }
|
|
949
|
-
if configurationDepth > 0 {
|
|
950
|
-
pendingStop = true
|
|
951
|
-
return
|
|
851
|
+
self.sessionQueue.async {
|
|
852
|
+
if self.session.isRunning {
|
|
853
|
+
self.session.stopRunning()
|
|
854
|
+
}
|
|
952
855
|
}
|
|
953
|
-
session.stopRunning()
|
|
954
856
|
}
|
|
955
857
|
|
|
956
858
|
// MARK: - Private helper for safe session stop
|
|
957
|
-
|
|
859
|
+
|
|
958
860
|
private func handlePendingStopIfNeeded() {
|
|
959
861
|
// Must be called on sessionQueue after configuration completes
|
|
960
|
-
assertOnSessionQueue()
|
|
961
862
|
// Only execute if all configurations are done (depth == 0)
|
|
962
863
|
guard configurationDepth == 0 && pendingStop else { return }
|
|
963
|
-
|
|
864
|
+
|
|
964
865
|
pendingStop = false
|
|
965
866
|
if session.isRunning {
|
|
966
867
|
session.stopRunning()
|
package/package.json
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
2
|
+
"name": "@momo-kits/camerakit",
|
|
3
|
+
"version": "0.161.2-beta.7",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/teslamotors/react-native-camera-kit.git"
|
|
7
|
+
},
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
},
|
|
11
|
+
"description": "A high performance, fully featured, rock solid camera library for React Native applications",
|
|
12
|
+
"nativePackage": true,
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "echo",
|
|
15
|
+
"test": "jest",
|
|
16
|
+
"lint": "yarn eslint -c .eslintrc.js"
|
|
17
|
+
},
|
|
18
|
+
"main": "./src/index.ts",
|
|
19
|
+
"dependencies": {},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"react": "19.0.0",
|
|
23
|
+
"react-native": "0.80.1"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": "*",
|
|
27
|
+
"react-native": "*"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"codegenConfig": {
|
|
33
|
+
"name": "rncamerakit_specs",
|
|
34
|
+
"type": "all",
|
|
35
|
+
"jsSrcsDir": "src/specs",
|
|
36
|
+
"android": {
|
|
37
|
+
"javaPackageName": "com.rncamerakit"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"packageManager": "yarn@1.22.22"
|
|
41
|
+
}
|