capacitor-camera-view 2.0.2 → 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 +112 -41
- 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
|
@@ -5,39 +5,34 @@ extension CameraViewManager: AVCaptureMetadataOutputObjectsDelegate {
|
|
|
5
5
|
/// Set up metadata output for the capture session in case it's not configured yet
|
|
6
6
|
/// Make sure to call `captureSession.beginConfiguration` before calling this
|
|
7
7
|
///
|
|
8
|
+
/// - Parameter barcodeTypes: Optional array of specific barcode types to detect.
|
|
9
|
+
/// If nil, all supported types are detected (backwards compatible).
|
|
8
10
|
/// - Throws: An error if the output cannot be set.
|
|
9
|
-
internal func setupMetadataOutput() throws {
|
|
10
|
-
let
|
|
11
|
+
internal func setupMetadataOutput(barcodeTypes: [AVMetadataObject.ObjectType]? = nil) throws {
|
|
12
|
+
let requestedBarcodeTypes = barcodeTypes ?? ALL_SUPPORTED_BARCODE_TYPES
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
// Nothing todo, we already have an output
|
|
14
|
-
return
|
|
15
|
-
}
|
|
14
|
+
let metadataOutput: AVCaptureMetadataOutput
|
|
16
15
|
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
}
|
|
16
|
+
if let existingOutput = captureSession.outputs.first(where: { $0 is AVCaptureMetadataOutput }) as? AVCaptureMetadataOutput {
|
|
17
|
+
metadataOutput = existingOutput
|
|
18
|
+
} else {
|
|
19
|
+
let newOutput = AVCaptureMetadataOutput()
|
|
20
|
+
if !captureSession.canAddOutput(newOutput) {
|
|
21
|
+
throw CameraError.outputAdditionFailed
|
|
22
|
+
}
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
captureSession.addOutput(newOutput)
|
|
25
|
+
newOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
|
26
|
+
metadataOutput = newOutput
|
|
27
|
+
}
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
let supportedTypes = Set(metadataOutput.availableMetadataObjectTypes)
|
|
30
|
+
let resolvedTypes = requestedBarcodeTypes.filter { supportedTypes.contains($0) }
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
.code39,
|
|
30
|
-
.code39Mod43,
|
|
31
|
-
.code93,
|
|
32
|
-
.ean8,
|
|
33
|
-
.ean13,
|
|
34
|
-
.interleaved2of5,
|
|
35
|
-
.itf14,
|
|
36
|
-
.pdf417,
|
|
37
|
-
.aztec,
|
|
38
|
-
.dataMatrix,
|
|
39
|
-
.upce
|
|
40
|
-
]
|
|
32
|
+
if metadataOutput.metadataObjectTypes != resolvedTypes {
|
|
33
|
+
// Update the metadata output with the resolved types only if they differ from the current configuration
|
|
34
|
+
metadataOutput.metadataObjectTypes = resolvedTypes
|
|
35
|
+
}
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
/// Remove the metadata output if in case it is already configured, e.g. because
|
|
@@ -80,21 +75,15 @@ extension CameraViewManager: AVCaptureMetadataOutputObjectsDelegate {
|
|
|
80
75
|
return
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
let boundingRect
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
let boundingRect = BarcodeDetectedEvent.BoundingRect(
|
|
79
|
+
x: Double(transformedMetadataObject.bounds.origin.x),
|
|
80
|
+
y: Double(transformedMetadataObject.bounds.origin.y),
|
|
81
|
+
width: Double(transformedMetadataObject.bounds.width),
|
|
82
|
+
height: Double(transformedMetadataObject.bounds.height)
|
|
83
|
+
)
|
|
89
84
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
object: nil,
|
|
93
|
-
userInfo: [
|
|
94
|
-
"value": barcodeValue,
|
|
95
|
-
"type": barcodeType,
|
|
96
|
-
"boundingRect": boundingRect
|
|
97
|
-
]
|
|
85
|
+
eventEmitter.emitBarcodeDetected(
|
|
86
|
+
BarcodeDetectedEvent(value: barcodeValue, type: barcodeType, boundingRect: boundingRect)
|
|
98
87
|
)
|
|
99
88
|
}
|
|
100
89
|
}
|
|
@@ -13,19 +13,22 @@ extension CameraViewManager: AVCapturePhotoCaptureDelegate {
|
|
|
13
13
|
// use outputs for taking photos here we don't need a new one
|
|
14
14
|
return
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
// Balanced should be a good choice for most use cases
|
|
18
18
|
avPhotoOutput.maxPhotoQualityPrioritization = .balanced
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
if !captureSession.canAddOutput(avPhotoOutput) {
|
|
21
21
|
throw CameraError.outputAdditionFailed
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
captureSession.addOutput(avPhotoOutput)
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
/// Delegate method called when a photo has been captured via `AVCapturePhotoCaptureDelegate`
|
|
28
28
|
///
|
|
29
|
+
/// This method handles both the legacy UIImage-based callback and the optimized Data-based
|
|
30
|
+
/// callback to eliminate double JPEG encoding when possible.
|
|
31
|
+
///
|
|
29
32
|
/// - Parameters:
|
|
30
33
|
/// - output: The photo output that captured the photo.
|
|
31
34
|
/// - photo: The captured photo.
|
|
@@ -35,17 +38,46 @@ extension CameraViewManager: AVCapturePhotoCaptureDelegate {
|
|
|
35
38
|
didFinishProcessingPhoto photo: AVCapturePhoto,
|
|
36
39
|
error: Error?
|
|
37
40
|
) {
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
// Handle optimized Data-based callback first (avoids double encoding)
|
|
42
|
+
if let dataHandler = photoDataCaptureHandler {
|
|
43
|
+
photoDataCaptureHandler = nil
|
|
44
|
+
|
|
45
|
+
if let error = error {
|
|
46
|
+
dataHandler(nil, error)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
guard let data = photo.fileDataRepresentation() else {
|
|
51
|
+
dataHandler(nil, CameraError.photoOutputError)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
dataHandler(data, nil)
|
|
40
56
|
return
|
|
41
57
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
|
|
59
|
+
// Handle legacy UIImage-based callback
|
|
60
|
+
if let imageHandler = photoCaptureHandler {
|
|
61
|
+
photoCaptureHandler = nil
|
|
62
|
+
|
|
63
|
+
if let error = error {
|
|
64
|
+
imageHandler(nil, error)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
guard let data = photo.fileDataRepresentation() else {
|
|
69
|
+
imageHandler(nil, CameraError.photoOutputError)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
guard let image = UIImage(data: data) else {
|
|
74
|
+
imageHandler(nil, CameraError.photoOutputError)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
imageHandler(image, nil)
|
|
46
79
|
}
|
|
47
|
-
|
|
48
|
-
photoCaptureHandler?(image, nil)
|
|
49
80
|
}
|
|
50
|
-
|
|
81
|
+
|
|
51
82
|
}
|
|
83
|
+
|
|
@@ -33,7 +33,8 @@ extension CameraViewManager: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
33
33
|
captureSession.addOutput(avVideoDataOutput)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/// Capture a snapshot from the camera feed
|
|
36
|
+
/// Capture a snapshot from the camera feed using the shared Metal-backed CIContext.
|
|
37
|
+
/// Using a shared CIContext eliminates the ~80% CPU overhead of creating one per frame.
|
|
37
38
|
public func captureOutput(
|
|
38
39
|
_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer,
|
|
39
40
|
from connection: AVCaptureConnection
|
|
@@ -52,8 +53,8 @@ extension CameraViewManager: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
let ciImage = CIImage(cvPixelBuffer: imageBuffer)
|
|
55
|
-
|
|
56
|
-
guard let cgImage =
|
|
56
|
+
// Use the shared Metal-backed CIContext for efficient rendering
|
|
57
|
+
guard let cgImage = CameraViewManager.sharedCIContext.createCGImage(ciImage, from: ciImage.extent) else {
|
|
57
58
|
completionHandler(nil, CameraError.frameCaptureError)
|
|
58
59
|
return
|
|
59
60
|
}
|
|
@@ -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
|
|
|
@@ -16,11 +18,39 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
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()
|
|
@@ -74,7 +111,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
74
111
|
)
|
|
75
112
|
}
|
|
76
113
|
|
|
77
|
-
|
|
114
|
+
sessionQueue.async { [weak self] in
|
|
78
115
|
guard let self = self else { return }
|
|
79
116
|
|
|
80
117
|
do {
|
|
@@ -93,12 +130,15 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
93
130
|
self.displayPreview(
|
|
94
131
|
on: webView,
|
|
95
132
|
completion: { error in
|
|
96
|
-
if error != nil {
|
|
133
|
+
if error != nil {
|
|
134
|
+
completion(error)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
97
137
|
|
|
98
138
|
// Handle barcode detection after session is running
|
|
99
139
|
if configuration.enableBarcodeDetection {
|
|
100
140
|
do {
|
|
101
|
-
try self.enableBarcodeDetection()
|
|
141
|
+
try self.enableBarcodeDetection(barcodeTypes: configuration.barcodeTypes)
|
|
102
142
|
} catch {
|
|
103
143
|
completion(error)
|
|
104
144
|
return
|
|
@@ -119,18 +159,21 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
119
159
|
}
|
|
120
160
|
}
|
|
121
161
|
|
|
122
|
-
/// Stops the current capture session.
|
|
162
|
+
/// Stops the current capture session and cleans up temporary files.
|
|
123
163
|
public func stopSession(completion: (() -> Void)? = nil) {
|
|
124
164
|
guard captureSession.isRunning else {
|
|
125
165
|
completion?()
|
|
126
166
|
return
|
|
127
167
|
}
|
|
128
168
|
|
|
129
|
-
|
|
169
|
+
sessionQueue.async { [weak self] in
|
|
130
170
|
self?.captureSession.stopRunning()
|
|
131
171
|
|
|
132
172
|
DispatchQueue.main.async { [weak self] in
|
|
133
|
-
guard let self = self else {
|
|
173
|
+
guard let self = self else {
|
|
174
|
+
completion?()
|
|
175
|
+
return
|
|
176
|
+
}
|
|
134
177
|
self.videoPreviewLayer.removeFromSuperlayer()
|
|
135
178
|
self.webView?.isOpaque = true
|
|
136
179
|
self.webView?.backgroundColor = nil
|
|
@@ -140,9 +183,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
140
183
|
blurOverlayView.removeFromSuperview()
|
|
141
184
|
self.blurOverlayView = nil
|
|
142
185
|
}
|
|
143
|
-
}
|
|
144
186
|
|
|
145
|
-
|
|
187
|
+
completion?()
|
|
188
|
+
}
|
|
146
189
|
}
|
|
147
190
|
}
|
|
148
191
|
|
|
@@ -173,11 +216,10 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
173
216
|
|
|
174
217
|
// Ensure proper orientation
|
|
175
218
|
if let photoConnection = avPhotoOutput.connection(with: .video),
|
|
176
|
-
|
|
219
|
+
let previewConnection = videoPreviewLayer.connection
|
|
177
220
|
{
|
|
178
221
|
if photoConnection.isVideoOrientationSupported {
|
|
179
|
-
photoConnection.videoOrientation =
|
|
180
|
-
previewConnection.videoOrientation
|
|
222
|
+
photoConnection.videoOrientation = previewConnection.videoOrientation
|
|
181
223
|
}
|
|
182
224
|
}
|
|
183
225
|
|
|
@@ -185,6 +227,42 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
185
227
|
photoCaptureHandler = completion
|
|
186
228
|
}
|
|
187
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
|
+
|
|
188
266
|
/// Capture a snapshot of the current camera view. This is faster than actually processing a
|
|
189
267
|
/// photo via capturePhoto
|
|
190
268
|
/// - Parameter completion: called with the captured UIImage or an error.
|
|
@@ -203,20 +281,14 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
203
281
|
|
|
204
282
|
// Ensure proper orientation
|
|
205
283
|
if let videoConnection = avVideoDataOutput.connection(with: .video),
|
|
206
|
-
|
|
284
|
+
let previewConnection = videoPreviewLayer.connection
|
|
207
285
|
{
|
|
208
286
|
if videoConnection.isVideoOrientationSupported {
|
|
209
|
-
videoConnection.videoOrientation =
|
|
210
|
-
previewConnection.videoOrientation
|
|
287
|
+
videoConnection.videoOrientation = previewConnection.videoOrientation
|
|
211
288
|
}
|
|
212
289
|
}
|
|
213
290
|
|
|
214
|
-
//
|
|
215
|
-
let sampleBufferQueue = DispatchQueue(
|
|
216
|
-
label: "com.michaelwolz.capacitorcameraview.snapshotQueue"
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
// Set the delegate for a single frame capture
|
|
291
|
+
// Set the delegate for a single frame capture using the reusable queue
|
|
220
292
|
snapshotCompletionHandler = completion
|
|
221
293
|
avVideoDataOutput.setSampleBufferDelegate(
|
|
222
294
|
self,
|
|
@@ -227,9 +299,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
227
299
|
/// Flips the camera to the opposite position (front to back or back to front).
|
|
228
300
|
public func flipCamera() throws {
|
|
229
301
|
let currentPosition: AVCaptureDevice.Position =
|
|
230
|
-
|
|
302
|
+
currentCameraDevice?.position ?? .back
|
|
231
303
|
let newPosition: AVCaptureDevice.Position =
|
|
232
|
-
|
|
304
|
+
currentPosition == .back ? .front : .back
|
|
233
305
|
|
|
234
306
|
let newCamera = try getCameraDevice(for: newPosition)
|
|
235
307
|
try setInput(with: newCamera)
|
|
@@ -459,8 +531,9 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
459
531
|
/// figure out the root cause of this but it might generally be a good idea
|
|
460
532
|
/// to only add the metadata output when the session is already running.
|
|
461
533
|
///
|
|
534
|
+
/// - Parameter barcodeTypes: Optional array of barcode types to detect. If nil, all supported types are used.
|
|
462
535
|
/// - Throws: An error if the metadata output cannot be added.
|
|
463
|
-
private func enableBarcodeDetection() throws {
|
|
536
|
+
private func enableBarcodeDetection(barcodeTypes: [AVMetadataObject.ObjectType]? = nil) throws {
|
|
464
537
|
guard captureSession.isRunning else {
|
|
465
538
|
throw CameraError.sessionNotRunning
|
|
466
539
|
}
|
|
@@ -468,7 +541,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
468
541
|
captureSession.beginConfiguration()
|
|
469
542
|
defer { captureSession.commitConfiguration() }
|
|
470
543
|
|
|
471
|
-
try setupMetadataOutput()
|
|
544
|
+
try setupMetadataOutput(barcodeTypes: barcodeTypes)
|
|
472
545
|
}
|
|
473
546
|
|
|
474
547
|
/// Retrieve a list of a available camera devices
|
|
@@ -502,7 +575,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
502
575
|
/// - Returns: The camera device for the specified position
|
|
503
576
|
/// - Throws: An error if no camera device is found.
|
|
504
577
|
private func getCameraDevice(for position: AVCaptureDevice.Position?) throws
|
|
505
|
-
|
|
578
|
+
-> AVCaptureDevice
|
|
506
579
|
{
|
|
507
580
|
let preferredDevices = getPreferredCameraDevices()
|
|
508
581
|
|
|
@@ -515,11 +588,10 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
515
588
|
// If we haven't found one we try to get a best match for the position by iterating all supported device types
|
|
516
589
|
// Only doing this when preferredCameraDeviceTypes size differs from SUPPORTED_CAMERA_DEVICE_TYPES, otherwise
|
|
517
590
|
// we don't have to initialize a new discovery session
|
|
518
|
-
if preferredCameraDeviceTypes.count
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
})
|
|
591
|
+
if preferredCameraDeviceTypes.count < SUPPORTED_CAMERA_DEVICE_TYPES.count,
|
|
592
|
+
let match = getAvailableDevices().first(where: {
|
|
593
|
+
$0.position == position
|
|
594
|
+
})
|
|
523
595
|
{
|
|
524
596
|
return match
|
|
525
597
|
}
|
|
@@ -530,8 +602,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
530
602
|
}
|
|
531
603
|
|
|
532
604
|
// Log when we're falling back to a device with different position than requested
|
|
533
|
-
if let requestedPosition = position,
|
|
534
|
-
device.position != requestedPosition
|
|
605
|
+
if let requestedPosition = position, device.position != requestedPosition
|
|
535
606
|
{
|
|
536
607
|
print(
|
|
537
608
|
"Warning: Falling back to camera at position \(device.position) when \(requestedPosition) was requested"
|
|
@@ -548,7 +619,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
548
619
|
/// - Returns: The camera device for the specified position
|
|
549
620
|
/// - Throws: An error if no camera device is found.
|
|
550
621
|
private func getCameraDeviceById(_ deviceId: String) throws
|
|
551
|
-
|
|
622
|
+
-> AVCaptureDevice
|
|
552
623
|
{
|
|
553
624
|
guard
|
|
554
625
|
let device = getAvailableDevices().first(where: {
|
|
@@ -668,7 +739,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
668
739
|
/// - Parameter duration: The duration of the fade out animation
|
|
669
740
|
@MainActor
|
|
670
741
|
private func removeBlurOverlayWithAnimation(duration: TimeInterval = 0.3)
|
|
671
|
-
|
|
742
|
+
async
|
|
672
743
|
{
|
|
673
744
|
guard let blurEffectView = blurOverlayView else { return }
|
|
674
745
|
|
|
@@ -703,14 +774,14 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
703
774
|
/// Updates the preview layer orientation based on the current device orientation.
|
|
704
775
|
private func updatePreviewOrientation() {
|
|
705
776
|
guard let connection = self.videoPreviewLayer.connection,
|
|
706
|
-
|
|
777
|
+
connection.isVideoOrientationSupported
|
|
707
778
|
else {
|
|
708
779
|
return
|
|
709
780
|
}
|
|
710
781
|
|
|
711
|
-
let interfaceOrientation =
|
|
712
|
-
|
|
713
|
-
.interfaceOrientation ?? .portrait
|
|
782
|
+
let interfaceOrientation = UIApplication.shared.connectedScenes
|
|
783
|
+
.compactMap({ $0 as? UIWindowScene })
|
|
784
|
+
.first?.interfaceOrientation ?? .portrait
|
|
714
785
|
let videoOrientation: AVCaptureVideoOrientation
|
|
715
786
|
|
|
716
787
|
switch interfaceOrientation {
|
|
@@ -758,7 +829,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
758
829
|
@objc private func handleAppWillResignActive() {
|
|
759
830
|
// Pause the session when app goes to background to save resources
|
|
760
831
|
if captureSession.isRunning {
|
|
761
|
-
|
|
832
|
+
sessionQueue.async { [weak self] in
|
|
762
833
|
self?.captureSession.stopRunning()
|
|
763
834
|
}
|
|
764
835
|
}
|
|
@@ -768,7 +839,7 @@ internal let SUPPORTED_CAMERA_DEVICE_TYPES: [AVCaptureDevice.DeviceType] = [
|
|
|
768
839
|
@objc private func handleAppDidBecomeActive() {
|
|
769
840
|
// Resume the session when app comes back to foreground
|
|
770
841
|
if !captureSession.isRunning && webView != nil {
|
|
771
|
-
|
|
842
|
+
sessionQueue.async { [weak self] in
|
|
772
843
|
self?.captureSession.startRunning()
|
|
773
844
|
}
|
|
774
845
|
}
|