@momo-kits/camerakit 0.161.2-beta.25 → 0.161.2-beta.27
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.
|
@@ -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 }
|
|
@@ -123,38 +140,33 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
123
140
|
// handled by startCamera() -> startSessionIfNeeded().
|
|
124
141
|
guard self.setupResult == .notStarted else { return }
|
|
125
142
|
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
143
|
+
// Configure the WHOLE session (inputs, preset, outputs) inside a single
|
|
144
|
+
// begin/commit transaction on the session queue. The preset is set here
|
|
145
|
+
// and never on the main queue, so the startRunning() below can no longer
|
|
146
|
+
// observe the session mid-configuration from another thread.
|
|
147
|
+
//
|
|
148
|
+
// This is the root-cause fix for:
|
|
149
|
+
// "[AVCaptureSession startRunning] startRunning may not be called between
|
|
150
|
+
// calls to beginConfiguration and commitConfiguration".
|
|
151
|
+
self.setupResult = self.configureSession(cameraType: cameraType,
|
|
152
|
+
supportedBarcodeType: supportedBarcodeType)
|
|
153
|
+
|
|
133
154
|
guard self.setupResult == .success else { return }
|
|
134
155
|
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
// session
|
|
156
|
+
// Set the preview orientation on the main queue NOW — BEFORE the (~145ms)
|
|
157
|
+
// startRunning below — so the very first frame renders at the correct
|
|
158
|
+
// orientation instead of appearing once and then snapping ("giật 1 nhịp").
|
|
159
|
+
// The preview's `.session` and gravity are already wired at init; this is a
|
|
160
|
+
// pure connection-property write (no session config transaction → no race
|
|
161
|
+
// with startRunning). The connection exists because configureSession has
|
|
162
|
+
// committed the video input above.
|
|
140
163
|
DispatchQueue.main.async { [weak self] in
|
|
141
164
|
guard let self else { return }
|
|
142
|
-
self.cameraPreview.session = self.session
|
|
143
|
-
self.cameraPreview.previewLayer.videoGravity = .resizeAspectFill
|
|
144
165
|
self.setVideoOrientationToInterfaceOrientation()
|
|
145
166
|
}
|
|
146
167
|
|
|
147
|
-
//
|
|
168
|
+
// Order matches the previous behaviour: start, then observe, then torch.
|
|
148
169
|
self.startSessionIfNeeded()
|
|
149
|
-
|
|
150
|
-
// PHASE 2 — add the heavier outputs and upgrade to the .photo preset in a
|
|
151
|
-
// SECOND transaction on this same serial queue, now that preview is live.
|
|
152
|
-
// Adding outputs to an already-running session inside begin/commit is
|
|
153
|
-
// legal; because startRunning() is NEVER called while configurationDepth
|
|
154
|
-
// > 0 (see startSessionIfNeeded), the "startRunning may not be called
|
|
155
|
-
// between beginConfiguration and commitConfiguration" crash stays fixed.
|
|
156
|
-
self.configureAdditionalOutputs(supportedBarcodeType: supportedBarcodeType)
|
|
157
|
-
|
|
158
170
|
self.addObservers()
|
|
159
171
|
self.update(torchMode: self.torchMode)
|
|
160
172
|
}
|
|
@@ -166,11 +178,10 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
166
178
|
|
|
167
179
|
// MARK: - Private optimization methods
|
|
168
180
|
|
|
169
|
-
///
|
|
170
|
-
/// transaction. Must
|
|
171
|
-
///
|
|
172
|
-
|
|
173
|
-
private func configureBaseSession(cameraType: CameraType) -> SetupResult {
|
|
181
|
+
/// Configures the video input, session preset and every output in ONE atomic
|
|
182
|
+
/// transaction. Must be called on `sessionQueue`. Order is significant:
|
|
183
|
+
/// beginConfiguration -> addInput -> set preset -> addOutputs -> commitConfiguration.
|
|
184
|
+
private func configureSession(cameraType: CameraType, supportedBarcodeType: [CodeFormat]) -> SetupResult {
|
|
174
185
|
assertOnSessionQueue()
|
|
175
186
|
guard let videoDevice = self.getBestDevice(for: cameraType),
|
|
176
187
|
let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else {
|
|
@@ -180,7 +191,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
180
191
|
beginConfiguration()
|
|
181
192
|
defer { commitConfiguration() }
|
|
182
193
|
|
|
183
|
-
// Video input
|
|
194
|
+
// 1. Video input
|
|
184
195
|
guard session.canAddInput(videoDeviceInput) else {
|
|
185
196
|
return .sessionConfigurationFailed
|
|
186
197
|
}
|
|
@@ -188,33 +199,15 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
188
199
|
self.videoDeviceInput = videoDeviceInput
|
|
189
200
|
self.resetZoom(forDevice: videoDevice)
|
|
190
201
|
|
|
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.
|
|
202
|
+
// 2. Preset — set inside the transaction with the device input already
|
|
203
|
+
// present, so `canSetSessionPreset` reflects the real device. `.photo` is
|
|
204
|
+
// supported by every camera device; if it somehow is not, we keep whatever
|
|
205
|
+
// preset the session defaults to rather than crashing.
|
|
213
206
|
if session.canSetSessionPreset(.photo) {
|
|
214
207
|
session.sessionPreset = .photo
|
|
215
208
|
}
|
|
216
209
|
|
|
217
|
-
// Photo output
|
|
210
|
+
// 3. Photo output
|
|
218
211
|
if #available(iOS 13.0, *) {
|
|
219
212
|
if let maxPhotoQualityPrioritization = maxPhotoQualityPrioritization {
|
|
220
213
|
photoOutput.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization.avQualityPrioritization
|
|
@@ -229,7 +222,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
229
222
|
}
|
|
230
223
|
}
|
|
231
224
|
|
|
232
|
-
// Metadata output for barcode scanning
|
|
225
|
+
// 4. Metadata output for barcode scanning
|
|
233
226
|
if self.session.canAddOutput(metadataOutput) {
|
|
234
227
|
self.session.addOutput(metadataOutput)
|
|
235
228
|
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
|
@@ -242,10 +235,12 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
|
|
|
242
235
|
metadataOutput.metadataObjectTypes = filteredTypes
|
|
243
236
|
}
|
|
244
237
|
|
|
245
|
-
// Video data output for text / MRZ detection
|
|
238
|
+
// 5. Video data output for text / MRZ detection
|
|
246
239
|
if textRequest != nil && self.session.canAddOutput(self.videoDataOutput) {
|
|
247
240
|
self.session.addOutput(self.videoDataOutput)
|
|
248
241
|
}
|
|
242
|
+
|
|
243
|
+
return .success
|
|
249
244
|
}
|
|
250
245
|
|
|
251
246
|
// MARK: - Pause / Resume non-essential outputs
|