@capgo/camera-preview 7.4.0-beta.10 → 7.4.0-beta.11
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 +202 -273
- 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(.medium) {
|
|
70
|
+
captureSession!.sessionPreset = .medium
|
|
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,206 @@ 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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
241
|
+
// Add audio input if needed
|
|
242
|
+
if !disableAudio {
|
|
243
|
+
if self.audioDevice == nil {
|
|
244
|
+
self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
|
|
245
|
+
}
|
|
246
|
+
if let audioDevice = self.audioDevice {
|
|
247
|
+
self.audioInput = try AVCaptureDeviceInput(device: audioDevice)
|
|
248
|
+
if captureSession.canAddInput(self.audioInput!) {
|
|
249
|
+
captureSession.addInput(self.audioInput!)
|
|
299
250
|
} else {
|
|
300
|
-
|
|
251
|
+
throw CameraControllerError.inputsAreInvalid
|
|
301
252
|
}
|
|
302
253
|
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
303
256
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
257
|
+
private func addOutputsToSession(cameraMode: Bool, aspectRatio: String?) throws {
|
|
258
|
+
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
259
|
+
|
|
260
|
+
// Update session preset based on aspect ratio if needed
|
|
261
|
+
if let aspectRatio = aspectRatio {
|
|
262
|
+
var targetPreset: AVCaptureSession.Preset
|
|
263
|
+
switch aspectRatio {
|
|
264
|
+
case "16:9":
|
|
265
|
+
targetPreset = captureSession.canSetSessionPreset(.hd1920x1080) ? .hd1920x1080 : .high
|
|
266
|
+
case "4:3":
|
|
267
|
+
targetPreset = captureSession.canSetSessionPreset(.photo) ? .photo : .high
|
|
268
|
+
default:
|
|
269
|
+
targetPreset = .high
|
|
316
270
|
}
|
|
317
271
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
272
|
+
if captureSession.canSetSessionPreset(targetPreset) {
|
|
273
|
+
captureSession.sessionPreset = targetPreset
|
|
274
|
+
print("[CameraPreview] Updated preset to \(targetPreset) for aspect ratio: \(aspectRatio)")
|
|
327
275
|
}
|
|
328
|
-
captureSession.startRunning()
|
|
329
276
|
}
|
|
330
277
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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!)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
captureSession.commitConfiguration()
|
|
344
|
-
|
|
345
|
-
let queue = DispatchQueue(label: "DataOutput", attributes: [])
|
|
346
|
-
self.dataOutput?.setSampleBufferDelegate(self, queue: queue)
|
|
278
|
+
// Add photo output (already created in prepareOutputs)
|
|
279
|
+
if let photoOutput = self.photoOutput, captureSession.canAddOutput(photoOutput) {
|
|
280
|
+
photoOutput.isHighResolutionCaptureEnabled = self.highResolutionOutput
|
|
281
|
+
captureSession.addOutput(photoOutput)
|
|
347
282
|
}
|
|
348
283
|
|
|
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
|
-
}
|
|
284
|
+
// Add video output only if camera mode is enabled
|
|
285
|
+
if cameraMode, let videoOutput = self.fileVideoOutput, captureSession.canAddOutput(videoOutput) {
|
|
286
|
+
captureSession.addOutput(videoOutput)
|
|
287
|
+
}
|
|
361
288
|
|
|
362
|
-
|
|
363
|
-
|
|
289
|
+
// Add data output
|
|
290
|
+
if let dataOutput = self.dataOutput, captureSession.canAddOutput(dataOutput) {
|
|
291
|
+
captureSession.addOutput(dataOutput)
|
|
292
|
+
captureSession.commitConfiguration()
|
|
364
293
|
|
|
365
|
-
DispatchQueue.main
|
|
366
|
-
completionHandler(nil)
|
|
367
|
-
}
|
|
294
|
+
dataOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
|
|
368
295
|
}
|
|
369
296
|
}
|
|
370
297
|
|
|
@@ -386,7 +313,7 @@ extension CameraController {
|
|
|
386
313
|
}
|
|
387
314
|
|
|
388
315
|
view.layer.insertSublayer(self.previewLayer!, at: 0)
|
|
389
|
-
|
|
316
|
+
|
|
390
317
|
// Disable animation for frame update
|
|
391
318
|
CATransaction.begin()
|
|
392
319
|
CATransaction.setDisableActions(true)
|
|
@@ -443,8 +370,8 @@ extension CameraController {
|
|
|
443
370
|
if Thread.isMainThread {
|
|
444
371
|
updateVideoOrientationOnMainThread()
|
|
445
372
|
} else {
|
|
446
|
-
DispatchQueue.main.
|
|
447
|
-
self
|
|
373
|
+
DispatchQueue.main.sync {
|
|
374
|
+
self.updateVideoOrientationOnMainThread()
|
|
448
375
|
}
|
|
449
376
|
}
|
|
450
377
|
}
|
|
@@ -485,7 +412,7 @@ extension CameraController {
|
|
|
485
412
|
|
|
486
413
|
// Ensure we have the necessary cameras
|
|
487
414
|
guard (currentCameraPosition == .front && rearCamera != nil) ||
|
|
488
|
-
|
|
415
|
+
(currentCameraPosition == .rear && frontCamera != nil) else {
|
|
489
416
|
throw CameraControllerError.noCamerasAvailable
|
|
490
417
|
}
|
|
491
418
|
|
|
@@ -501,9 +428,7 @@ extension CameraController {
|
|
|
501
428
|
captureSession.commitConfiguration()
|
|
502
429
|
// Restart the session if it was running before
|
|
503
430
|
if wasRunning {
|
|
504
|
-
|
|
505
|
-
self?.captureSession?.startRunning()
|
|
506
|
-
}
|
|
431
|
+
captureSession.startRunning()
|
|
507
432
|
}
|
|
508
433
|
}
|
|
509
434
|
|
|
@@ -513,7 +438,7 @@ extension CameraController {
|
|
|
513
438
|
// Remove only video inputs
|
|
514
439
|
captureSession.inputs.forEach { input in
|
|
515
440
|
if (input as? AVCaptureDeviceInput)?.device.hasMediaType(.video) ?? false {
|
|
516
|
-
|
|
441
|
+
captureSession.removeInput(input)
|
|
517
442
|
}
|
|
518
443
|
}
|
|
519
444
|
|
|
@@ -532,7 +457,7 @@ extension CameraController {
|
|
|
532
457
|
rearCamera.unlockForConfiguration()
|
|
533
458
|
|
|
534
459
|
if let newInput = try? AVCaptureDeviceInput(device: rearCamera),
|
|
535
|
-
|
|
460
|
+
captureSession.canAddInput(newInput) {
|
|
536
461
|
captureSession.addInput(newInput)
|
|
537
462
|
rearCameraInput = newInput
|
|
538
463
|
self.currentCameraPosition = .rear
|
|
@@ -552,7 +477,7 @@ extension CameraController {
|
|
|
552
477
|
frontCamera.unlockForConfiguration()
|
|
553
478
|
|
|
554
479
|
if let newInput = try? AVCaptureDeviceInput(device: frontCamera),
|
|
555
|
-
|
|
480
|
+
captureSession.canAddInput(newInput) {
|
|
556
481
|
captureSession.addInput(newInput)
|
|
557
482
|
frontCameraInput = newInput
|
|
558
483
|
self.currentCameraPosition = .front
|
|
@@ -567,9 +492,7 @@ extension CameraController {
|
|
|
567
492
|
}
|
|
568
493
|
|
|
569
494
|
// Update video orientation
|
|
570
|
-
|
|
571
|
-
self?.updateVideoOrientation()
|
|
572
|
-
}
|
|
495
|
+
self.updateVideoOrientation()
|
|
573
496
|
}
|
|
574
497
|
|
|
575
498
|
func captureImage(width: Int?, height: Int?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Error?) -> Void) {
|
|
@@ -637,7 +560,7 @@ extension CameraController {
|
|
|
637
560
|
|
|
638
561
|
func resizeImage(image: UIImage, to size: CGSize) -> UIImage? {
|
|
639
562
|
let renderer = UIGraphicsImageRenderer(size: size)
|
|
640
|
-
let resizedImage = renderer.image { (
|
|
563
|
+
let resizedImage = renderer.image { (_) in
|
|
641
564
|
image.draw(in: CGRect(origin: .zero, size: size))
|
|
642
565
|
}
|
|
643
566
|
return resizedImage
|
|
@@ -963,9 +886,7 @@ extension CameraController {
|
|
|
963
886
|
captureSession.commitConfiguration()
|
|
964
887
|
// Restart the session if it was running before
|
|
965
888
|
if wasRunning {
|
|
966
|
-
|
|
967
|
-
self?.captureSession?.startRunning()
|
|
968
|
-
}
|
|
889
|
+
captureSession.startRunning()
|
|
969
890
|
}
|
|
970
891
|
}
|
|
971
892
|
|
|
@@ -1012,13 +933,9 @@ extension CameraController {
|
|
|
1012
933
|
}
|
|
1013
934
|
|
|
1014
935
|
// Update video orientation
|
|
1015
|
-
|
|
1016
|
-
self?.updateVideoOrientation()
|
|
1017
|
-
}
|
|
936
|
+
self.updateVideoOrientation()
|
|
1018
937
|
}
|
|
1019
938
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
939
|
func cleanup() {
|
|
1023
940
|
if let captureSession = self.captureSession {
|
|
1024
941
|
captureSession.stopRunning()
|
|
@@ -1139,7 +1056,7 @@ extension CameraController: UIGestureRecognizerDelegate {
|
|
|
1139
1056
|
}
|
|
1140
1057
|
}
|
|
1141
1058
|
|
|
1142
|
-
|
|
1059
|
+
@objc
|
|
1143
1060
|
private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
|
|
1144
1061
|
guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
|
|
1145
1062
|
|
|
@@ -1151,7 +1068,7 @@ extension CameraController: UIGestureRecognizerDelegate {
|
|
|
1151
1068
|
// Store the initial zoom factor when pinch begins
|
|
1152
1069
|
zoomFactor = device.videoZoomFactor
|
|
1153
1070
|
|
|
1154
|
-
|
|
1071
|
+
case .changed:
|
|
1155
1072
|
// Throttle zoom updates to prevent excessive CPU usage
|
|
1156
1073
|
let currentTime = CACurrentMediaTime()
|
|
1157
1074
|
guard currentTime - lastZoomUpdateTime >= zoomUpdateThrottle else { return }
|
|
@@ -1181,16 +1098,24 @@ extension CameraController: UIGestureRecognizerDelegate {
|
|
|
1181
1098
|
}
|
|
1182
1099
|
|
|
1183
1100
|
extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
1184
|
-
public func photoOutput(_
|
|
1185
|
-
resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) {
|
|
1101
|
+
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
|
|
1186
1102
|
if let error = error {
|
|
1187
1103
|
self.photoCaptureCompletionBlock?(nil, error)
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1104
|
+
return
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Get the photo data using the modern API
|
|
1108
|
+
guard let imageData = photo.fileDataRepresentation() else {
|
|
1109
|
+
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
1110
|
+
return
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
guard let image = UIImage(data: imageData) else {
|
|
1192
1114
|
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
1115
|
+
return
|
|
1193
1116
|
}
|
|
1117
|
+
|
|
1118
|
+
self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
|
|
1194
1119
|
}
|
|
1195
1120
|
}
|
|
1196
1121
|
|
|
@@ -1312,6 +1237,8 @@ extension UIImage {
|
|
|
1312
1237
|
print("right")
|
|
1313
1238
|
case .up, .upMirrored:
|
|
1314
1239
|
break
|
|
1240
|
+
@unknown default:
|
|
1241
|
+
break
|
|
1315
1242
|
}
|
|
1316
1243
|
|
|
1317
1244
|
// Flip image one more time if needed to, this is to prevent flipped image
|
|
@@ -1324,6 +1251,8 @@ extension UIImage {
|
|
|
1324
1251
|
transform.scaledBy(x: -1, y: 1)
|
|
1325
1252
|
case .up, .down, .left, .right:
|
|
1326
1253
|
break
|
|
1254
|
+
@unknown default:
|
|
1255
|
+
break
|
|
1327
1256
|
}
|
|
1328
1257
|
|
|
1329
1258
|
ctx.concatenate(transform)
|