@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.
- package/README.md +3 -3
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +281 -133
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +2028 -1467
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +71 -58
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +55 -46
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +61 -52
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +151 -72
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +29 -23
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +24 -23
- package/dist/docs.json +3 -3
- package/dist/esm/definitions.d.ts +3 -3
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -2
- package/dist/esm/web.js +51 -34
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +51 -34
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +51 -34
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreview/CameraController.swift +212 -274
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +467 -390
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
62
|
-
|
|
63
|
-
// Create
|
|
62
|
+
|
|
63
|
+
print("[CameraPreview] Preparing full camera session in background")
|
|
64
|
+
|
|
65
|
+
// 1. Create and configure session
|
|
64
66
|
self.captureSession = AVCaptureSession()
|
|
65
|
-
|
|
66
|
-
//
|
|
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
|
-
|
|
119
|
+
|
|
120
|
+
// Pre-configure focus modes
|
|
121
|
+
configureCameraFocus(camera: self.rearCamera)
|
|
122
|
+
configureCameraFocus(camera: self.frontCamera)
|
|
93
123
|
}
|
|
94
124
|
|
|
95
|
-
func
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
throw CameraControllerError.noCamerasAvailable
|
|
135
|
-
}
|
|
144
|
+
// Pre-create video output
|
|
145
|
+
self.fileVideoOutput = AVCaptureMovieFileOutput()
|
|
136
146
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
154
|
+
print("[CameraPreview] Outputs pre-created")
|
|
155
|
+
}
|
|
143
156
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Fallback
|
|
149
|
-
self.
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
167
|
-
self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
169
|
+
print("[CameraPreview] Fast prepare - using pre-initialized session")
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
// Configure device inputs for the requested camera
|
|
172
|
+
try self.configureDeviceInputs(cameraPosition: cameraPosition, deviceId: deviceId, disableAudio: disableAudio)
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
// Add outputs to session and apply user settings
|
|
175
|
+
try self.addOutputsToSession(cameraMode: cameraMode, aspectRatio: aspectRatio)
|
|
175
176
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
206
|
-
|
|
213
|
+
print("[CameraPreview] Configuring device: \(finalDevice.localizedName)")
|
|
214
|
+
let deviceInput = try AVCaptureDeviceInput(device: finalDevice)
|
|
207
215
|
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
223
|
+
self.rearCameraInput = deviceInput
|
|
224
|
+
self.currentCameraPosition = .rear
|
|
240
225
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
332
|
-
|
|
257
|
+
private func addOutputsToSession(cameraMode: Bool, aspectRatio: String?) throws {
|
|
258
|
+
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
333
259
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
captureSession.
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
447
|
-
self
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 { (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(_
|
|
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
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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)
|