@capgo/camera-preview 7.3.8 → 7.4.0-beta.1
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/CapgoCameraPreview.podspec +16 -13
- package/README.md +298 -68
- package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
- package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
- 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/fileChanges/last-build.bin +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/8.14.2/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
- package/android/.gradle/file-system.probe +0 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +9 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +239 -545
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +848 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +54 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +70 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +65 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +34 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +34 -0
- package/dist/docs.json +702 -151
- package/dist/esm/definitions.d.ts +326 -80
- package/dist/esm/definitions.js +10 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +27 -1
- package/dist/esm/web.js +244 -4
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +254 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +254 -4
- package/dist/plugin.js.map +1 -1
- package/ios/{Plugin → Sources/CapgoCameraPreview}/CameraController.swift +359 -34
- package/ios/{Plugin → Sources/CapgoCameraPreview}/Plugin.swift +307 -16
- package/ios/Tests/CameraPreviewPluginTests/CameraPreviewPluginTests.swift +15 -0
- package/package.json +1 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +0 -1279
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +0 -29
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +0 -39
- package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +0 -461
- package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +0 -24
- package/ios/Plugin/Info.plist +0 -24
- package/ios/Plugin/Plugin.h +0 -10
- package/ios/Plugin/Plugin.m +0 -18
- package/ios/Plugin.xcodeproj/project.pbxproj +0 -593
- package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
- package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/PluginTests/Info.plist +0 -22
- package/ios/PluginTests/PluginTests.swift +0 -83
- package/ios/Podfile +0 -13
- package/ios/Podfile.lock +0 -23
|
@@ -40,34 +40,78 @@ class CameraController: NSObject {
|
|
|
40
40
|
var zoomFactor: CGFloat = 1.0
|
|
41
41
|
|
|
42
42
|
var videoFileURL: URL?
|
|
43
|
+
private let saneMaxZoomFactor: CGFloat = 25.5
|
|
44
|
+
|
|
45
|
+
var isUsingMultiLensVirtualCamera: Bool {
|
|
46
|
+
guard let device = (currentCameraPosition == .rear) ? rearCamera : frontCamera else { return false }
|
|
47
|
+
// A rear multi-lens virtual camera will have a min zoom of 1.0 but support wider angles
|
|
48
|
+
return device.position == .back && device.isVirtualDevice && device.constituentDevices.count > 1
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
extension CameraController {
|
|
46
|
-
func prepare(cameraPosition: String, disableAudio: Bool, cameraMode: Bool, completionHandler: @escaping (Error?) -> Void) {
|
|
53
|
+
func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, completionHandler: @escaping (Error?) -> Void) {
|
|
47
54
|
func createCaptureSession() {
|
|
48
55
|
self.captureSession = AVCaptureSession()
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
func configureCaptureDevices() throws {
|
|
59
|
+
// Expanded device types to support more camera configurations
|
|
60
|
+
let deviceTypes: [AVCaptureDevice.DeviceType] = [
|
|
61
|
+
.builtInWideAngleCamera,
|
|
62
|
+
.builtInUltraWideCamera,
|
|
63
|
+
.builtInTelephotoCamera,
|
|
64
|
+
.builtInDualCamera,
|
|
65
|
+
.builtInDualWideCamera,
|
|
66
|
+
.builtInTripleCamera,
|
|
67
|
+
.builtInTrueDepthCamera
|
|
68
|
+
]
|
|
52
69
|
|
|
53
|
-
let session = AVCaptureDevice.DiscoverySession(deviceTypes:
|
|
70
|
+
let session = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: .unspecified)
|
|
54
71
|
|
|
55
72
|
let cameras = session.devices.compactMap { $0 }
|
|
56
|
-
guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
|
|
57
73
|
|
|
74
|
+
// Log all found devices for debugging
|
|
75
|
+
print("[CameraPreview] Found \(cameras.count) devices:")
|
|
58
76
|
for camera in cameras {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
let constituentCount = camera.isVirtualDevice ? camera.constituentDevices.count : 1
|
|
78
|
+
print("[CameraPreview] - \(camera.localizedName) (Position: \(camera.position.rawValue), Virtual: \(camera.isVirtualDevice), Lenses: \(constituentCount), Zoom: \(camera.minAvailableVideoZoomFactor)-\(camera.maxAvailableVideoZoomFactor))")
|
|
79
|
+
}
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
guard !cameras.isEmpty else {
|
|
82
|
+
print("[CameraPreview] ERROR: No cameras found.")
|
|
83
|
+
throw CameraControllerError.noCamerasAvailable
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- Corrected Device Selection Logic ---
|
|
87
|
+
// Find the virtual device with the most constituent cameras (this is the most capable one)
|
|
88
|
+
let rearVirtualDevices = cameras.filter { $0.position == .back && $0.isVirtualDevice }
|
|
89
|
+
let bestRearVirtualDevice = rearVirtualDevices.max { $0.constituentDevices.count < $1.constituentDevices.count }
|
|
90
|
+
|
|
91
|
+
self.frontCamera = cameras.first(where: { $0.position == .front })
|
|
92
|
+
|
|
93
|
+
if let bestCamera = bestRearVirtualDevice {
|
|
94
|
+
self.rearCamera = bestCamera
|
|
95
|
+
print("[CameraPreview] Selected best virtual rear camera: \(bestCamera.localizedName) with \(bestCamera.constituentDevices.count) physical cameras.")
|
|
96
|
+
} else if let firstRearCamera = cameras.first(where: { $0.position == .back }) {
|
|
97
|
+
// Fallback for devices without a virtual camera system
|
|
98
|
+
self.rearCamera = firstRearCamera
|
|
99
|
+
print("[CameraPreview] WARN: No virtual rear camera found. Selected first available: \(firstRearCamera.localizedName)")
|
|
100
|
+
}
|
|
101
|
+
// --- End of Correction ---
|
|
65
102
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
103
|
+
if let rearCamera = self.rearCamera {
|
|
104
|
+
do {
|
|
105
|
+
try rearCamera.lockForConfiguration()
|
|
106
|
+
if rearCamera.isFocusModeSupported(.continuousAutoFocus) {
|
|
107
|
+
rearCamera.focusMode = .continuousAutoFocus
|
|
108
|
+
}
|
|
109
|
+
rearCamera.unlockForConfiguration()
|
|
110
|
+
} catch {
|
|
111
|
+
print("[CameraPreview] WARN: Could not set focus mode on rear camera. \(error)")
|
|
69
112
|
}
|
|
70
113
|
}
|
|
114
|
+
|
|
71
115
|
if disableAudio == false {
|
|
72
116
|
self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
|
|
73
117
|
}
|
|
@@ -76,23 +120,72 @@ extension CameraController {
|
|
|
76
120
|
func configureDeviceInputs() throws {
|
|
77
121
|
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
78
122
|
|
|
79
|
-
|
|
80
|
-
if let rearCamera = self.rearCamera {
|
|
81
|
-
self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
|
|
123
|
+
var selectedDevice: AVCaptureDevice?
|
|
82
124
|
|
|
83
|
-
|
|
125
|
+
// If deviceId is specified, use that specific device
|
|
126
|
+
if let deviceId = deviceId {
|
|
127
|
+
let allDevices = AVCaptureDevice.DiscoverySession(
|
|
128
|
+
deviceTypes: [.builtInWideAngleCamera, .builtInUltraWideCamera, .builtInTelephotoCamera, .builtInDualCamera, .builtInDualWideCamera, .builtInTripleCamera, .builtInTrueDepthCamera],
|
|
129
|
+
mediaType: .video,
|
|
130
|
+
position: .unspecified
|
|
131
|
+
).devices
|
|
84
132
|
|
|
85
|
-
|
|
133
|
+
selectedDevice = allDevices.first(where: { $0.uniqueID == deviceId })
|
|
134
|
+
guard selectedDevice != nil else {
|
|
135
|
+
throw CameraControllerError.noCamerasAvailable
|
|
86
136
|
}
|
|
87
|
-
} else
|
|
88
|
-
|
|
89
|
-
|
|
137
|
+
} else {
|
|
138
|
+
// Use position-based selection
|
|
139
|
+
if cameraPosition == "rear" {
|
|
140
|
+
selectedDevice = self.rearCamera
|
|
141
|
+
} else if cameraPosition == "front" {
|
|
142
|
+
selectedDevice = self.frontCamera
|
|
143
|
+
}
|
|
144
|
+
}
|
|
90
145
|
|
|
91
|
-
|
|
146
|
+
guard let finalDevice = selectedDevice else {
|
|
147
|
+
print("[CameraPreview] ERROR: No camera device selected for position: \(cameraPosition)")
|
|
148
|
+
throw CameraControllerError.noCamerasAvailable
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
print("[CameraPreview] Configuring device: \(finalDevice.localizedName)")
|
|
152
|
+
let deviceInput = try AVCaptureDeviceInput(device: finalDevice)
|
|
92
153
|
|
|
154
|
+
if captureSession.canAddInput(deviceInput) {
|
|
155
|
+
captureSession.addInput(deviceInput)
|
|
156
|
+
|
|
157
|
+
if finalDevice.position == .front {
|
|
158
|
+
self.frontCameraInput = deviceInput
|
|
159
|
+
self.frontCamera = finalDevice
|
|
93
160
|
self.currentCameraPosition = .front
|
|
161
|
+
} else {
|
|
162
|
+
self.rearCameraInput = deviceInput
|
|
163
|
+
self.rearCamera = finalDevice
|
|
164
|
+
self.currentCameraPosition = .rear
|
|
165
|
+
|
|
166
|
+
// --- Corrected Initial Zoom Logic ---
|
|
167
|
+
try finalDevice.lockForConfiguration()
|
|
168
|
+
if finalDevice.isFocusModeSupported(.continuousAutoFocus) {
|
|
169
|
+
finalDevice.focusMode = .continuousAutoFocus
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// On a multi-camera system, a zoom factor of 2.0 often corresponds to the standard "1x" wide-angle lens.
|
|
173
|
+
// We set this as the default to provide a familiar starting point for users.
|
|
174
|
+
let defaultWideAngleZoom: CGFloat = 2.0
|
|
175
|
+
if finalDevice.isVirtualDevice && finalDevice.constituentDevices.count > 1 && finalDevice.videoZoomFactor != defaultWideAngleZoom {
|
|
176
|
+
// Check if 2.0 is a valid zoom factor before setting it.
|
|
177
|
+
if defaultWideAngleZoom >= finalDevice.minAvailableVideoZoomFactor && defaultWideAngleZoom <= finalDevice.maxAvailableVideoZoomFactor {
|
|
178
|
+
print("[CameraPreview] Multi-camera system detected. Setting initial zoom to \(defaultWideAngleZoom) (standard wide-angle).")
|
|
179
|
+
finalDevice.videoZoomFactor = defaultWideAngleZoom
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
finalDevice.unlockForConfiguration()
|
|
183
|
+
// --- End of Correction ---
|
|
94
184
|
}
|
|
95
|
-
} else {
|
|
185
|
+
} else {
|
|
186
|
+
print("[CameraPreview] ERROR: Cannot add device input to session.")
|
|
187
|
+
throw CameraControllerError.inputsAreInvalid
|
|
188
|
+
}
|
|
96
189
|
|
|
97
190
|
// Add audio input
|
|
98
191
|
if disableAudio == false {
|
|
@@ -213,7 +306,7 @@ extension CameraController {
|
|
|
213
306
|
|
|
214
307
|
private func updateVideoOrientationOnMainThread() {
|
|
215
308
|
let videoOrientation: AVCaptureVideoOrientation
|
|
216
|
-
|
|
309
|
+
|
|
217
310
|
// Use window scene interface orientation
|
|
218
311
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
219
312
|
switch windowScene.interfaceOrientation {
|
|
@@ -244,19 +337,19 @@ extension CameraController {
|
|
|
244
337
|
let captureSession = self.captureSession else {
|
|
245
338
|
throw CameraControllerError.captureSessionIsMissing
|
|
246
339
|
}
|
|
247
|
-
|
|
340
|
+
|
|
248
341
|
// Ensure we have the necessary cameras
|
|
249
342
|
guard (currentCameraPosition == .front && rearCamera != nil) ||
|
|
250
343
|
(currentCameraPosition == .rear && frontCamera != nil) else {
|
|
251
344
|
throw CameraControllerError.noCamerasAvailable
|
|
252
345
|
}
|
|
253
|
-
|
|
346
|
+
|
|
254
347
|
// Store the current running state
|
|
255
348
|
let wasRunning = captureSession.isRunning
|
|
256
349
|
if wasRunning {
|
|
257
350
|
captureSession.stopRunning()
|
|
258
351
|
}
|
|
259
|
-
|
|
352
|
+
|
|
260
353
|
// Begin configuration
|
|
261
354
|
captureSession.beginConfiguration()
|
|
262
355
|
defer {
|
|
@@ -268,7 +361,7 @@ extension CameraController {
|
|
|
268
361
|
}
|
|
269
362
|
}
|
|
270
363
|
}
|
|
271
|
-
|
|
364
|
+
|
|
272
365
|
// Store audio input if it exists
|
|
273
366
|
let audioInput = captureSession.inputs.first { ($0 as? AVCaptureDeviceInput)?.device.hasMediaType(.audio) ?? false }
|
|
274
367
|
|
|
@@ -278,21 +371,21 @@ extension CameraController {
|
|
|
278
371
|
captureSession.removeInput(input)
|
|
279
372
|
}
|
|
280
373
|
}
|
|
281
|
-
|
|
374
|
+
|
|
282
375
|
// Configure new camera
|
|
283
376
|
switch currentCameraPosition {
|
|
284
377
|
case .front:
|
|
285
378
|
guard let rearCamera = rearCamera else {
|
|
286
379
|
throw CameraControllerError.invalidOperation
|
|
287
380
|
}
|
|
288
|
-
|
|
381
|
+
|
|
289
382
|
// Configure rear camera
|
|
290
383
|
try rearCamera.lockForConfiguration()
|
|
291
384
|
if rearCamera.isFocusModeSupported(.continuousAutoFocus) {
|
|
292
385
|
rearCamera.focusMode = .continuousAutoFocus
|
|
293
386
|
}
|
|
294
387
|
rearCamera.unlockForConfiguration()
|
|
295
|
-
|
|
388
|
+
|
|
296
389
|
if let newInput = try? AVCaptureDeviceInput(device: rearCamera),
|
|
297
390
|
captureSession.canAddInput(newInput) {
|
|
298
391
|
captureSession.addInput(newInput)
|
|
@@ -306,13 +399,13 @@ extension CameraController {
|
|
|
306
399
|
throw CameraControllerError.invalidOperation
|
|
307
400
|
}
|
|
308
401
|
|
|
309
|
-
// Configure front camera
|
|
402
|
+
// Configure front camera
|
|
310
403
|
try frontCamera.lockForConfiguration()
|
|
311
404
|
if frontCamera.isFocusModeSupported(.continuousAutoFocus) {
|
|
312
405
|
frontCamera.focusMode = .continuousAutoFocus
|
|
313
406
|
}
|
|
314
407
|
frontCamera.unlockForConfiguration()
|
|
315
|
-
|
|
408
|
+
|
|
316
409
|
if let newInput = try? AVCaptureDeviceInput(device: frontCamera),
|
|
317
410
|
captureSession.canAddInput(newInput) {
|
|
318
411
|
captureSession.addInput(newInput)
|
|
@@ -327,7 +420,7 @@ extension CameraController {
|
|
|
327
420
|
if let audioInput = audioInput, captureSession.canAddInput(audioInput) {
|
|
328
421
|
captureSession.addInput(audioInput)
|
|
329
422
|
}
|
|
330
|
-
|
|
423
|
+
|
|
331
424
|
// Update video orientation
|
|
332
425
|
DispatchQueue.main.async { [weak self] in
|
|
333
426
|
self?.updateVideoOrientation()
|
|
@@ -415,8 +508,15 @@ extension CameraController {
|
|
|
415
508
|
throw CameraControllerError.noCamerasAvailable
|
|
416
509
|
}
|
|
417
510
|
|
|
418
|
-
|
|
511
|
+
// Get the active format and field of view
|
|
512
|
+
let activeFormat = device.activeFormat
|
|
513
|
+
let fov = activeFormat.videoFieldOfView
|
|
419
514
|
|
|
515
|
+
// Adjust for current zoom level
|
|
516
|
+
let zoomFactor = device.videoZoomFactor
|
|
517
|
+
let adjustedFov = fov / Float(zoomFactor)
|
|
518
|
+
|
|
519
|
+
return adjustedFov
|
|
420
520
|
}
|
|
421
521
|
func setFlashMode(flashMode: AVCaptureDevice.FlashMode) throws {
|
|
422
522
|
var currentCamera: AVCaptureDevice?
|
|
@@ -489,6 +589,230 @@ extension CameraController {
|
|
|
489
589
|
}
|
|
490
590
|
}
|
|
491
591
|
|
|
592
|
+
func getZoom() throws -> (min: Float, max: Float, current: Float) {
|
|
593
|
+
var currentCamera: AVCaptureDevice?
|
|
594
|
+
switch currentCameraPosition {
|
|
595
|
+
case .front:
|
|
596
|
+
currentCamera = self.frontCamera
|
|
597
|
+
case .rear:
|
|
598
|
+
currentCamera = self.rearCamera
|
|
599
|
+
default: break
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
guard let device = currentCamera else {
|
|
603
|
+
throw CameraControllerError.noCamerasAvailable
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
let effectiveMaxZoom = min(device.maxAvailableVideoZoomFactor, self.saneMaxZoomFactor)
|
|
607
|
+
|
|
608
|
+
return (
|
|
609
|
+
min: Float(device.minAvailableVideoZoomFactor),
|
|
610
|
+
max: Float(effectiveMaxZoom),
|
|
611
|
+
current: Float(device.videoZoomFactor)
|
|
612
|
+
)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
func setZoom(level: CGFloat, ramp: Bool) throws {
|
|
616
|
+
var currentCamera: AVCaptureDevice?
|
|
617
|
+
switch currentCameraPosition {
|
|
618
|
+
case .front:
|
|
619
|
+
currentCamera = self.frontCamera
|
|
620
|
+
case .rear:
|
|
621
|
+
currentCamera = self.rearCamera
|
|
622
|
+
default: break
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
guard let device = currentCamera else {
|
|
626
|
+
throw CameraControllerError.noCamerasAvailable
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
let effectiveMaxZoom = min(device.maxAvailableVideoZoomFactor, self.saneMaxZoomFactor)
|
|
630
|
+
let zoomLevel = max(device.minAvailableVideoZoomFactor, min(level, effectiveMaxZoom))
|
|
631
|
+
|
|
632
|
+
do {
|
|
633
|
+
try device.lockForConfiguration()
|
|
634
|
+
|
|
635
|
+
if ramp {
|
|
636
|
+
device.ramp(toVideoZoomFactor: zoomLevel, withRate: 1.0)
|
|
637
|
+
} else {
|
|
638
|
+
device.videoZoomFactor = zoomLevel
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
device.unlockForConfiguration()
|
|
642
|
+
|
|
643
|
+
// Update our internal zoom factor tracking
|
|
644
|
+
self.zoomFactor = zoomLevel
|
|
645
|
+
} catch {
|
|
646
|
+
throw CameraControllerError.invalidOperation
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
func getFlashMode() throws -> String {
|
|
651
|
+
switch self.flashMode {
|
|
652
|
+
case .off:
|
|
653
|
+
return "off"
|
|
654
|
+
case .on:
|
|
655
|
+
return "on"
|
|
656
|
+
case .auto:
|
|
657
|
+
return "auto"
|
|
658
|
+
@unknown default:
|
|
659
|
+
return "off"
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
func getCurrentDeviceId() throws -> String {
|
|
664
|
+
var currentCamera: AVCaptureDevice?
|
|
665
|
+
switch currentCameraPosition {
|
|
666
|
+
case .front:
|
|
667
|
+
currentCamera = self.frontCamera
|
|
668
|
+
case .rear:
|
|
669
|
+
currentCamera = self.rearCamera
|
|
670
|
+
default:
|
|
671
|
+
break
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
guard let device = currentCamera else {
|
|
675
|
+
throw CameraControllerError.noCamerasAvailable
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return device.uniqueID
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
func getCurrentLensInfo() throws -> (focalLength: Float, deviceType: String, baseZoomRatio: Float) {
|
|
682
|
+
var currentCamera: AVCaptureDevice?
|
|
683
|
+
switch currentCameraPosition {
|
|
684
|
+
case .front:
|
|
685
|
+
currentCamera = self.frontCamera
|
|
686
|
+
case .rear:
|
|
687
|
+
currentCamera = self.rearCamera
|
|
688
|
+
default:
|
|
689
|
+
break
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
guard let device = currentCamera else {
|
|
693
|
+
throw CameraControllerError.noCamerasAvailable
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
var deviceType = "wideAngle"
|
|
697
|
+
var baseZoomRatio: Float = 1.0
|
|
698
|
+
|
|
699
|
+
switch device.deviceType {
|
|
700
|
+
case .builtInWideAngleCamera:
|
|
701
|
+
deviceType = "wideAngle"
|
|
702
|
+
baseZoomRatio = 1.0
|
|
703
|
+
case .builtInUltraWideCamera:
|
|
704
|
+
deviceType = "ultraWide"
|
|
705
|
+
baseZoomRatio = 0.5
|
|
706
|
+
case .builtInTelephotoCamera:
|
|
707
|
+
deviceType = "telephoto"
|
|
708
|
+
baseZoomRatio = 2.0
|
|
709
|
+
case .builtInDualCamera:
|
|
710
|
+
deviceType = "dual"
|
|
711
|
+
baseZoomRatio = 1.0
|
|
712
|
+
case .builtInDualWideCamera:
|
|
713
|
+
deviceType = "dualWide"
|
|
714
|
+
baseZoomRatio = 1.0
|
|
715
|
+
case .builtInTripleCamera:
|
|
716
|
+
deviceType = "triple"
|
|
717
|
+
baseZoomRatio = 1.0
|
|
718
|
+
case .builtInTrueDepthCamera:
|
|
719
|
+
deviceType = "trueDepth"
|
|
720
|
+
baseZoomRatio = 1.0
|
|
721
|
+
default:
|
|
722
|
+
deviceType = "wideAngle"
|
|
723
|
+
baseZoomRatio = 1.0
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Approximate focal length for mobile devices
|
|
727
|
+
let focalLength: Float = 4.25
|
|
728
|
+
|
|
729
|
+
return (focalLength: focalLength, deviceType: deviceType, baseZoomRatio: baseZoomRatio)
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
func swapToDevice(deviceId: String) throws {
|
|
733
|
+
guard let captureSession = self.captureSession else {
|
|
734
|
+
throw CameraControllerError.captureSessionIsMissing
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Find the device with the specified deviceId
|
|
738
|
+
let allDevices = AVCaptureDevice.DiscoverySession(
|
|
739
|
+
deviceTypes: [.builtInWideAngleCamera, .builtInUltraWideCamera, .builtInTelephotoCamera, .builtInDualCamera, .builtInDualWideCamera, .builtInTripleCamera, .builtInTrueDepthCamera],
|
|
740
|
+
mediaType: .video,
|
|
741
|
+
position: .unspecified
|
|
742
|
+
).devices
|
|
743
|
+
|
|
744
|
+
guard let targetDevice = allDevices.first(where: { $0.uniqueID == deviceId }) else {
|
|
745
|
+
throw CameraControllerError.noCamerasAvailable
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Store the current running state
|
|
749
|
+
let wasRunning = captureSession.isRunning
|
|
750
|
+
if wasRunning {
|
|
751
|
+
captureSession.stopRunning()
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Begin configuration
|
|
755
|
+
captureSession.beginConfiguration()
|
|
756
|
+
defer {
|
|
757
|
+
captureSession.commitConfiguration()
|
|
758
|
+
// Restart the session if it was running before
|
|
759
|
+
if wasRunning {
|
|
760
|
+
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
761
|
+
self?.captureSession?.startRunning()
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Store audio input if it exists
|
|
767
|
+
let audioInput = captureSession.inputs.first { ($0 as? AVCaptureDeviceInput)?.device.hasMediaType(.audio) ?? false }
|
|
768
|
+
|
|
769
|
+
// Remove only video inputs
|
|
770
|
+
captureSession.inputs.forEach { input in
|
|
771
|
+
if (input as? AVCaptureDeviceInput)?.device.hasMediaType(.video) ?? false {
|
|
772
|
+
captureSession.removeInput(input)
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Configure the new device
|
|
777
|
+
let newInput = try AVCaptureDeviceInput(device: targetDevice)
|
|
778
|
+
|
|
779
|
+
if captureSession.canAddInput(newInput) {
|
|
780
|
+
captureSession.addInput(newInput)
|
|
781
|
+
|
|
782
|
+
// Update camera references based on device position
|
|
783
|
+
if targetDevice.position == .front {
|
|
784
|
+
self.frontCameraInput = newInput
|
|
785
|
+
self.frontCamera = targetDevice
|
|
786
|
+
self.currentCameraPosition = .front
|
|
787
|
+
} else {
|
|
788
|
+
self.rearCameraInput = newInput
|
|
789
|
+
self.rearCamera = targetDevice
|
|
790
|
+
self.currentCameraPosition = .rear
|
|
791
|
+
|
|
792
|
+
// Configure rear camera
|
|
793
|
+
try targetDevice.lockForConfiguration()
|
|
794
|
+
if targetDevice.isFocusModeSupported(.continuousAutoFocus) {
|
|
795
|
+
targetDevice.focusMode = .continuousAutoFocus
|
|
796
|
+
}
|
|
797
|
+
targetDevice.unlockForConfiguration()
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
throw CameraControllerError.invalidOperation
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Re-add audio input if it existed
|
|
804
|
+
if let audioInput = audioInput, captureSession.canAddInput(audioInput) {
|
|
805
|
+
captureSession.addInput(audioInput)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Update video orientation
|
|
809
|
+
DispatchQueue.main.async { [weak self] in
|
|
810
|
+
self?.updateVideoOrientation()
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
|
|
492
816
|
func cleanup() {
|
|
493
817
|
if let captureSession = self.captureSession {
|
|
494
818
|
captureSession.stopRunning()
|
|
@@ -613,7 +937,8 @@ extension CameraController: UIGestureRecognizerDelegate {
|
|
|
613
937
|
private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
|
|
614
938
|
guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
|
|
615
939
|
|
|
616
|
-
|
|
940
|
+
let effectiveMaxZoom = min(device.maxAvailableVideoZoomFactor, self.saneMaxZoomFactor)
|
|
941
|
+
func minMaxZoom(_ factor: CGFloat) -> CGFloat { return max(device.minAvailableVideoZoomFactor, min(factor, effectiveMaxZoom)) }
|
|
617
942
|
|
|
618
943
|
func update(scale factor: CGFloat) {
|
|
619
944
|
do {
|