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.
Files changed (28) hide show
  1. package/README.md +19 -9
  2. package/android/build.gradle +8 -5
  3. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +217 -126
  4. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +70 -30
  5. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraResult.kt +47 -0
  6. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +11 -1
  7. package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +94 -5
  8. package/dist/docs.json +81 -0
  9. package/dist/esm/definitions.d.ts +44 -0
  10. package/dist/esm/definitions.js.map +1 -1
  11. package/dist/esm/web.d.ts +7 -1
  12. package/dist/esm/web.js +67 -2
  13. package/dist/esm/web.js.map +1 -1
  14. package/dist/plugin.cjs.js +68 -2
  15. package/dist/plugin.cjs.js.map +1 -1
  16. package/dist/plugin.js +68 -2
  17. package/dist/plugin.js.map +1 -1
  18. package/ios/Sources/CameraViewPlugin/CameraError.swift +97 -2
  19. package/ios/Sources/CameraViewPlugin/CameraEvents.swift +109 -0
  20. package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +29 -2
  21. package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +30 -41
  22. package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +45 -13
  23. package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +4 -3
  24. package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +112 -41
  25. package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +83 -84
  26. package/ios/Sources/CameraViewPlugin/TempFileManager.swift +181 -0
  27. package/ios/Sources/CameraViewPlugin/Utils.swift +102 -0
  28. 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 metadataOutput = AVCaptureMetadataOutput()
11
+ internal func setupMetadataOutput(barcodeTypes: [AVMetadataObject.ObjectType]? = nil) throws {
12
+ let requestedBarcodeTypes = barcodeTypes ?? ALL_SUPPORTED_BARCODE_TYPES
11
13
 
12
- if (captureSession.outputs.contains { $0 is AVCaptureMetadataOutput }) {
13
- // Nothing todo, we already have an output
14
- return
15
- }
14
+ let metadataOutput: AVCaptureMetadataOutput
16
15
 
17
- if !captureSession.canAddOutput(metadataOutput) {
18
- throw CameraError.outputAdditionFailed
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
- captureSession.addOutput(metadataOutput)
24
+ captureSession.addOutput(newOutput)
25
+ newOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
26
+ metadataOutput = newOutput
27
+ }
22
28
 
23
- metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
29
+ let supportedTypes = Set(metadataOutput.availableMetadataObjectTypes)
30
+ let resolvedTypes = requestedBarcodeTypes.filter { supportedTypes.contains($0) }
24
31
 
25
- // Set all available barcode types
26
- metadataOutput.metadataObjectTypes = [
27
- .qr,
28
- .code128,
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: [String: Double] = [
84
- "x": Double(transformedMetadataObject.bounds.origin.x),
85
- "y": Double(transformedMetadataObject.bounds.origin.y),
86
- "width": Double(transformedMetadataObject.bounds.width),
87
- "height": Double(transformedMetadataObject.bounds.height)
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
- NotificationCenter.default.post(
91
- name: Notification.Name("barcodeDetected"),
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
- if let error = error {
39
- photoCaptureHandler?(nil, error)
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
- guard let data = photo.fileDataRepresentation(), let image = UIImage(data: data) else {
44
- photoCaptureHandler?(nil, CameraError.photoOutputError)
45
- return
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
- let context = CIContext()
56
- guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else {
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
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
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 { completion(error) }
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
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
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 { return }
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
- completion?()
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
- let previewConnection = videoPreviewLayer.connection
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
- let previewConnection = videoPreviewLayer.connection
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
- // Create a serial queue for sample buffer processing
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
- currentCameraDevice?.position ?? .back
302
+ currentCameraDevice?.position ?? .back
231
303
  let newPosition: AVCaptureDevice.Position =
232
- currentPosition == .back ? .front : .back
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
- -> AVCaptureDevice
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
- < SUPPORTED_CAMERA_DEVICE_TYPES.count,
520
- let match = getAvailableDevices().first(where: {
521
- $0.position == position
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
- -> AVCaptureDevice
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
- async
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
- connection.isVideoOrientationSupported
777
+ connection.isVideoOrientationSupported
707
778
  else {
708
779
  return
709
780
  }
710
781
 
711
- let interfaceOrientation =
712
- UIApplication.shared.windows.first?.windowScene?
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
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
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
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
842
+ sessionQueue.async { [weak self] in
772
843
  self?.captureSession.startRunning()
773
844
  }
774
845
  }