@capgo/camera-preview 7.4.0-beta.10 → 7.4.0-beta.12

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.
@@ -24,6 +24,8 @@ class CameraController: NSObject {
24
24
  var rearCamera: AVCaptureDevice?
25
25
  var rearCameraInput: AVCaptureDeviceInput?
26
26
 
27
+ var allDiscoveredDevices: [AVCaptureDevice] = []
28
+
27
29
  var fileVideoOutput: AVCaptureMovieFileOutput?
28
30
 
29
31
  var previewLayer: AVCaptureVideoPreviewLayer?
@@ -54,16 +56,30 @@ class CameraController: NSObject {
54
56
  }
55
57
 
56
58
  extension CameraController {
57
- func prepareBasicSession() {
59
+ func prepareFullSession() {
58
60
  // Only prepare if we don't already have a session
59
61
  guard self.captureSession == nil else { return }
60
-
61
- print("[CameraPreview] Preparing basic camera session in background")
62
-
63
- // Create basic capture session
62
+
63
+ print("[CameraPreview] Preparing full camera session in background")
64
+
65
+ // 1. Create and configure session
64
66
  self.captureSession = AVCaptureSession()
65
-
66
- // Configure basic devices without full preparation
67
+
68
+ // 2. Pre-configure session preset (can be changed later)
69
+ if captureSession!.canSetSessionPreset(.high) {
70
+ captureSession!.sessionPreset = .high
71
+ }
72
+
73
+ // 3. Discover and configure all cameras
74
+ discoverAndConfigureCameras()
75
+
76
+ // 4. Pre-create outputs (don't add to session yet)
77
+ prepareOutputs()
78
+
79
+ print("[CameraPreview] Full session preparation complete")
80
+ }
81
+
82
+ private func discoverAndConfigureCameras() {
67
83
  let deviceTypes: [AVCaptureDevice.DeviceType] = [
68
84
  .builtInWideAngleCamera,
69
85
  .builtInUltraWideCamera,
@@ -76,295 +92,212 @@ extension CameraController {
76
92
 
77
93
  let session = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: .unspecified)
78
94
  let cameras = session.devices.compactMap { $0 }
79
-
95
+
96
+ // Store all discovered devices for fast lookup later
97
+ self.allDiscoveredDevices = cameras
98
+
99
+ // Log all found devices for debugging
100
+ print("[CameraPreview] Found \(cameras.count) devices:")
101
+ for camera in cameras {
102
+ let constituentCount = camera.isVirtualDevice ? camera.constituentDevices.count : 1
103
+ print("[CameraPreview] - \(camera.localizedName) (Position: \(camera.position.rawValue), Virtual: \(camera.isVirtualDevice), Lenses: \(constituentCount))")
104
+ }
105
+
80
106
  // Find best cameras
81
107
  let rearVirtualDevices = cameras.filter { $0.position == .back && $0.isVirtualDevice }
82
108
  let bestRearVirtualDevice = rearVirtualDevices.max { $0.constituentDevices.count < $1.constituentDevices.count }
83
-
109
+
84
110
  self.frontCamera = cameras.first(where: { $0.position == .front })
85
-
111
+
86
112
  if let bestCamera = bestRearVirtualDevice {
87
113
  self.rearCamera = bestCamera
114
+ print("[CameraPreview] Selected best virtual rear camera: \(bestCamera.localizedName) with \(bestCamera.constituentDevices.count) physical cameras.")
88
115
  } else if let firstRearCamera = cameras.first(where: { $0.position == .back }) {
89
116
  self.rearCamera = firstRearCamera
117
+ print("[CameraPreview] WARN: No virtual rear camera found. Selected first available: \(firstRearCamera.localizedName)")
90
118
  }
91
-
92
- print("[CameraPreview] Basic session prepared with \(cameras.count) devices")
119
+
120
+ // Pre-configure focus modes
121
+ configureCameraFocus(camera: self.rearCamera)
122
+ configureCameraFocus(camera: self.frontCamera)
93
123
  }
94
124
 
95
- func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, aspectRatio: String? = nil, completionHandler: @escaping (Error?) -> Void) {
96
- func createCaptureSession() {
97
- // Use existing session if available from background preparation
98
- if self.captureSession == nil {
99
- self.captureSession = AVCaptureSession()
125
+ private func configureCameraFocus(camera: AVCaptureDevice?) {
126
+ guard let camera = camera else { return }
127
+
128
+ do {
129
+ try camera.lockForConfiguration()
130
+ if camera.isFocusModeSupported(.continuousAutoFocus) {
131
+ camera.focusMode = .continuousAutoFocus
100
132
  }
133
+ camera.unlockForConfiguration()
134
+ } catch {
135
+ print("[CameraPreview] Could not configure focus for \(camera.localizedName): \(error)")
101
136
  }
137
+ }
102
138
 
103
- func configureCaptureDevices() throws {
104
- // Skip device discovery if cameras are already found during background preparation
105
- if self.frontCamera != nil || self.rearCamera != nil {
106
- print("[CameraPreview] Using pre-discovered cameras")
107
- return
108
- }
109
-
110
- // Expanded device types to support more camera configurations
111
- let deviceTypes: [AVCaptureDevice.DeviceType] = [
112
- .builtInWideAngleCamera,
113
- .builtInUltraWideCamera,
114
- .builtInTelephotoCamera,
115
- .builtInDualCamera,
116
- .builtInDualWideCamera,
117
- .builtInTripleCamera,
118
- .builtInTrueDepthCamera
119
- ]
120
-
121
- let session = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: .unspecified)
122
-
123
- let cameras = session.devices.compactMap { $0 }
124
-
125
- // Log all found devices for debugging
126
- print("[CameraPreview] Found \(cameras.count) devices:")
127
- for camera in cameras {
128
- let constituentCount = camera.isVirtualDevice ? camera.constituentDevices.count : 1
129
- print("[CameraPreview] - \(camera.localizedName) (Position: \(camera.position.rawValue), Virtual: \(camera.isVirtualDevice), Lenses: \(constituentCount), Zoom: \(camera.minAvailableVideoZoomFactor)-\(camera.maxAvailableVideoZoomFactor))")
130
- }
139
+ private func prepareOutputs() {
140
+ // Pre-create photo output
141
+ self.photoOutput = AVCapturePhotoOutput()
142
+ self.photoOutput?.isHighResolutionCaptureEnabled = false // Default, can be changed
131
143
 
132
- guard !cameras.isEmpty else {
133
- print("[CameraPreview] ERROR: No cameras found.")
134
- throw CameraControllerError.noCamerasAvailable
135
- }
144
+ // Pre-create video output
145
+ self.fileVideoOutput = AVCaptureMovieFileOutput()
136
146
 
137
- // --- Corrected Device Selection Logic ---
138
- // Find the virtual device with the most constituent cameras (this is the most capable one)
139
- let rearVirtualDevices = cameras.filter { $0.position == .back && $0.isVirtualDevice }
140
- let bestRearVirtualDevice = rearVirtualDevices.max { $0.constituentDevices.count < $1.constituentDevices.count }
147
+ // Pre-create data output
148
+ self.dataOutput = AVCaptureVideoDataOutput()
149
+ self.dataOutput?.videoSettings = [
150
+ (kCVPixelBufferPixelFormatTypeKey as String): NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)
151
+ ]
152
+ self.dataOutput?.alwaysDiscardsLateVideoFrames = true
141
153
 
142
- self.frontCamera = cameras.first(where: { $0.position == .front })
154
+ print("[CameraPreview] Outputs pre-created")
155
+ }
143
156
 
144
- if let bestCamera = bestRearVirtualDevice {
145
- self.rearCamera = bestCamera
146
- print("[CameraPreview] Selected best virtual rear camera: \(bestCamera.localizedName) with \(bestCamera.constituentDevices.count) physical cameras.")
147
- } else if let firstRearCamera = cameras.first(where: { $0.position == .back }) {
148
- // Fallback for devices without a virtual camera system
149
- self.rearCamera = firstRearCamera
150
- print("[CameraPreview] WARN: No virtual rear camera found. Selected first available: \(firstRearCamera.localizedName)")
157
+ func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, aspectRatio: String? = nil, completionHandler: @escaping (Error?) -> Void) {
158
+ do {
159
+ // Session and outputs already created in load(), just configure user-specific settings
160
+ if self.captureSession == nil {
161
+ // Fallback if prepareFullSession() wasn't called
162
+ self.prepareFullSession()
151
163
  }
152
- // --- End of Correction ---
153
164
 
154
- if let rearCamera = self.rearCamera {
155
- do {
156
- try rearCamera.lockForConfiguration()
157
- if rearCamera.isFocusModeSupported(.continuousAutoFocus) {
158
- rearCamera.focusMode = .continuousAutoFocus
159
- }
160
- rearCamera.unlockForConfiguration()
161
- } catch {
162
- print("[CameraPreview] WARN: Could not set focus mode on rear camera. \(error)")
163
- }
165
+ guard let captureSession = self.captureSession else {
166
+ throw CameraControllerError.captureSessionIsMissing
164
167
  }
165
168
 
166
- if disableAudio == false {
167
- self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
168
- }
169
- }
169
+ print("[CameraPreview] Fast prepare - using pre-initialized session")
170
170
 
171
- func configureDeviceInputs() throws {
172
- guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
171
+ // Configure device inputs for the requested camera
172
+ try self.configureDeviceInputs(cameraPosition: cameraPosition, deviceId: deviceId, disableAudio: disableAudio)
173
173
 
174
- var selectedDevice: AVCaptureDevice?
174
+ // Add outputs to session and apply user settings
175
+ try self.addOutputsToSession(cameraMode: cameraMode, aspectRatio: aspectRatio)
175
176
 
176
- // If deviceId is specified, use that specific device
177
- if let deviceId = deviceId {
178
- let allDevices = AVCaptureDevice.DiscoverySession(
179
- deviceTypes: [.builtInWideAngleCamera, .builtInUltraWideCamera, .builtInTelephotoCamera, .builtInDualCamera, .builtInDualWideCamera, .builtInTripleCamera, .builtInTrueDepthCamera],
180
- mediaType: .video,
181
- position: .unspecified
182
- ).devices
177
+ // Start the session
178
+ captureSession.startRunning()
179
+ print("[CameraPreview] Session started")
183
180
 
184
- selectedDevice = allDevices.first(where: { $0.uniqueID == deviceId })
185
- guard selectedDevice != nil else {
186
- throw CameraControllerError.noCamerasAvailable
187
- }
188
- } else {
189
- // Use position-based selection
190
- if cameraPosition == "rear" {
191
- selectedDevice = self.rearCamera
192
- } else if cameraPosition == "front" {
193
- selectedDevice = self.frontCamera
194
- }
195
- }
181
+ completionHandler(nil)
182
+ } catch {
183
+ completionHandler(error)
184
+ }
185
+ }
186
+
187
+ private func configureDeviceInputs(cameraPosition: String, deviceId: String?, disableAudio: Bool) throws {
188
+ guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
189
+
190
+ var selectedDevice: AVCaptureDevice?
196
191
 
197
- guard let finalDevice = selectedDevice else {
198
- print("[CameraPreview] ERROR: No camera device selected for position: \(cameraPosition)")
192
+ // If deviceId is specified, find that specific device from pre-discovered devices
193
+ if let deviceId = deviceId {
194
+ selectedDevice = self.allDiscoveredDevices.first(where: { $0.uniqueID == deviceId })
195
+ guard selectedDevice != nil else {
196
+ print("[CameraPreview] ERROR: Device with ID \(deviceId) not found in pre-discovered devices")
199
197
  throw CameraControllerError.noCamerasAvailable
200
198
  }
199
+ } else {
200
+ // Use position-based selection from pre-discovered cameras
201
+ if cameraPosition == "rear" {
202
+ selectedDevice = self.rearCamera
203
+ } else if cameraPosition == "front" {
204
+ selectedDevice = self.frontCamera
205
+ }
206
+ }
201
207
 
202
- print("[CameraPreview] Configuring device: \(finalDevice.localizedName)")
203
- let deviceInput = try AVCaptureDeviceInput(device: finalDevice)
208
+ guard let finalDevice = selectedDevice else {
209
+ print("[CameraPreview] ERROR: No camera device selected for position: \(cameraPosition)")
210
+ throw CameraControllerError.noCamerasAvailable
211
+ }
204
212
 
205
- if captureSession.canAddInput(deviceInput) {
206
- captureSession.addInput(deviceInput)
213
+ print("[CameraPreview] Configuring device: \(finalDevice.localizedName)")
214
+ let deviceInput = try AVCaptureDeviceInput(device: finalDevice)
207
215
 
208
- if finalDevice.position == .front {
209
- self.frontCameraInput = deviceInput
210
- self.frontCamera = finalDevice
211
- self.currentCameraPosition = .front
212
- } else {
213
- self.rearCameraInput = deviceInput
214
- self.rearCamera = finalDevice
215
- self.currentCameraPosition = .rear
216
-
217
- // --- Corrected Initial Zoom Logic ---
218
- try finalDevice.lockForConfiguration()
219
- if finalDevice.isFocusModeSupported(.continuousAutoFocus) {
220
- finalDevice.focusMode = .continuousAutoFocus
221
- }
216
+ if captureSession.canAddInput(deviceInput) {
217
+ captureSession.addInput(deviceInput)
222
218
 
223
- // On a multi-camera system, a zoom factor of 2.0 often corresponds to the standard "1x" wide-angle lens.
224
- // We set this as the default to provide a familiar starting point for users.
225
- let defaultWideAngleZoom: CGFloat = 2.0
226
- if finalDevice.isVirtualDevice && finalDevice.constituentDevices.count > 1 && finalDevice.videoZoomFactor != defaultWideAngleZoom {
227
- // Check if 2.0 is a valid zoom factor before setting it.
228
- if defaultWideAngleZoom >= finalDevice.minAvailableVideoZoomFactor && defaultWideAngleZoom <= finalDevice.maxAvailableVideoZoomFactor {
229
- print("[CameraPreview] Multi-camera system detected. Setting initial zoom to \(defaultWideAngleZoom) (standard wide-angle).")
230
- finalDevice.videoZoomFactor = defaultWideAngleZoom
231
- }
232
- }
233
- finalDevice.unlockForConfiguration()
234
- // --- End of Correction ---
235
- }
219
+ if finalDevice.position == .front {
220
+ self.frontCameraInput = deviceInput
221
+ self.currentCameraPosition = .front
236
222
  } else {
237
- print("[CameraPreview] ERROR: Cannot add device input to session.")
238
- throw CameraControllerError.inputsAreInvalid
239
- }
223
+ self.rearCameraInput = deviceInput
224
+ self.currentCameraPosition = .rear
240
225
 
241
- // Add audio input
242
- if disableAudio == false {
243
- if let audioDevice = self.audioDevice {
244
- self.audioInput = try AVCaptureDeviceInput(device: audioDevice)
245
- if captureSession.canAddInput(self.audioInput!) {
246
- captureSession.addInput(self.audioInput!)
247
- } else {
248
- throw CameraControllerError.inputsAreInvalid
226
+ // Configure zoom for multi-camera systems
227
+ try finalDevice.lockForConfiguration()
228
+ let defaultWideAngleZoom: CGFloat = 2.0
229
+ if finalDevice.isVirtualDevice && finalDevice.constituentDevices.count > 1 && finalDevice.videoZoomFactor != defaultWideAngleZoom {
230
+ if defaultWideAngleZoom >= finalDevice.minAvailableVideoZoomFactor && defaultWideAngleZoom <= finalDevice.maxAvailableVideoZoomFactor {
231
+ print("[CameraPreview] Setting initial zoom to \(defaultWideAngleZoom)")
232
+ finalDevice.videoZoomFactor = defaultWideAngleZoom
249
233
  }
250
234
  }
235
+ finalDevice.unlockForConfiguration()
251
236
  }
237
+ } else {
238
+ throw CameraControllerError.inputsAreInvalid
252
239
  }
253
240
 
254
- func configurePhotoOutput(cameraMode: Bool) throws {
255
- guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
256
-
257
- // Configure session preset for high-quality preview
258
- // Prioritize higher quality presets for better preview resolution
259
- var targetPreset: AVCaptureSession.Preset = .high // Default to high quality
260
-
261
- if let aspectRatio = aspectRatio {
262
- switch aspectRatio {
263
- case "16:9":
264
- // Use highest available HD preset for 16:9 aspect ratio
265
- if captureSession.canSetSessionPreset(.hd4K3840x2160) {
266
- targetPreset = .hd4K3840x2160
267
- } else if captureSession.canSetSessionPreset(.hd1920x1080) {
268
- targetPreset = .hd1920x1080
269
- } else if captureSession.canSetSessionPreset(.hd1280x720) {
270
- targetPreset = .hd1280x720
271
- } else {
272
- targetPreset = .high
273
- }
274
- case "4:3":
275
- // Use photo preset for 4:3 aspect ratio (highest quality)
276
- if captureSession.canSetSessionPreset(.photo) {
277
- targetPreset = .photo
278
- } else if captureSession.canSetSessionPreset(.high) {
279
- targetPreset = .high
280
- } else {
281
- targetPreset = .medium
282
- }
283
- default:
284
- // Default to highest available quality
285
- if captureSession.canSetSessionPreset(.photo) {
286
- targetPreset = .photo
287
- } else if captureSession.canSetSessionPreset(.high) {
288
- targetPreset = .high
289
- } else {
290
- targetPreset = .medium
291
- }
292
- }
293
- } else {
294
- // Default to highest available quality when no aspect ratio specified
295
- if captureSession.canSetSessionPreset(.photo) {
296
- targetPreset = .photo
297
- } else if captureSession.canSetSessionPreset(.high) {
298
- targetPreset = .high
299
- } else {
300
- targetPreset = .medium
301
- }
241
+ // Add audio input if needed
242
+ if !disableAudio {
243
+ if self.audioDevice == nil {
244
+ self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
302
245
  }
303
-
304
- // Apply the determined preset
305
- if captureSession.canSetSessionPreset(targetPreset) {
306
- captureSession.sessionPreset = targetPreset
307
- print("[CameraPreview] Set session preset to \(targetPreset) for aspect ratio: \(aspectRatio ?? "default")")
308
- } else {
309
- // Fallback to high quality preset if the target preset is not supported
310
- print("[CameraPreview] Target preset \(targetPreset) not supported, falling back to .high")
311
- if captureSession.canSetSessionPreset(.high) {
312
- captureSession.sessionPreset = .high
313
- } else if captureSession.canSetSessionPreset(.medium) {
314
- captureSession.sessionPreset = .medium
246
+ if let audioDevice = self.audioDevice {
247
+ self.audioInput = try AVCaptureDeviceInput(device: audioDevice)
248
+ if captureSession.canAddInput(self.audioInput!) {
249
+ captureSession.addInput(self.audioInput!)
250
+ } else {
251
+ throw CameraControllerError.inputsAreInvalid
315
252
  }
316
253
  }
317
-
318
- self.photoOutput = AVCapturePhotoOutput()
319
- self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
320
- self.photoOutput?.isHighResolutionCaptureEnabled = self.highResolutionOutput
321
- if captureSession.canAddOutput(self.photoOutput!) { captureSession.addOutput(self.photoOutput!) }
322
-
323
- let fileVideoOutput = AVCaptureMovieFileOutput()
324
- if captureSession.canAddOutput(fileVideoOutput) {
325
- captureSession.addOutput(fileVideoOutput)
326
- self.fileVideoOutput = fileVideoOutput
327
- }
328
- captureSession.startRunning()
329
254
  }
255
+ }
330
256
 
331
- func configureDataOutput() throws {
332
- guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
257
+ private func addOutputsToSession(cameraMode: Bool, aspectRatio: String?) throws {
258
+ guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
333
259
 
334
- self.dataOutput = AVCaptureVideoDataOutput()
335
- self.dataOutput?.videoSettings = [
336
- (kCVPixelBufferPixelFormatTypeKey as String): NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)
337
- ]
338
- self.dataOutput?.alwaysDiscardsLateVideoFrames = true
339
- if captureSession.canAddOutput(self.dataOutput!) {
340
- captureSession.addOutput(self.dataOutput!)
260
+ // Update session preset based on aspect ratio if needed
261
+ var targetPreset: AVCaptureSession.Preset = .high // Default to high quality
262
+
263
+ if let aspectRatio = aspectRatio {
264
+ switch aspectRatio {
265
+ case "16:9":
266
+ targetPreset = captureSession.canSetSessionPreset(.hd1920x1080) ? .hd1920x1080 : .high
267
+ case "4:3":
268
+ targetPreset = captureSession.canSetSessionPreset(.photo) ? .photo : .high
269
+ default:
270
+ targetPreset = .high
341
271
  }
272
+ }
273
+
274
+ // Always try to set the best preset available
275
+ if captureSession.canSetSessionPreset(targetPreset) {
276
+ captureSession.sessionPreset = targetPreset
277
+ print("[CameraPreview] Updated preset to \(targetPreset) for aspect ratio: \(aspectRatio ?? "default")")
278
+ } else if captureSession.canSetSessionPreset(.high) {
279
+ // Fallback to high if target preset not available
280
+ captureSession.sessionPreset = .high
281
+ print("[CameraPreview] Fallback to high preset")
282
+ }
342
283
 
343
- captureSession.commitConfiguration()
344
-
345
- let queue = DispatchQueue(label: "DataOutput", attributes: [])
346
- self.dataOutput?.setSampleBufferDelegate(self, queue: queue)
284
+ // Add photo output (already created in prepareOutputs)
285
+ if let photoOutput = self.photoOutput, captureSession.canAddOutput(photoOutput) {
286
+ photoOutput.isHighResolutionCaptureEnabled = self.highResolutionOutput
287
+ captureSession.addOutput(photoOutput)
347
288
  }
348
289
 
349
- DispatchQueue(label: "prepare").async {
350
- do {
351
- createCaptureSession()
352
- try configureCaptureDevices()
353
- try configureDeviceInputs()
354
- try configurePhotoOutput(cameraMode: cameraMode)
355
- try configureDataOutput()
356
- // try configureVideoOutput()
357
- } catch {
358
- DispatchQueue.main.async {
359
- completionHandler(error)
360
- }
290
+ // Add video output only if camera mode is enabled
291
+ if cameraMode, let videoOutput = self.fileVideoOutput, captureSession.canAddOutput(videoOutput) {
292
+ captureSession.addOutput(videoOutput)
293
+ }
361
294
 
362
- return
363
- }
295
+ // Add data output
296
+ if let dataOutput = self.dataOutput, captureSession.canAddOutput(dataOutput) {
297
+ captureSession.addOutput(dataOutput)
298
+ captureSession.commitConfiguration()
364
299
 
365
- DispatchQueue.main.async {
366
- completionHandler(nil)
367
- }
300
+ dataOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
368
301
  }
369
302
  }
370
303
 
@@ -379,6 +312,9 @@ extension CameraController {
379
312
  // Optimize preview layer for better quality
380
313
  self.previewLayer?.connection?.videoOrientation = .portrait
381
314
  self.previewLayer?.isOpaque = true
315
+
316
+ // Set contentsScale for retina display quality
317
+ self.previewLayer?.contentsScale = UIScreen.main.scale
382
318
 
383
319
  // Enable high-quality rendering
384
320
  if #available(iOS 13.0, *) {
@@ -386,7 +322,7 @@ extension CameraController {
386
322
  }
387
323
 
388
324
  view.layer.insertSublayer(self.previewLayer!, at: 0)
389
-
325
+
390
326
  // Disable animation for frame update
391
327
  CATransaction.begin()
392
328
  CATransaction.setDisableActions(true)
@@ -443,8 +379,8 @@ extension CameraController {
443
379
  if Thread.isMainThread {
444
380
  updateVideoOrientationOnMainThread()
445
381
  } else {
446
- DispatchQueue.main.async { [weak self] in
447
- self?.updateVideoOrientationOnMainThread()
382
+ DispatchQueue.main.sync {
383
+ self.updateVideoOrientationOnMainThread()
448
384
  }
449
385
  }
450
386
  }
@@ -485,7 +421,7 @@ extension CameraController {
485
421
 
486
422
  // Ensure we have the necessary cameras
487
423
  guard (currentCameraPosition == .front && rearCamera != nil) ||
488
- (currentCameraPosition == .rear && frontCamera != nil) else {
424
+ (currentCameraPosition == .rear && frontCamera != nil) else {
489
425
  throw CameraControllerError.noCamerasAvailable
490
426
  }
491
427
 
@@ -501,9 +437,7 @@ extension CameraController {
501
437
  captureSession.commitConfiguration()
502
438
  // Restart the session if it was running before
503
439
  if wasRunning {
504
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
505
- self?.captureSession?.startRunning()
506
- }
440
+ captureSession.startRunning()
507
441
  }
508
442
  }
509
443
 
@@ -513,7 +447,7 @@ extension CameraController {
513
447
  // Remove only video inputs
514
448
  captureSession.inputs.forEach { input in
515
449
  if (input as? AVCaptureDeviceInput)?.device.hasMediaType(.video) ?? false {
516
- captureSession.removeInput(input)
450
+ captureSession.removeInput(input)
517
451
  }
518
452
  }
519
453
 
@@ -532,7 +466,7 @@ extension CameraController {
532
466
  rearCamera.unlockForConfiguration()
533
467
 
534
468
  if let newInput = try? AVCaptureDeviceInput(device: rearCamera),
535
- captureSession.canAddInput(newInput) {
469
+ captureSession.canAddInput(newInput) {
536
470
  captureSession.addInput(newInput)
537
471
  rearCameraInput = newInput
538
472
  self.currentCameraPosition = .rear
@@ -552,7 +486,7 @@ extension CameraController {
552
486
  frontCamera.unlockForConfiguration()
553
487
 
554
488
  if let newInput = try? AVCaptureDeviceInput(device: frontCamera),
555
- captureSession.canAddInput(newInput) {
489
+ captureSession.canAddInput(newInput) {
556
490
  captureSession.addInput(newInput)
557
491
  frontCameraInput = newInput
558
492
  self.currentCameraPosition = .front
@@ -567,9 +501,7 @@ extension CameraController {
567
501
  }
568
502
 
569
503
  // Update video orientation
570
- DispatchQueue.main.async { [weak self] in
571
- self?.updateVideoOrientation()
572
- }
504
+ self.updateVideoOrientation()
573
505
  }
574
506
 
575
507
  func captureImage(width: Int?, height: Int?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Error?) -> Void) {
@@ -637,7 +569,7 @@ extension CameraController {
637
569
 
638
570
  func resizeImage(image: UIImage, to size: CGSize) -> UIImage? {
639
571
  let renderer = UIGraphicsImageRenderer(size: size)
640
- let resizedImage = renderer.image { (context) in
572
+ let resizedImage = renderer.image { (_) in
641
573
  image.draw(in: CGRect(origin: .zero, size: size))
642
574
  }
643
575
  return resizedImage
@@ -963,9 +895,7 @@ extension CameraController {
963
895
  captureSession.commitConfiguration()
964
896
  // Restart the session if it was running before
965
897
  if wasRunning {
966
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
967
- self?.captureSession?.startRunning()
968
- }
898
+ captureSession.startRunning()
969
899
  }
970
900
  }
971
901
 
@@ -1012,13 +942,9 @@ extension CameraController {
1012
942
  }
1013
943
 
1014
944
  // Update video orientation
1015
- DispatchQueue.main.async { [weak self] in
1016
- self?.updateVideoOrientation()
1017
- }
945
+ self.updateVideoOrientation()
1018
946
  }
1019
947
 
1020
-
1021
-
1022
948
  func cleanup() {
1023
949
  if let captureSession = self.captureSession {
1024
950
  captureSession.stopRunning()
@@ -1139,7 +1065,7 @@ extension CameraController: UIGestureRecognizerDelegate {
1139
1065
  }
1140
1066
  }
1141
1067
 
1142
- @objc
1068
+ @objc
1143
1069
  private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
1144
1070
  guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
1145
1071
 
@@ -1151,7 +1077,7 @@ extension CameraController: UIGestureRecognizerDelegate {
1151
1077
  // Store the initial zoom factor when pinch begins
1152
1078
  zoomFactor = device.videoZoomFactor
1153
1079
 
1154
- case .changed:
1080
+ case .changed:
1155
1081
  // Throttle zoom updates to prevent excessive CPU usage
1156
1082
  let currentTime = CACurrentMediaTime()
1157
1083
  guard currentTime - lastZoomUpdateTime >= zoomUpdateThrottle else { return }
@@ -1181,16 +1107,24 @@ extension CameraController: UIGestureRecognizerDelegate {
1181
1107
  }
1182
1108
 
1183
1109
  extension CameraController: AVCapturePhotoCaptureDelegate {
1184
- public func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?,
1185
- resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) {
1110
+ public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
1186
1111
  if let error = error {
1187
1112
  self.photoCaptureCompletionBlock?(nil, error)
1188
- } else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil),
1189
- let image = UIImage(data: data) {
1190
- self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
1191
- } else {
1113
+ return
1114
+ }
1115
+
1116
+ // Get the photo data using the modern API
1117
+ guard let imageData = photo.fileDataRepresentation() else {
1192
1118
  self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
1119
+ return
1193
1120
  }
1121
+
1122
+ guard let image = UIImage(data: imageData) else {
1123
+ self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
1124
+ return
1125
+ }
1126
+
1127
+ self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
1194
1128
  }
1195
1129
  }
1196
1130
 
@@ -1312,6 +1246,8 @@ extension UIImage {
1312
1246
  print("right")
1313
1247
  case .up, .upMirrored:
1314
1248
  break
1249
+ @unknown default:
1250
+ break
1315
1251
  }
1316
1252
 
1317
1253
  // Flip image one more time if needed to, this is to prevent flipped image
@@ -1324,6 +1260,8 @@ extension UIImage {
1324
1260
  transform.scaledBy(x: -1, y: 1)
1325
1261
  case .up, .down, .left, .right:
1326
1262
  break
1263
+ @unknown default:
1264
+ break
1327
1265
  }
1328
1266
 
1329
1267
  ctx.concatenate(transform)