capacitor-camera-view 2.0.1 → 2.1.0
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 +19 -9
- package/android/build.gradle +8 -5
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +217 -126
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +70 -30
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraResult.kt +47 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +11 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +94 -5
- package/dist/docs.json +81 -0
- package/dist/esm/definitions.d.ts +44 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +7 -1
- package/dist/esm/web.js +67 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +68 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +68 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CameraViewPlugin/CameraError.swift +97 -2
- package/ios/Sources/CameraViewPlugin/CameraEvents.swift +109 -0
- package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +29 -2
- package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +30 -41
- package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +45 -13
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +4 -3
- package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +193 -59
- package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +83 -84
- package/ios/Sources/CameraViewPlugin/TempFileManager.swift +181 -0
- package/ios/Sources/CameraViewPlugin/Utils.swift +102 -0
- package/package.json +17 -17
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import AVFoundation
|
|
2
|
+
import CoreImage
|
|
2
3
|
import Foundation
|
|
4
|
+
import Metal
|
|
3
5
|
import UIKit
|
|
4
6
|
import WebKit
|
|
5
7
|
|
|
@@ -11,16 +13,44 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
11
13
|
.builtInDualCamera,
|
|
12
14
|
.builtInDualWideCamera,
|
|
13
15
|
.builtInTripleCamera,
|
|
14
|
-
.builtInTrueDepthCamera
|
|
16
|
+
.builtInTrueDepthCamera,
|
|
15
17
|
]
|
|
16
18
|
|
|
17
19
|
/// A camera implementation that handles camera session management and photo capture.
|
|
18
20
|
@objc public class CameraViewManager: NSObject {
|
|
21
|
+
// MARK: - Shared Resources
|
|
22
|
+
|
|
23
|
+
/// Metal-backed CIContext singleton for efficient image processing.
|
|
24
|
+
/// Creating CIContext per frame is extremely expensive (80%+ CPU waste).
|
|
25
|
+
/// This shared instance uses Metal for GPU acceleration when available.
|
|
26
|
+
internal static let sharedCIContext: CIContext = {
|
|
27
|
+
if let metalDevice = MTLCreateSystemDefaultDevice() {
|
|
28
|
+
return CIContext(mtlDevice: metalDevice, options: [.cacheIntermediates: false])
|
|
29
|
+
}
|
|
30
|
+
return CIContext(options: [.useSoftwareRenderer: true])
|
|
31
|
+
}()
|
|
32
|
+
|
|
33
|
+
// MARK: - Capture Session Components
|
|
34
|
+
|
|
19
35
|
internal let captureSession = AVCaptureSession()
|
|
20
36
|
internal let avPhotoOutput = AVCapturePhotoOutput()
|
|
21
37
|
internal let avVideoDataOutput = AVCaptureVideoDataOutput()
|
|
22
38
|
internal let videoPreviewLayer = AVCaptureVideoPreviewLayer()
|
|
23
39
|
|
|
40
|
+
/// Dedicated queue for all capture session operations.
|
|
41
|
+
/// Using a consistent queue prevents race conditions and ensures thread safety.
|
|
42
|
+
private let sessionQueue = DispatchQueue(
|
|
43
|
+
label: "com.michaelwolz.capacitorcameraview.session",
|
|
44
|
+
qos: .userInitiated
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
/// Reusable queue for sample buffer processing during snapshot capture.
|
|
48
|
+
/// Creating a new queue per snapshot causes memory allocation churn.
|
|
49
|
+
private let sampleBufferQueue = DispatchQueue(
|
|
50
|
+
label: "com.michaelwolz.capacitorcameraview.sampleBuffer",
|
|
51
|
+
qos: .userInitiated
|
|
52
|
+
)
|
|
53
|
+
|
|
24
54
|
/// The currently active camera device.
|
|
25
55
|
private var currentCameraDevice: AVCaptureDevice?
|
|
26
56
|
|
|
@@ -36,12 +66,19 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
36
66
|
/// Reference to the webView that is used by the Capacitor plugin for the preview layer is shown on
|
|
37
67
|
private var webView: UIView?
|
|
38
68
|
|
|
39
|
-
/// Callback for when photo capture completes.
|
|
69
|
+
/// Callback for when photo capture completes (legacy UIImage-based API).
|
|
40
70
|
internal var photoCaptureHandler: ((UIImage?, Error?) -> Void)?
|
|
41
71
|
|
|
72
|
+
/// Callback for when photo capture completes with raw Data (optimized API).
|
|
73
|
+
/// This avoids double JPEG encoding by returning the camera's JPEG data directly.
|
|
74
|
+
internal var photoDataCaptureHandler: ((Data?, Error?) -> Void)?
|
|
75
|
+
|
|
42
76
|
/// Callback for when snapshot capture completes.
|
|
43
77
|
internal var snapshotCompletionHandler: ((UIImage?, Error?) -> Void)?
|
|
44
78
|
|
|
79
|
+
/// Emits typed camera events to the delegate and NotificationCenter.
|
|
80
|
+
internal let eventEmitter = CameraEventEmitter()
|
|
81
|
+
|
|
45
82
|
override public init() {
|
|
46
83
|
super.init()
|
|
47
84
|
setupOrientationObserver()
|
|
@@ -66,11 +103,15 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
66
103
|
webView: UIView,
|
|
67
104
|
completion: @escaping (Error?) -> Void
|
|
68
105
|
) {
|
|
69
|
-
if let preferredCameraDeviceTypes = configuration
|
|
70
|
-
|
|
106
|
+
if let preferredCameraDeviceTypes = configuration
|
|
107
|
+
.preferredCameraDeviceTypes
|
|
108
|
+
{
|
|
109
|
+
self.preferredCameraDeviceTypes = convertToNativeCameraTypes(
|
|
110
|
+
preferredCameraDeviceTypes
|
|
111
|
+
)
|
|
71
112
|
}
|
|
72
113
|
|
|
73
|
-
|
|
114
|
+
sessionQueue.async { [weak self] in
|
|
74
115
|
guard let self = self else { return }
|
|
75
116
|
|
|
76
117
|
do {
|
|
@@ -89,12 +130,15 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
89
130
|
self.displayPreview(
|
|
90
131
|
on: webView,
|
|
91
132
|
completion: { error in
|
|
92
|
-
if error != nil {
|
|
133
|
+
if error != nil {
|
|
134
|
+
completion(error)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
93
137
|
|
|
94
138
|
// Handle barcode detection after session is running
|
|
95
139
|
if configuration.enableBarcodeDetection {
|
|
96
140
|
do {
|
|
97
|
-
try self.enableBarcodeDetection()
|
|
141
|
+
try self.enableBarcodeDetection(barcodeTypes: configuration.barcodeTypes)
|
|
98
142
|
} catch {
|
|
99
143
|
completion(error)
|
|
100
144
|
return
|
|
@@ -110,34 +154,38 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
110
154
|
await self.upgradeToTripleCameraIfAvailable()
|
|
111
155
|
}
|
|
112
156
|
}
|
|
113
|
-
}
|
|
157
|
+
}
|
|
158
|
+
)
|
|
114
159
|
}
|
|
115
160
|
}
|
|
116
161
|
|
|
117
|
-
/// Stops the current capture session.
|
|
162
|
+
/// Stops the current capture session and cleans up temporary files.
|
|
118
163
|
public func stopSession(completion: (() -> Void)? = nil) {
|
|
119
164
|
guard captureSession.isRunning else {
|
|
120
165
|
completion?()
|
|
121
166
|
return
|
|
122
167
|
}
|
|
123
168
|
|
|
124
|
-
|
|
169
|
+
sessionQueue.async { [weak self] in
|
|
125
170
|
self?.captureSession.stopRunning()
|
|
126
|
-
}
|
|
127
171
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
self.blurOverlayView = nil
|
|
138
|
-
}
|
|
172
|
+
DispatchQueue.main.async { [weak self] in
|
|
173
|
+
guard let self = self else {
|
|
174
|
+
completion?()
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
self.videoPreviewLayer.removeFromSuperlayer()
|
|
178
|
+
self.webView?.isOpaque = true
|
|
179
|
+
self.webView?.backgroundColor = nil
|
|
180
|
+
self.webView = nil
|
|
139
181
|
|
|
140
|
-
|
|
182
|
+
if let blurOverlayView = self.blurOverlayView {
|
|
183
|
+
blurOverlayView.removeFromSuperview()
|
|
184
|
+
self.blurOverlayView = nil
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
completion?()
|
|
188
|
+
}
|
|
141
189
|
}
|
|
142
190
|
}
|
|
143
191
|
|
|
@@ -168,7 +216,8 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
168
216
|
|
|
169
217
|
// Ensure proper orientation
|
|
170
218
|
if let photoConnection = avPhotoOutput.connection(with: .video),
|
|
171
|
-
let previewConnection = videoPreviewLayer.connection
|
|
219
|
+
let previewConnection = videoPreviewLayer.connection
|
|
220
|
+
{
|
|
172
221
|
if photoConnection.isVideoOrientationSupported {
|
|
173
222
|
photoConnection.videoOrientation = previewConnection.videoOrientation
|
|
174
223
|
}
|
|
@@ -178,10 +227,48 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
178
227
|
photoCaptureHandler = completion
|
|
179
228
|
}
|
|
180
229
|
|
|
230
|
+
/// Captures a photo and returns the raw JPEG data directly.
|
|
231
|
+
/// This optimized method avoids double JPEG encoding by returning the camera's
|
|
232
|
+
/// native JPEG data instead of converting through UIImage.
|
|
233
|
+
///
|
|
234
|
+
/// - Parameter completion: Called with the captured JPEG data or an error.
|
|
235
|
+
public func capturePhotoData(completion: @escaping (Data?, Error?) -> Void) {
|
|
236
|
+
guard let cameraDevice = currentCameraDevice else {
|
|
237
|
+
completion(nil, CameraError.cameraUnavailable)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
guard captureSession.isRunning else {
|
|
242
|
+
completion(nil, CameraError.sessionNotRunning)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let photoSettings = AVCapturePhotoSettings()
|
|
247
|
+
if cameraDevice.hasFlash {
|
|
248
|
+
photoSettings.flashMode = flashMode
|
|
249
|
+
} else {
|
|
250
|
+
photoSettings.flashMode = .off
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Ensure proper orientation
|
|
254
|
+
if let photoConnection = avPhotoOutput.connection(with: .video),
|
|
255
|
+
let previewConnection = videoPreviewLayer.connection
|
|
256
|
+
{
|
|
257
|
+
if photoConnection.isVideoOrientationSupported {
|
|
258
|
+
photoConnection.videoOrientation = previewConnection.videoOrientation
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
avPhotoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
263
|
+
photoDataCaptureHandler = completion
|
|
264
|
+
}
|
|
265
|
+
|
|
181
266
|
/// Capture a snapshot of the current camera view. This is faster than actually processing a
|
|
182
267
|
/// photo via capturePhoto
|
|
183
268
|
/// - Parameter completion: called with the captured UIImage or an error.
|
|
184
|
-
public func captureSnapshot(
|
|
269
|
+
public func captureSnapshot(
|
|
270
|
+
completion: @escaping (UIImage?, Error?) -> Void
|
|
271
|
+
) {
|
|
185
272
|
guard currentCameraDevice != nil else {
|
|
186
273
|
completion(nil, CameraError.cameraUnavailable)
|
|
187
274
|
return
|
|
@@ -194,24 +281,27 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
194
281
|
|
|
195
282
|
// Ensure proper orientation
|
|
196
283
|
if let videoConnection = avVideoDataOutput.connection(with: .video),
|
|
197
|
-
let previewConnection = videoPreviewLayer.connection
|
|
284
|
+
let previewConnection = videoPreviewLayer.connection
|
|
285
|
+
{
|
|
198
286
|
if videoConnection.isVideoOrientationSupported {
|
|
199
287
|
videoConnection.videoOrientation = previewConnection.videoOrientation
|
|
200
288
|
}
|
|
201
289
|
}
|
|
202
290
|
|
|
203
|
-
//
|
|
204
|
-
let sampleBufferQueue = DispatchQueue(label: "com.michaelwolz.capacitorcameraview.snapshotQueue")
|
|
205
|
-
|
|
206
|
-
// Set the delegate for a single frame capture
|
|
291
|
+
// Set the delegate for a single frame capture using the reusable queue
|
|
207
292
|
snapshotCompletionHandler = completion
|
|
208
|
-
avVideoDataOutput.setSampleBufferDelegate(
|
|
293
|
+
avVideoDataOutput.setSampleBufferDelegate(
|
|
294
|
+
self,
|
|
295
|
+
queue: sampleBufferQueue
|
|
296
|
+
)
|
|
209
297
|
}
|
|
210
298
|
|
|
211
299
|
/// Flips the camera to the opposite position (front to back or back to front).
|
|
212
300
|
public func flipCamera() throws {
|
|
213
|
-
let currentPosition: AVCaptureDevice.Position =
|
|
214
|
-
|
|
301
|
+
let currentPosition: AVCaptureDevice.Position =
|
|
302
|
+
currentCameraDevice?.position ?? .back
|
|
303
|
+
let newPosition: AVCaptureDevice.Position =
|
|
304
|
+
currentPosition == .back ? .front : .back
|
|
215
305
|
|
|
216
306
|
let newCamera = try getCameraDevice(for: newPosition)
|
|
217
307
|
try setInput(with: newCamera)
|
|
@@ -222,7 +312,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
222
312
|
/// - Parameter mode: The desired flash mode (.on, .of, or .auto).
|
|
223
313
|
/// - Throws: An error if the flash mode cannot be set or is not supported.
|
|
224
314
|
public func setFlashMode(_ mode: AVCaptureDevice.FlashMode) throws {
|
|
225
|
-
guard let camera = currentCameraDevice else {
|
|
315
|
+
guard let camera = currentCameraDevice else {
|
|
316
|
+
throw CameraError.cameraUnavailable
|
|
317
|
+
}
|
|
226
318
|
guard camera.hasFlash else { throw CameraError.unsupportedFlashMode }
|
|
227
319
|
guard avPhotoOutput.supportedFlashModes.contains(mode)
|
|
228
320
|
else {
|
|
@@ -275,7 +367,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
275
367
|
/// - level: The torch intensity level (0.0 to 1.0)
|
|
276
368
|
/// - Throws: An error if the torch mode cannot be set or is not supported.
|
|
277
369
|
public func setTorchMode(enabled: Bool, level: Float = 1.0) throws {
|
|
278
|
-
guard let camera = currentCameraDevice else {
|
|
370
|
+
guard let camera = currentCameraDevice else {
|
|
371
|
+
throw CameraError.cameraUnavailable
|
|
372
|
+
}
|
|
279
373
|
guard camera.hasTorch else { throw CameraError.torchUnavailable }
|
|
280
374
|
|
|
281
375
|
do {
|
|
@@ -297,7 +391,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
297
391
|
/// because some devices report very high zoom factors that aren't useful.
|
|
298
392
|
///
|
|
299
393
|
/// - Returns: A tuple containing the minimum, maximum, and current zoom factors.
|
|
300
|
-
public func getSupportedZoomFactors() -> (
|
|
394
|
+
public func getSupportedZoomFactors() -> (
|
|
395
|
+
min: CGFloat, max: CGFloat, current: CGFloat
|
|
396
|
+
) {
|
|
301
397
|
guard let currentDevice = currentCameraDevice else {
|
|
302
398
|
return (
|
|
303
399
|
min: 1.0,
|
|
@@ -307,7 +403,10 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
307
403
|
}
|
|
308
404
|
|
|
309
405
|
let minZoomFactor = currentDevice.minAvailableVideoZoomFactor
|
|
310
|
-
let maxZoomFactor = min(
|
|
406
|
+
let maxZoomFactor = min(
|
|
407
|
+
currentDevice.activeFormat.videoMaxZoomFactor,
|
|
408
|
+
10.0
|
|
409
|
+
)
|
|
311
410
|
let currentZoomFactor = currentDevice.videoZoomFactor
|
|
312
411
|
|
|
313
412
|
return (
|
|
@@ -324,10 +423,15 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
324
423
|
/// - ramp: If enabled the zoom will be applied via ramp
|
|
325
424
|
/// - Throws: An error if the zoom factor cannot be set.
|
|
326
425
|
public func setZoomFactor(_ factor: CGFloat, ramp: Bool = true) throws {
|
|
327
|
-
guard let device = currentCameraDevice else {
|
|
426
|
+
guard let device = currentCameraDevice else {
|
|
427
|
+
throw CameraError.cameraUnavailable
|
|
428
|
+
}
|
|
328
429
|
|
|
329
430
|
let supportedZoomFactors = getSupportedZoomFactors()
|
|
330
|
-
guard
|
|
431
|
+
guard
|
|
432
|
+
factor >= supportedZoomFactors.min
|
|
433
|
+
&& factor <= supportedZoomFactors.max
|
|
434
|
+
else {
|
|
331
435
|
throw CameraError.zoomFactorOutOfRange
|
|
332
436
|
}
|
|
333
437
|
|
|
@@ -349,7 +453,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
349
453
|
///
|
|
350
454
|
/// - Parameters:
|
|
351
455
|
/// - configuration: The configuration object for the camera session.
|
|
352
|
-
private func initiateCaptureSession(
|
|
456
|
+
private func initiateCaptureSession(
|
|
457
|
+
configuration: CameraSessionConfiguration
|
|
458
|
+
) throws {
|
|
353
459
|
captureSession.beginConfiguration()
|
|
354
460
|
defer { captureSession.commitConfiguration() }
|
|
355
461
|
|
|
@@ -418,7 +524,6 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
418
524
|
}
|
|
419
525
|
}
|
|
420
526
|
|
|
421
|
-
|
|
422
527
|
/// Enables barcode detection by adding metadata output to the running session.
|
|
423
528
|
/// Somehow adding the metadata output with the session not being started yet
|
|
424
529
|
/// caused issues on some devices (iPad 7th Gen) where the session would just
|
|
@@ -426,8 +531,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
426
531
|
/// figure out the root cause of this but it might generally be a good idea
|
|
427
532
|
/// to only add the metadata output when the session is already running.
|
|
428
533
|
///
|
|
534
|
+
/// - Parameter barcodeTypes: Optional array of barcode types to detect. If nil, all supported types are used.
|
|
429
535
|
/// - Throws: An error if the metadata output cannot be added.
|
|
430
|
-
private func enableBarcodeDetection() throws {
|
|
536
|
+
private func enableBarcodeDetection(barcodeTypes: [AVMetadataObject.ObjectType]? = nil) throws {
|
|
431
537
|
guard captureSession.isRunning else {
|
|
432
538
|
throw CameraError.sessionNotRunning
|
|
433
539
|
}
|
|
@@ -435,7 +541,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
435
541
|
captureSession.beginConfiguration()
|
|
436
542
|
defer { captureSession.commitConfiguration() }
|
|
437
543
|
|
|
438
|
-
try setupMetadataOutput()
|
|
544
|
+
try setupMetadataOutput(barcodeTypes: barcodeTypes)
|
|
439
545
|
}
|
|
440
546
|
|
|
441
547
|
/// Retrieve a list of a available camera devices
|
|
@@ -468,11 +574,14 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
468
574
|
/// - position: The position of the camera device to get
|
|
469
575
|
/// - Returns: The camera device for the specified position
|
|
470
576
|
/// - Throws: An error if no camera device is found.
|
|
471
|
-
private func getCameraDevice(for position: AVCaptureDevice.Position?) throws
|
|
577
|
+
private func getCameraDevice(for position: AVCaptureDevice.Position?) throws
|
|
578
|
+
-> AVCaptureDevice
|
|
579
|
+
{
|
|
472
580
|
let preferredDevices = getPreferredCameraDevices()
|
|
473
581
|
|
|
474
582
|
// First try to get the best match based on the users preferred camera device types
|
|
475
|
-
if let match = preferredDevices.first(where: { $0.position == position }
|
|
583
|
+
if let match = preferredDevices.first(where: { $0.position == position }
|
|
584
|
+
) {
|
|
476
585
|
return match
|
|
477
586
|
}
|
|
478
587
|
|
|
@@ -480,7 +589,10 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
480
589
|
// Only doing this when preferredCameraDeviceTypes size differs from SUPPORTED_CAMERA_DEVICE_TYPES, otherwise
|
|
481
590
|
// we don't have to initialize a new discovery session
|
|
482
591
|
if preferredCameraDeviceTypes.count < SUPPORTED_CAMERA_DEVICE_TYPES.count,
|
|
483
|
-
let match = getAvailableDevices().first(where: {
|
|
592
|
+
let match = getAvailableDevices().first(where: {
|
|
593
|
+
$0.position == position
|
|
594
|
+
})
|
|
595
|
+
{
|
|
484
596
|
return match
|
|
485
597
|
}
|
|
486
598
|
|
|
@@ -490,8 +602,11 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
490
602
|
}
|
|
491
603
|
|
|
492
604
|
// Log when we're falling back to a device with different position than requested
|
|
493
|
-
if let requestedPosition = position, device.position != requestedPosition
|
|
494
|
-
|
|
605
|
+
if let requestedPosition = position, device.position != requestedPosition
|
|
606
|
+
{
|
|
607
|
+
print(
|
|
608
|
+
"Warning: Falling back to camera at position \(device.position) when \(requestedPosition) was requested"
|
|
609
|
+
)
|
|
495
610
|
}
|
|
496
611
|
|
|
497
612
|
return device
|
|
@@ -503,8 +618,14 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
503
618
|
/// - deviceId: The unique identifier of the camera device to get
|
|
504
619
|
/// - Returns: The camera device for the specified position
|
|
505
620
|
/// - Throws: An error if no camera device is found.
|
|
506
|
-
private func getCameraDeviceById(_ deviceId: String) throws
|
|
507
|
-
|
|
621
|
+
private func getCameraDeviceById(_ deviceId: String) throws
|
|
622
|
+
-> AVCaptureDevice
|
|
623
|
+
{
|
|
624
|
+
guard
|
|
625
|
+
let device = getAvailableDevices().first(where: {
|
|
626
|
+
$0.uniqueID == deviceId
|
|
627
|
+
})
|
|
628
|
+
else {
|
|
508
629
|
throw CameraError.cameraUnavailable
|
|
509
630
|
}
|
|
510
631
|
return device
|
|
@@ -519,7 +640,10 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
519
640
|
/// - view: The view that will display the camera preview.
|
|
520
641
|
/// - completion: The completion handler after successfully adding the previewLayer to the provided view
|
|
521
642
|
/// - Throws: An error if the preview layer cannot be set up.
|
|
522
|
-
private func displayPreview(
|
|
643
|
+
private func displayPreview(
|
|
644
|
+
on view: UIView,
|
|
645
|
+
completion: @escaping (Error?) -> Void
|
|
646
|
+
) {
|
|
523
647
|
guard captureSession.isRunning else {
|
|
524
648
|
completion(CameraError.sessionNotRunning)
|
|
525
649
|
return
|
|
@@ -580,7 +704,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
580
704
|
try self.setZoomFactor(2.0, ramp: false)
|
|
581
705
|
} catch {
|
|
582
706
|
// Fail silently if we can't upgrade to the triple camera
|
|
583
|
-
print(
|
|
707
|
+
print(
|
|
708
|
+
"Failed to upgrade to triple camera: \(error.localizedDescription)"
|
|
709
|
+
)
|
|
584
710
|
}
|
|
585
711
|
|
|
586
712
|
self.captureSession.commitConfiguration()
|
|
@@ -612,7 +738,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
612
738
|
/// Removes the blur overlay with a fade out animation to have a smooth transition
|
|
613
739
|
/// - Parameter duration: The duration of the fade out animation
|
|
614
740
|
@MainActor
|
|
615
|
-
private func removeBlurOverlayWithAnimation(duration: TimeInterval = 0.3)
|
|
741
|
+
private func removeBlurOverlayWithAnimation(duration: TimeInterval = 0.3)
|
|
742
|
+
async
|
|
743
|
+
{
|
|
616
744
|
guard let blurEffectView = blurOverlayView else { return }
|
|
617
745
|
|
|
618
746
|
await withCheckedContinuation { continuation in
|
|
@@ -625,7 +753,8 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
625
753
|
blurEffectView.removeFromSuperview()
|
|
626
754
|
self.blurOverlayView = nil
|
|
627
755
|
continuation.resume()
|
|
628
|
-
}
|
|
756
|
+
}
|
|
757
|
+
)
|
|
629
758
|
}
|
|
630
759
|
}
|
|
631
760
|
|
|
@@ -644,11 +773,15 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
644
773
|
|
|
645
774
|
/// Updates the preview layer orientation based on the current device orientation.
|
|
646
775
|
private func updatePreviewOrientation() {
|
|
647
|
-
guard let connection = self.videoPreviewLayer.connection,
|
|
776
|
+
guard let connection = self.videoPreviewLayer.connection,
|
|
777
|
+
connection.isVideoOrientationSupported
|
|
778
|
+
else {
|
|
648
779
|
return
|
|
649
780
|
}
|
|
650
781
|
|
|
651
|
-
let interfaceOrientation = UIApplication.shared.
|
|
782
|
+
let interfaceOrientation = UIApplication.shared.connectedScenes
|
|
783
|
+
.compactMap({ $0 as? UIWindowScene })
|
|
784
|
+
.first?.interfaceOrientation ?? .portrait
|
|
652
785
|
let videoOrientation: AVCaptureVideoOrientation
|
|
653
786
|
|
|
654
787
|
switch interfaceOrientation {
|
|
@@ -688,14 +821,15 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
688
821
|
self,
|
|
689
822
|
selector: #selector(handleAppDidBecomeActive),
|
|
690
823
|
name: UIApplication.didBecomeActiveNotification,
|
|
691
|
-
object: nil
|
|
824
|
+
object: nil
|
|
825
|
+
)
|
|
692
826
|
}
|
|
693
827
|
|
|
694
828
|
/// Handles the app going to background by pausing the camera session.
|
|
695
829
|
@objc private func handleAppWillResignActive() {
|
|
696
830
|
// Pause the session when app goes to background to save resources
|
|
697
831
|
if captureSession.isRunning {
|
|
698
|
-
|
|
832
|
+
sessionQueue.async { [weak self] in
|
|
699
833
|
self?.captureSession.stopRunning()
|
|
700
834
|
}
|
|
701
835
|
}
|
|
@@ -705,7 +839,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
705
839
|
@objc private func handleAppDidBecomeActive() {
|
|
706
840
|
// Resume the session when app comes back to foreground
|
|
707
841
|
if !captureSession.isRunning && webView != nil {
|
|
708
|
-
|
|
842
|
+
sessionQueue.async { [weak self] in
|
|
709
843
|
self?.captureSession.startRunning()
|
|
710
844
|
}
|
|
711
845
|
}
|