@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.
- package/README.md +98 -30
- package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/file-system.probe +0 -0
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +170 -30
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +510 -15
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +2 -0
- package/dist/docs.json +100 -9
- package/dist/esm/definitions.d.ts +64 -8
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +34 -4
- package/dist/esm/web.js +94 -16
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +94 -16
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +94 -16
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreview/CameraController.swift +129 -30
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +334 -79
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
|
209
|
-
|
|
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
|
|
215
|
-
if
|
|
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 (
|
|
224
|
-
if
|
|
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
|
|
231
|
-
if
|
|
284
|
+
// Default to highest available quality
|
|
285
|
+
if captureSession.canSetSessionPreset(.photo) {
|
|
232
286
|
targetPreset = .photo
|
|
233
|
-
} else if
|
|
287
|
+
} else if captureSession.canSetSessionPreset(.high) {
|
|
234
288
|
targetPreset = .high
|
|
289
|
+
} else {
|
|
290
|
+
targetPreset = .medium
|
|
235
291
|
}
|
|
236
292
|
}
|
|
237
293
|
} else {
|
|
238
|
-
//
|
|
239
|
-
if
|
|
294
|
+
// Default to highest available quality when no aspect ratio specified
|
|
295
|
+
if captureSession.canSetSessionPreset(.photo) {
|
|
240
296
|
targetPreset = .photo
|
|
241
|
-
} else if
|
|
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
|
|
252
|
-
print("[CameraPreview] Target preset \(targetPreset) not supported, falling back to .
|
|
253
|
-
if captureSession.canSetSessionPreset(.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1065
|
-
|
|
1066
|
-
device.
|
|
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
|
}
|