@capgo/camera-preview 7.4.0-beta.6 → 7.4.0-beta.8

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.
@@ -40,6 +40,8 @@ class CameraController: NSObject {
40
40
  var audioInput: AVCaptureDeviceInput?
41
41
 
42
42
  var zoomFactor: CGFloat = 1.0
43
+ private var lastZoomUpdateTime: TimeInterval = 0
44
+ private let zoomUpdateThrottle: TimeInterval = 1.0 / 60.0 // 60 FPS max
43
45
 
44
46
  var videoFileURL: URL?
45
47
  private let saneMaxZoomFactor: CGFloat = 25.5
@@ -52,12 +54,59 @@ class CameraController: NSObject {
52
54
  }
53
55
 
54
56
  extension CameraController {
57
+ func prepareBasicSession() {
58
+ // Only prepare if we don't already have a session
59
+ guard self.captureSession == nil else { return }
60
+
61
+ print("[CameraPreview] Preparing basic camera session in background")
62
+
63
+ // Create basic capture session
64
+ self.captureSession = AVCaptureSession()
65
+
66
+ // Configure basic devices without full preparation
67
+ let deviceTypes: [AVCaptureDevice.DeviceType] = [
68
+ .builtInWideAngleCamera,
69
+ .builtInUltraWideCamera,
70
+ .builtInTelephotoCamera,
71
+ .builtInDualCamera,
72
+ .builtInDualWideCamera,
73
+ .builtInTripleCamera,
74
+ .builtInTrueDepthCamera
75
+ ]
76
+
77
+ let session = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: .unspecified)
78
+ let cameras = session.devices.compactMap { $0 }
79
+
80
+ // Find best cameras
81
+ let rearVirtualDevices = cameras.filter { $0.position == .back && $0.isVirtualDevice }
82
+ let bestRearVirtualDevice = rearVirtualDevices.max { $0.constituentDevices.count < $1.constituentDevices.count }
83
+
84
+ self.frontCamera = cameras.first(where: { $0.position == .front })
85
+
86
+ if let bestCamera = bestRearVirtualDevice {
87
+ self.rearCamera = bestCamera
88
+ } else if let firstRearCamera = cameras.first(where: { $0.position == .back }) {
89
+ self.rearCamera = firstRearCamera
90
+ }
91
+
92
+ print("[CameraPreview] Basic session prepared with \(cameras.count) devices")
93
+ }
94
+
55
95
  func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, aspectRatio: String? = nil, completionHandler: @escaping (Error?) -> Void) {
56
96
  func createCaptureSession() {
57
- self.captureSession = AVCaptureSession()
97
+ // Use existing session if available from background preparation
98
+ if self.captureSession == nil {
99
+ self.captureSession = AVCaptureSession()
100
+ }
58
101
  }
59
102
 
60
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
+
61
110
  // Expanded device types to support more camera configurations
62
111
  let deviceTypes: [AVCaptureDevice.DeviceType] = [
63
112
  .builtInWideAngleCamera,
@@ -205,14 +254,17 @@ extension CameraController {
205
254
  func configurePhotoOutput(cameraMode: Bool) throws {
206
255
  guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
207
256
 
208
- // Configure session preset based on aspect ratio and other settings
209
- var targetPreset: AVCaptureSession.Preset = .photo // Default preset
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
210
260
 
211
261
  if let aspectRatio = aspectRatio {
212
262
  switch aspectRatio {
213
263
  case "16:9":
214
- // Use HD presets for 16:9 aspect ratio
215
- if self.highResolutionOutput && captureSession.canSetSessionPreset(.hd1920x1080) {
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) {
216
268
  targetPreset = .hd1920x1080
217
269
  } else if captureSession.canSetSessionPreset(.hd1280x720) {
218
270
  targetPreset = .hd1280x720
@@ -220,26 +272,32 @@ extension CameraController {
220
272
  targetPreset = .high
221
273
  }
222
274
  case "4:3":
223
- // Use photo preset for 4:3 aspect ratio (traditional photo format)
224
- if self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
275
+ // Use photo preset for 4:3 aspect ratio (highest quality)
276
+ if captureSession.canSetSessionPreset(.photo) {
225
277
  targetPreset = .photo
278
+ } else if captureSession.canSetSessionPreset(.high) {
279
+ targetPreset = .high
226
280
  } else {
227
281
  targetPreset = .medium
228
282
  }
229
283
  default:
230
- // Default behavior for unrecognized aspect ratios
231
- if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
284
+ // Default to highest available quality
285
+ if captureSession.canSetSessionPreset(.photo) {
232
286
  targetPreset = .photo
233
- } else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
287
+ } else if captureSession.canSetSessionPreset(.high) {
234
288
  targetPreset = .high
289
+ } else {
290
+ targetPreset = .medium
235
291
  }
236
292
  }
237
293
  } else {
238
- // Original logic when no aspect ratio is specified
239
- if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
294
+ // Default to highest available quality when no aspect ratio specified
295
+ if captureSession.canSetSessionPreset(.photo) {
240
296
  targetPreset = .photo
241
- } else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
297
+ } else if captureSession.canSetSessionPreset(.high) {
242
298
  targetPreset = .high
299
+ } else {
300
+ targetPreset = .medium
243
301
  }
244
302
  }
245
303
 
@@ -248,9 +306,11 @@ extension CameraController {
248
306
  captureSession.sessionPreset = targetPreset
249
307
  print("[CameraPreview] Set session preset to \(targetPreset) for aspect ratio: \(aspectRatio ?? "default")")
250
308
  } else {
251
- // Fallback to a basic preset if the target preset is not supported
252
- print("[CameraPreview] Target preset \(targetPreset) not supported, falling back to .medium")
253
- if captureSession.canSetSessionPreset(.medium) {
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) {
254
314
  captureSession.sessionPreset = .medium
255
315
  }
256
316
  }
@@ -311,11 +371,30 @@ extension CameraController {
311
371
  func displayPreview(on view: UIView) throws {
312
372
  guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
313
373
 
374
+ print("[CameraPreview] displayPreview called with view frame: \(view.frame)")
375
+
314
376
  self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
315
377
  self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
316
378
 
379
+ // Optimize preview layer for better quality
380
+ self.previewLayer?.connection?.videoOrientation = .portrait
381
+ self.previewLayer?.isOpaque = true
382
+
383
+ // Enable high-quality rendering
384
+ if #available(iOS 13.0, *) {
385
+ self.previewLayer?.videoGravity = .resizeAspectFill
386
+ }
387
+
317
388
  view.layer.insertSublayer(self.previewLayer!, at: 0)
318
- self.previewLayer?.frame = view.frame
389
+
390
+ // Disable animation for frame update
391
+ CATransaction.begin()
392
+ CATransaction.setDisableActions(true)
393
+ self.previewLayer?.frame = view.bounds
394
+ CATransaction.commit()
395
+
396
+ print("[CameraPreview] Set preview layer frame to view bounds: \(view.bounds)")
397
+ print("[CameraPreview] Session preset: \(captureSession.sessionPreset.rawValue)")
319
398
 
320
399
  updateVideoOrientation()
321
400
  }
@@ -323,9 +402,13 @@ extension CameraController {
323
402
  func addGridOverlay(to view: UIView, gridMode: String) {
324
403
  removeGridOverlay()
325
404
 
405
+ // Disable animation for grid overlay creation and positioning
406
+ CATransaction.begin()
407
+ CATransaction.setDisableActions(true)
326
408
  gridOverlayView = GridOverlayView(frame: view.bounds)
327
409
  gridOverlayView?.gridMode = gridMode
328
410
  view.addSubview(gridOverlayView!)
411
+ CATransaction.commit()
329
412
  }
330
413
 
331
414
  func removeGridOverlay() {
@@ -349,6 +432,10 @@ extension CameraController {
349
432
  func setupPinchGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
350
433
  let pinchGesture = UIPinchGestureRecognizer(target: self, action: selector)
351
434
  pinchGesture.delegate = delegate
435
+ // Optimize gesture recognition for better performance
436
+ pinchGesture.delaysTouchesBegan = false
437
+ pinchGesture.delaysTouchesEnded = false
438
+ pinchGesture.cancelsTouchesInView = false
352
439
  target.addGestureRecognizer(pinchGesture)
353
440
  }
354
441
 
@@ -751,7 +838,8 @@ extension CameraController {
751
838
  try device.lockForConfiguration()
752
839
 
753
840
  if ramp {
754
- device.ramp(toVideoZoomFactor: zoomLevel, withRate: 1.0)
841
+ // Use a very fast ramp rate for immediate response
842
+ device.ramp(toVideoZoomFactor: zoomLevel, withRate: 8.0)
755
843
  } else {
756
844
  device.videoZoomFactor = zoomLevel
757
845
  }
@@ -1051,31 +1139,42 @@ extension CameraController: UIGestureRecognizerDelegate {
1051
1139
  }
1052
1140
  }
1053
1141
 
1054
- @objc
1142
+ @objc
1055
1143
  private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
1056
1144
  guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
1057
1145
 
1058
1146
  let effectiveMaxZoom = min(device.maxAvailableVideoZoomFactor, self.saneMaxZoomFactor)
1059
1147
  func minMaxZoom(_ factor: CGFloat) -> CGFloat { return max(device.minAvailableVideoZoomFactor, min(factor, effectiveMaxZoom)) }
1060
1148
 
1061
- func update(scale factor: CGFloat) {
1149
+ switch pinch.state {
1150
+ case .began:
1151
+ // Store the initial zoom factor when pinch begins
1152
+ zoomFactor = device.videoZoomFactor
1153
+
1154
+ case .changed:
1155
+ // Throttle zoom updates to prevent excessive CPU usage
1156
+ let currentTime = CACurrentMediaTime()
1157
+ guard currentTime - lastZoomUpdateTime >= zoomUpdateThrottle else { return }
1158
+ lastZoomUpdateTime = currentTime
1159
+
1160
+ // Calculate new zoom factor based on pinch scale
1161
+ let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor)
1162
+
1163
+ // Use ramping for smooth zoom transitions during pinch
1164
+ // This provides much smoother performance than direct setting
1062
1165
  do {
1063
1166
  try device.lockForConfiguration()
1064
- defer { device.unlockForConfiguration() }
1065
-
1066
- device.videoZoomFactor = factor
1167
+ // Use a very fast ramp rate for immediate response
1168
+ device.ramp(toVideoZoomFactor: newScaleFactor, withRate: 5.0)
1169
+ device.unlockForConfiguration()
1067
1170
  } catch {
1068
- debugPrint(error)
1171
+ debugPrint("Failed to set zoom: \(error)")
1069
1172
  }
1070
- }
1071
1173
 
1072
- switch pinch.state {
1073
- case .began: fallthrough
1074
- case .changed:
1075
- let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor)
1076
- update(scale: newScaleFactor)
1077
1174
  case .ended:
1175
+ // Update our internal zoom factor tracking
1078
1176
  zoomFactor = device.videoZoomFactor
1177
+
1079
1178
  default: break
1080
1179
  }
1081
1180
  }