@momo-kits/camerakit 0.161.2-beta.25 → 0.161.2-beta.26
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.
|
@@ -71,6 +71,10 @@ public class CameraView: UIView {
|
|
|
71
71
|
|
|
72
72
|
// This is used to delay camera setup until we have both granted permission & received default props
|
|
73
73
|
var hasCameraBeenSetup = false
|
|
74
|
+
// [CKCameraKit][perf] TEMP measurement — remove before publish. Captured when the
|
|
75
|
+
// view is constructed so we can see how long the OUTSIDE path (permission grant +
|
|
76
|
+
// first props) takes before camera.setup() is even called.
|
|
77
|
+
private let viewCreatedAt = CFAbsoluteTimeGetCurrent()
|
|
74
78
|
var hasPropBeenSetup = false {
|
|
75
79
|
didSet {
|
|
76
80
|
setupCamera()
|
|
@@ -84,6 +88,8 @@ public class CameraView: UIView {
|
|
|
84
88
|
private func setupCamera() {
|
|
85
89
|
if hasPropBeenSetup && hasPermissionBeenGranted && !hasCameraBeenSetup {
|
|
86
90
|
hasCameraBeenSetup = true
|
|
91
|
+
// [CKCameraKit][perf] TEMP measurement — remove before publish.
|
|
92
|
+
print(String(format: "[CKCameraKit][perf] view→setupCalled=%.0fms (permission+props before setup)", (CFAbsoluteTimeGetCurrent() - viewCreatedAt) * 1000))
|
|
87
93
|
#if targetEnvironment(macCatalyst)
|
|
88
94
|
// Force front camera on Mac Catalyst during initial setup
|
|
89
95
|
camera.setup(cameraType: .front, supportedBarcodeType: scanBarcode && onReadCode != nil ? supportedBarcodeType : [])
|
|
@@ -72,6 +72,23 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
72
72
|
|
|
73
73
|
// MARK: - Lifecycle
|
|
74
74
|
|
|
75
|
+
override init() {
|
|
76
|
+
super.init()
|
|
77
|
+
#if DEBUG
|
|
78
|
+
assert(Thread.isMainThread, "RealCamera must be initialized on the main thread (it wires the preview CALayer).")
|
|
79
|
+
#endif
|
|
80
|
+
// Wire the preview to the (idle, never-started) session AND set its gravity
|
|
81
|
+
// ONCE here at init, on the main thread, before anything starts. Benefits:
|
|
82
|
+
// (a) the preview renders the instant the first frame arrives (~camera warm-up)
|
|
83
|
+
// instead of waiting for a late post-setup attach → less black.
|
|
84
|
+
// (b) assigning previewLayer.session opens an implicit AVFoundation config
|
|
85
|
+
// transaction; doing it now (session idle, no startRunning yet, never
|
|
86
|
+
// reassigned) makes it impossible to race startRunning() → the begin/commit
|
|
87
|
+
// crash class is closed by construction, on every device.
|
|
88
|
+
cameraPreview.session = session
|
|
89
|
+
cameraPreview.previewLayer.videoGravity = .resizeAspectFill
|
|
90
|
+
}
|
|
91
|
+
|
|
75
92
|
func cameraRemovedFromSuperview() {
|
|
76
93
|
sessionQueue.async { [weak self] in
|
|
77
94
|
guard let self, self.setupResult == .success else { return }
|
|
@@ -114,8 +131,11 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
114
131
|
// MARK: - Public
|
|
115
132
|
|
|
116
133
|
func setup(cameraType: CameraType, supportedBarcodeType: [CodeFormat]) {
|
|
134
|
+
// [CKCameraKit][perf] TEMP measurement — remove before publish.
|
|
135
|
+
let tCall = CFAbsoluteTimeGetCurrent()
|
|
117
136
|
sessionQueue.async { [weak self] in
|
|
118
137
|
guard let self else { return }
|
|
138
|
+
let tBlock = CFAbsoluteTimeGetCurrent()
|
|
119
139
|
|
|
120
140
|
// Idempotency guard: a RealCamera configures its session exactly once.
|
|
121
141
|
// A fresh instance is created on every mount / Fabric recycle, so this
|
|
@@ -123,38 +143,41 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
123
143
|
// handled by startCamera() -> startSessionIfNeeded().
|
|
124
144
|
guard self.setupResult == .notStarted else { return }
|
|
125
145
|
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
146
|
+
// Configure the WHOLE session (inputs, preset, outputs) inside a single
|
|
147
|
+
// begin/commit transaction on the session queue. The preset is set here
|
|
148
|
+
// and never on the main queue, so the startRunning() below can no longer
|
|
149
|
+
// observe the session mid-configuration from another thread.
|
|
150
|
+
//
|
|
151
|
+
// This is the root-cause fix for:
|
|
152
|
+
// "[AVCaptureSession startRunning] startRunning may not be called between
|
|
153
|
+
// calls to beginConfiguration and commitConfiguration".
|
|
154
|
+
self.setupResult = self.configureSession(cameraType: cameraType,
|
|
155
|
+
supportedBarcodeType: supportedBarcodeType)
|
|
156
|
+
let tConfigured = CFAbsoluteTimeGetCurrent()
|
|
157
|
+
|
|
133
158
|
guard self.setupResult == .success else { return }
|
|
134
159
|
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
// session
|
|
160
|
+
// Set the preview orientation on the main queue NOW — BEFORE the (~145ms)
|
|
161
|
+
// startRunning below — so the very first frame renders at the correct
|
|
162
|
+
// orientation instead of appearing once and then snapping ("giật 1 nhịp").
|
|
163
|
+
// The preview's `.session` and gravity are already wired at init; this is a
|
|
164
|
+
// pure connection-property write (no session config transaction → no race
|
|
165
|
+
// with startRunning). The connection exists because configureSession has
|
|
166
|
+
// committed the video input above.
|
|
140
167
|
DispatchQueue.main.async { [weak self] in
|
|
141
168
|
guard let self else { return }
|
|
142
|
-
self.cameraPreview.session = self.session
|
|
143
|
-
self.cameraPreview.previewLayer.videoGravity = .resizeAspectFill
|
|
144
169
|
self.setVideoOrientationToInterfaceOrientation()
|
|
145
170
|
}
|
|
146
171
|
|
|
147
|
-
//
|
|
172
|
+
// Order matches the previous behaviour: start, then observe, then torch.
|
|
148
173
|
self.startSessionIfNeeded()
|
|
149
|
-
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
self.configureAdditionalOutputs(supportedBarcodeType: supportedBarcodeType)
|
|
157
|
-
|
|
174
|
+
let tStarted = CFAbsoluteTimeGetCurrent()
|
|
175
|
+
// [CKCameraKit][perf] TEMP measurement — remove before publish.
|
|
176
|
+
print(String(format: "[CKCameraKit][perf] queueWait=%.0fms configure=%.0fms startRunning=%.0fms setupTotal=%.0fms",
|
|
177
|
+
(tBlock - tCall) * 1000,
|
|
178
|
+
(tConfigured - tBlock) * 1000,
|
|
179
|
+
(tStarted - tConfigured) * 1000,
|
|
180
|
+
(tStarted - tCall) * 1000))
|
|
158
181
|
self.addObservers()
|
|
159
182
|
self.update(torchMode: self.torchMode)
|
|
160
183
|
}
|
|
@@ -166,11 +189,10 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
166
189
|
|
|
167
190
|
// MARK: - Private optimization methods
|
|
168
191
|
|
|
169
|
-
///
|
|
170
|
-
/// transaction. Must
|
|
171
|
-
///
|
|
172
|
-
|
|
173
|
-
private func configureBaseSession(cameraType: CameraType) -> SetupResult {
|
|
192
|
+
/// Configures the video input, session preset and every output in ONE atomic
|
|
193
|
+
/// transaction. Must be called on `sessionQueue`. Order is significant:
|
|
194
|
+
/// beginConfiguration -> addInput -> set preset -> addOutputs -> commitConfiguration.
|
|
195
|
+
private func configureSession(cameraType: CameraType, supportedBarcodeType: [CodeFormat]) -> SetupResult {
|
|
174
196
|
assertOnSessionQueue()
|
|
175
197
|
guard let videoDevice = self.getBestDevice(for: cameraType),
|
|
176
198
|
let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else {
|
|
@@ -180,7 +202,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
180
202
|
beginConfiguration()
|
|
181
203
|
defer { commitConfiguration() }
|
|
182
204
|
|
|
183
|
-
// Video input
|
|
205
|
+
// 1. Video input
|
|
184
206
|
guard session.canAddInput(videoDeviceInput) else {
|
|
185
207
|
return .sessionConfigurationFailed
|
|
186
208
|
}
|
|
@@ -188,33 +210,15 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
188
210
|
self.videoDeviceInput = videoDeviceInput
|
|
189
211
|
self.resetZoom(forDevice: videoDevice)
|
|
190
212
|
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
if
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return .success
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/// Phase 2 of setup: the .photo preset + photo / barcode / OCR outputs, in ONE
|
|
201
|
-
/// transaction on `sessionQueue`. Runs AFTER the session is already running and
|
|
202
|
-
/// the preview is attached, so none of this work delays the first frame. Adding
|
|
203
|
-
/// outputs to a running session inside begin/commit is legal; the crash fix is
|
|
204
|
-
/// preserved because startRunning() is never called while a transaction is open.
|
|
205
|
-
private func configureAdditionalOutputs(supportedBarcodeType: [CodeFormat]) {
|
|
206
|
-
assertOnSessionQueue()
|
|
207
|
-
beginConfiguration()
|
|
208
|
-
defer { commitConfiguration() }
|
|
209
|
-
|
|
210
|
-
// Upgrade to the full still-capture preset now that preview is rendering.
|
|
211
|
-
// `.photo` is supported by every camera device; if it somehow is not, we
|
|
212
|
-
// keep the `.high` preset from phase 1 rather than crashing.
|
|
213
|
+
// 2. Preset — set inside the transaction with the device input already
|
|
214
|
+
// present, so `canSetSessionPreset` reflects the real device. `.photo` is
|
|
215
|
+
// supported by every camera device; if it somehow is not, we keep whatever
|
|
216
|
+
// preset the session defaults to rather than crashing.
|
|
213
217
|
if session.canSetSessionPreset(.photo) {
|
|
214
218
|
session.sessionPreset = .photo
|
|
215
219
|
}
|
|
216
220
|
|
|
217
|
-
// Photo output
|
|
221
|
+
// 3. Photo output
|
|
218
222
|
if #available(iOS 13.0, *) {
|
|
219
223
|
if let maxPhotoQualityPrioritization = maxPhotoQualityPrioritization {
|
|
220
224
|
photoOutput.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization.avQualityPrioritization
|
|
@@ -229,7 +233,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
229
233
|
}
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
// Metadata output for barcode scanning
|
|
236
|
+
// 4. Metadata output for barcode scanning
|
|
233
237
|
if self.session.canAddOutput(metadataOutput) {
|
|
234
238
|
self.session.addOutput(metadataOutput)
|
|
235
239
|
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
|
@@ -242,10 +246,12 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
242
246
|
metadataOutput.metadataObjectTypes = filteredTypes
|
|
243
247
|
}
|
|
244
248
|
|
|
245
|
-
// Video data output for text / MRZ detection
|
|
249
|
+
// 5. Video data output for text / MRZ detection
|
|
246
250
|
if textRequest != nil && self.session.canAddOutput(self.videoDataOutput) {
|
|
247
251
|
self.session.addOutput(self.videoDataOutput)
|
|
248
252
|
}
|
|
253
|
+
|
|
254
|
+
return .success
|
|
249
255
|
}
|
|
250
256
|
|
|
251
257
|
// MARK: - Pause / Resume non-essential outputs
|