@capgo/camera-preview 7.3.9 → 7.4.0-beta.2

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 (56) hide show
  1. package/CapgoCameraPreview.podspec +16 -13
  2. package/README.md +306 -70
  3. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  4. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.14.2/fileChanges/last-build.bin +0 -0
  8. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  10. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  11. package/android/.gradle/8.14.2/gc.properties +0 -0
  12. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  13. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  14. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  15. package/android/.gradle/file-system.probe +0 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/build.gradle +9 -0
  18. package/android/src/main/AndroidManifest.xml +5 -0
  19. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +260 -551
  20. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +968 -0
  21. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +54 -0
  22. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +70 -0
  23. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +65 -0
  24. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +34 -0
  25. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +34 -0
  26. package/dist/docs.json +729 -153
  27. package/dist/esm/definitions.d.ts +337 -80
  28. package/dist/esm/definitions.js +10 -1
  29. package/dist/esm/definitions.js.map +1 -1
  30. package/dist/esm/web.d.ts +27 -1
  31. package/dist/esm/web.js +248 -4
  32. package/dist/esm/web.js.map +1 -1
  33. package/dist/plugin.cjs.js +256 -4
  34. package/dist/plugin.cjs.js.map +1 -1
  35. package/dist/plugin.js +256 -4
  36. package/dist/plugin.js.map +1 -1
  37. package/ios/{Plugin → Sources/CapgoCameraPreview}/CameraController.swift +359 -34
  38. package/ios/{Plugin → Sources/CapgoCameraPreview}/Plugin.swift +348 -42
  39. package/ios/Tests/CameraPreviewPluginTests/CameraPreviewPluginTests.swift +15 -0
  40. package/package.json +1 -1
  41. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +0 -1279
  42. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +0 -29
  43. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +0 -39
  44. package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +0 -461
  45. package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +0 -24
  46. package/ios/Plugin/Info.plist +0 -24
  47. package/ios/Plugin/Plugin.h +0 -10
  48. package/ios/Plugin/Plugin.m +0 -18
  49. package/ios/Plugin.xcodeproj/project.pbxproj +0 -593
  50. package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  51. package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
  52. package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  53. package/ios/PluginTests/Info.plist +0 -22
  54. package/ios/PluginTests/PluginTests.swift +0 -83
  55. package/ios/Podfile +0 -13
  56. package/ios/Podfile.lock +0 -23
@@ -1,6 +1,8 @@
1
1
  import Foundation
2
2
  import Capacitor
3
3
  import AVFoundation
4
+ import Photos
5
+ import CoreImage
4
6
 
5
7
  extension UIWindow {
6
8
  static var isLandscape: Bool {
@@ -47,7 +49,14 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
47
49
  CAPPluginMethod(name: "startRecordVideo", returnType: CAPPluginReturnPromise),
48
50
  CAPPluginMethod(name: "stopRecordVideo", returnType: CAPPluginReturnPromise),
49
51
  CAPPluginMethod(name: "getTempFilePath", returnType: CAPPluginReturnPromise),
50
- CAPPluginMethod(name: "getSupportedPictureSizes", returnType: CAPPluginReturnPromise)
52
+ CAPPluginMethod(name: "getSupportedPictureSizes", returnType: CAPPluginReturnPromise),
53
+ CAPPluginMethod(name: "isRunning", returnType: CAPPluginReturnPromise),
54
+ CAPPluginMethod(name: "getAvailableDevices", returnType: CAPPluginReturnPromise),
55
+ CAPPluginMethod(name: "getZoom", returnType: CAPPluginReturnPromise),
56
+ CAPPluginMethod(name: "setZoom", returnType: CAPPluginReturnPromise),
57
+ CAPPluginMethod(name: "getFlashMode", returnType: CAPPluginReturnPromise),
58
+ CAPPluginMethod(name: "setDeviceId", returnType: CAPPluginReturnPromise),
59
+ CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise)
51
60
  ]
52
61
  // Camera state tracking
53
62
  private var isInitializing: Bool = false
@@ -68,6 +77,39 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
68
77
  var highResolutionOutput: Bool = false
69
78
  var disableAudio: Bool = false
70
79
 
80
+ // MARK: - Transparency Methods
81
+
82
+ private func makeWebViewTransparent() {
83
+ guard let webView = self.webView else { return }
84
+
85
+ // Define a recursive function to traverse the view hierarchy
86
+ func makeSubviewsTransparent(_ view: UIView) {
87
+ // Set the background color to clear
88
+ view.backgroundColor = .clear
89
+
90
+ // Recurse for all subviews
91
+ for subview in view.subviews {
92
+ makeSubviewsTransparent(subview)
93
+ }
94
+ }
95
+
96
+ // Set the main webView to be transparent
97
+ webView.isOpaque = false
98
+ webView.backgroundColor = .clear
99
+
100
+ // Recursively make all subviews transparent
101
+ makeSubviewsTransparent(webView)
102
+
103
+ // Also ensure the webview's container is transparent
104
+ webView.superview?.backgroundColor = .clear
105
+
106
+ // Force a layout pass to apply changes
107
+ DispatchQueue.main.async {
108
+ webView.setNeedsLayout()
109
+ webView.layoutIfNeeded()
110
+ }
111
+ }
112
+
71
113
  @objc func rotated() {
72
114
  guard let previewView = self.previewView,
73
115
  let posX = self.posX,
@@ -105,6 +147,27 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
105
147
  }
106
148
 
107
149
  cameraController.updateVideoOrientation()
150
+
151
+ // Ensure webview remains transparent after rotation
152
+ if self.isInitialized {
153
+ self.makeWebViewTransparent()
154
+ }
155
+ }
156
+
157
+ @objc func appDidBecomeActive() {
158
+ if self.isInitialized {
159
+ DispatchQueue.main.async {
160
+ self.makeWebViewTransparent()
161
+ }
162
+ }
163
+ }
164
+
165
+ @objc func appWillEnterForeground() {
166
+ if self.isInitialized {
167
+ DispatchQueue.main.async {
168
+ self.makeWebViewTransparent()
169
+ }
170
+ }
108
171
  }
109
172
 
110
173
  struct CameraInfo {
@@ -119,8 +182,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
119
182
  // Discover all available cameras
120
183
  let deviceTypes: [AVCaptureDevice.DeviceType] = [
121
184
  .builtInWideAngleCamera,
122
- .builtInTelephotoCamera,
123
185
  .builtInUltraWideCamera,
186
+ .builtInTelephotoCamera,
187
+ .builtInDualCamera,
188
+ .builtInDualWideCamera,
189
+ .builtInTripleCamera,
124
190
  .builtInTrueDepthCamera
125
191
  ]
126
192
 
@@ -197,6 +263,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
197
263
  self.isInitializing = true
198
264
 
199
265
  self.cameraPosition = call.getString("position") ?? "rear"
266
+ let deviceId = call.getString("deviceId")
200
267
  let cameraMode = call.getBool("cameraMode") ?? false
201
268
  self.highResolutionOutput = call.getBool("enableHighResolution") ?? false
202
269
  self.cameraController.highResolutionOutput = self.highResolutionOutput
@@ -218,7 +285,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
218
285
  }
219
286
 
220
287
  self.rotateWhenOrientationChanged = call.getBool("rotateWhenOrientationChanged") ?? true
221
- self.toBack = call.getBool("toBack") ?? false
288
+ self.toBack = call.getBool("toBack") ?? true
222
289
  self.storeToFile = call.getBool("storeToFile") ?? false
223
290
  self.enableZoom = call.getBool("enableZoom") ?? false
224
291
  self.disableAudio = call.getBool("disableAudio") ?? false
@@ -233,7 +300,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
233
300
  if self.cameraController.captureSession?.isRunning ?? false {
234
301
  call.reject("camera already started")
235
302
  } else {
236
- self.cameraController.prepare(cameraPosition: self.cameraPosition, disableAudio: self.disableAudio, cameraMode: cameraMode) {error in
303
+ self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode) {error in
237
304
  if let error = error {
238
305
  print(error)
239
306
  call.reject(error.localizedDescription)
@@ -241,9 +308,10 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
241
308
  }
242
309
  let height = self.paddingBottom != nil ? self.height! - self.paddingBottom!: self.height!
243
310
  self.previewView = UIView(frame: CGRect(x: self.posX ?? 0, y: self.posY ?? 0, width: self.width!, height: height))
244
- self.webView?.isOpaque = false
245
- self.webView?.backgroundColor = UIColor.clear
246
- self.webView?.scrollView.backgroundColor = UIColor.clear
311
+
312
+ // Make webview transparent - comprehensive approach
313
+ self.makeWebViewTransparent()
314
+
247
315
  self.webView?.superview?.addSubview(self.previewView)
248
316
  if self.toBack! {
249
317
  self.webView?.superview?.bringSubviewToFront(self.webView!)
@@ -256,6 +324,10 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
256
324
  if self.rotateWhenOrientationChanged == true {
257
325
  NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
258
326
  }
327
+
328
+ // Add observers for app state changes to maintain transparency
329
+ NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
330
+ NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
259
331
 
260
332
  self.isInitializing = false
261
333
  self.isInitialized = true
@@ -273,34 +345,38 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
273
345
  call.reject("Camera not initialized")
274
346
  return
275
347
  }
276
-
348
+
277
349
  DispatchQueue.main.async { [weak self] in
278
350
  guard let self = self else {
279
351
  call.reject("Camera controller deallocated")
280
352
  return
281
353
  }
282
-
354
+
283
355
  // Disable user interaction during flip
284
356
  self.previewView.isUserInteractionEnabled = false
285
-
357
+
286
358
  // Perform camera switch on background thread
287
359
  DispatchQueue.global(qos: .userInitiated).async {
288
360
  var retryCount = 0
289
361
  let maxRetries = 3
290
-
362
+
291
363
  func attemptFlip() {
292
364
  do {
293
365
  try self.cameraController.switchCameras()
294
-
366
+
295
367
  DispatchQueue.main.async {
296
368
  self.cameraController.previewLayer?.frame = self.previewView.bounds
297
369
  self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
298
370
  self.previewView.isUserInteractionEnabled = true
371
+
372
+ // Ensure webview remains transparent after flip
373
+ self.makeWebViewTransparent()
374
+
299
375
  call.resolve()
300
376
  }
301
377
  } catch {
302
378
  retryCount += 1
303
-
379
+
304
380
  if retryCount < maxRetries {
305
381
  DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.5) {
306
382
  attemptFlip()
@@ -314,7 +390,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
314
390
  }
315
391
  }
316
392
  }
317
-
393
+
318
394
  attemptFlip()
319
395
  }
320
396
  }
@@ -330,18 +406,21 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
330
406
  call.reject("camera not initialized")
331
407
  return
332
408
  }
333
-
409
+
334
410
  // Always attempt to stop and clean up, regardless of captureSession state
335
411
  if let previewView = self.previewView {
336
412
  previewView.removeFromSuperview()
337
413
  self.previewView = nil
338
414
  }
339
-
415
+
340
416
  self.webView?.isOpaque = true
341
417
  self.isInitialized = false
342
418
  self.isInitializing = false
343
419
  self.cameraController.cleanup()
344
420
 
421
+ // Remove notification observers
422
+ NotificationCenter.default.removeObserver(self)
423
+
345
424
  call.resolve()
346
425
  }
347
426
  }
@@ -359,43 +438,56 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
359
438
  @objc func capture(_ call: CAPPluginCall) {
360
439
  DispatchQueue.main.async {
361
440
 
362
- let quality: Int? = call.getInt("quality", 85)
363
-
364
- self.cameraController.captureImage { (image, error) in
441
+ let quality = call.getFloat("quality", 85)
442
+ let saveToGallery = call.getBool("saveToGallery", false)
365
443
 
366
- guard let image = image else {
367
- print(error ?? "Image capture error")
368
- guard let error = error else {
369
- call.reject("Image capture error")
370
- return
371
- }
444
+ self.cameraController.captureImage(quality: quality) { (image, error) in
445
+ if let error = error {
372
446
  call.reject(error.localizedDescription)
373
447
  return
374
448
  }
375
- let imageData: Data?
376
- if self.cameraController.currentCameraPosition == .front {
377
- let flippedImage = image.withHorizontallyFlippedOrientation()
378
- imageData = flippedImage.jpegData(compressionQuality: CGFloat(quality!/100))
379
- } else {
380
- imageData = image.jpegData(compressionQuality: CGFloat(quality!/100))
449
+
450
+ if saveToGallery {
451
+ PHPhotoLibrary.shared().performChanges({
452
+ PHAssetChangeRequest.creationRequestForAsset(from: image!)
453
+ }, completionHandler: { (success, error) in
454
+ if !success {
455
+ Logger.error("CameraPreview", "Error saving image to gallery", error)
456
+ }
457
+ })
381
458
  }
382
459
 
383
- if self.storeToFile == false {
384
- let imageBase64 = imageData?.base64EncodedString()
385
- call.resolve(["value": imageBase64!])
386
- } else {
387
- do {
388
- let fileUrl=self.getTempFilePath()
389
- try imageData?.write(to: fileUrl)
390
- call.resolve(["value": fileUrl.absoluteString])
391
- } catch {
392
- call.reject("error writing image to file")
393
- }
460
+ guard let imageData = image?.jpegData(compressionQuality: CGFloat(quality / 100.0)) else {
461
+ call.reject("Failed to get JPEG data from image")
462
+ return
394
463
  }
464
+
465
+ let exifData = self.getExifData(from: imageData)
466
+ let base64Image = imageData.base64EncodedString()
467
+
468
+ var result = JSObject()
469
+ result["value"] = base64Image
470
+ result["exif"] = exifData
471
+ call.resolve(result)
395
472
  }
396
473
  }
397
474
  }
398
475
 
476
+ private func getExifData(from imageData: Data) -> JSObject {
477
+ guard let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil),
478
+ let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any],
479
+ let exifDict = imageProperties[kCGImagePropertyExifDictionary as String] as? [String: Any] else {
480
+ return [:]
481
+ }
482
+
483
+ var exifData = JSObject()
484
+ for (key, value) in exifDict {
485
+ exifData[key] = value
486
+ }
487
+
488
+ return exifData
489
+ }
490
+
399
491
  @objc func captureSample(_ call: CAPPluginCall) {
400
492
  DispatchQueue.main.async {
401
493
  let quality: Int? = call.getInt("quality", 85)
@@ -508,4 +600,218 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
508
600
  }
509
601
  }
510
602
 
603
+ @objc func isRunning(_ call: CAPPluginCall) {
604
+ let isRunning = self.isInitialized && (self.cameraController.captureSession?.isRunning ?? false)
605
+ call.resolve(["isRunning": isRunning])
606
+ }
607
+
608
+ @objc func getAvailableDevices(_ call: CAPPluginCall) {
609
+ let deviceTypes: [AVCaptureDevice.DeviceType] = [
610
+ .builtInWideAngleCamera,
611
+ .builtInUltraWideCamera,
612
+ .builtInTelephotoCamera,
613
+ .builtInDualCamera,
614
+ .builtInDualWideCamera,
615
+ .builtInTripleCamera,
616
+ .builtInTrueDepthCamera
617
+ ]
618
+
619
+ let session = AVCaptureDevice.DiscoverySession(
620
+ deviceTypes: deviceTypes,
621
+ mediaType: .video,
622
+ position: .unspecified
623
+ )
624
+
625
+ var devices: [[String: Any]] = []
626
+
627
+ // Collect all devices by position
628
+ for device in session.devices {
629
+ var lenses: [[String: Any]] = []
630
+
631
+ let constituentDevices = device.isVirtualDevice ? device.constituentDevices : [device]
632
+
633
+ for lensDevice in constituentDevices {
634
+ var deviceType: String
635
+ switch lensDevice.deviceType {
636
+ case .builtInWideAngleCamera: deviceType = "wideAngle"
637
+ case .builtInUltraWideCamera: deviceType = "ultraWide"
638
+ case .builtInTelephotoCamera: deviceType = "telephoto"
639
+ case .builtInDualCamera: deviceType = "dual"
640
+ case .builtInDualWideCamera: deviceType = "dualWide"
641
+ case .builtInTripleCamera: deviceType = "triple"
642
+ case .builtInTrueDepthCamera: deviceType = "trueDepth"
643
+ default: deviceType = "unknown"
644
+ }
645
+
646
+ var baseZoomRatio: Float = 1.0
647
+ if lensDevice.deviceType == .builtInUltraWideCamera {
648
+ baseZoomRatio = 0.5
649
+ } else if lensDevice.deviceType == .builtInTelephotoCamera {
650
+ baseZoomRatio = 2.0 // A common value for telephoto lenses
651
+ }
652
+
653
+ let lensInfo: [String: Any] = [
654
+ "label": lensDevice.localizedName,
655
+ "deviceType": deviceType,
656
+ "focalLength": 4.25, // Placeholder
657
+ "baseZoomRatio": baseZoomRatio,
658
+ "minZoom": Float(lensDevice.minAvailableVideoZoomFactor),
659
+ "maxZoom": Float(lensDevice.maxAvailableVideoZoomFactor)
660
+ ]
661
+ lenses.append(lensInfo)
662
+ }
663
+
664
+ let deviceData: [String: Any] = [
665
+ "deviceId": device.uniqueID,
666
+ "label": device.localizedName,
667
+ "position": device.position == .front ? "front" : "rear",
668
+ "lenses": lenses,
669
+ "minZoom": Float(device.minAvailableVideoZoomFactor),
670
+ "maxZoom": Float(device.maxAvailableVideoZoomFactor),
671
+ "isLogical": device.isVirtualDevice
672
+ ]
673
+
674
+ devices.append(deviceData)
675
+ }
676
+
677
+ call.resolve(["devices": devices])
678
+ }
679
+
680
+ @objc func getZoom(_ call: CAPPluginCall) {
681
+ guard isInitialized else {
682
+ call.reject("Camera not initialized")
683
+ return
684
+ }
685
+
686
+ do {
687
+ let zoomInfo = try self.cameraController.getZoom()
688
+ let lensInfo = try self.cameraController.getCurrentLensInfo()
689
+
690
+ var minZoom = zoomInfo.min
691
+ var maxZoom = zoomInfo.max
692
+ var currentZoom = zoomInfo.current
693
+
694
+ // If using the multi-lens camera, translate the native zoom values for JS
695
+ if self.cameraController.isUsingMultiLensVirtualCamera {
696
+ minZoom -= 0.5
697
+ maxZoom -= 0.5
698
+ currentZoom -= 0.5
699
+ }
700
+
701
+ call.resolve([
702
+ "min": minZoom,
703
+ "max": maxZoom,
704
+ "current": currentZoom,
705
+ "lens": [
706
+ "focalLength": lensInfo.focalLength,
707
+ "deviceType": lensInfo.deviceType,
708
+ "baseZoomRatio": lensInfo.baseZoomRatio,
709
+ "digitalZoom": Float(currentZoom) / lensInfo.baseZoomRatio
710
+ ]
711
+ ])
712
+ } catch {
713
+ call.reject("Failed to get zoom: \(error.localizedDescription)")
714
+ }
715
+ }
716
+
717
+ @objc func setZoom(_ call: CAPPluginCall) {
718
+ guard isInitialized else {
719
+ call.reject("Camera not initialized")
720
+ return
721
+ }
722
+
723
+ guard var level = call.getFloat("level") else {
724
+ call.reject("level parameter is required")
725
+ return
726
+ }
727
+
728
+ // If using the multi-lens camera, translate the JS zoom value for the native layer
729
+ if self.cameraController.isUsingMultiLensVirtualCamera {
730
+ level += 0.5
731
+ }
732
+
733
+ let ramp = call.getBool("ramp") ?? true
734
+
735
+ do {
736
+ try self.cameraController.setZoom(level: CGFloat(level), ramp: ramp)
737
+ call.resolve()
738
+ } catch {
739
+ call.reject("Failed to set zoom: \(error.localizedDescription)")
740
+ }
741
+ }
742
+
743
+ @objc func getFlashMode(_ call: CAPPluginCall) {
744
+ guard isInitialized else {
745
+ call.reject("Camera not initialized")
746
+ return
747
+ }
748
+
749
+ do {
750
+ let flashMode = try self.cameraController.getFlashMode()
751
+ call.resolve(["flashMode": flashMode])
752
+ } catch {
753
+ call.reject("Failed to get flash mode: \(error.localizedDescription)")
754
+ }
755
+ }
756
+
757
+ @objc func setDeviceId(_ call: CAPPluginCall) {
758
+ guard isInitialized else {
759
+ call.reject("Camera not initialized")
760
+ return
761
+ }
762
+
763
+ guard let deviceId = call.getString("deviceId") else {
764
+ call.reject("deviceId parameter is required")
765
+ return
766
+ }
767
+
768
+ DispatchQueue.main.async { [weak self] in
769
+ guard let self = self else {
770
+ call.reject("Camera controller deallocated")
771
+ return
772
+ }
773
+
774
+ // Disable user interaction during device swap
775
+ self.previewView.isUserInteractionEnabled = false
776
+
777
+ DispatchQueue.global(qos: .userInitiated).async {
778
+ do {
779
+ try self.cameraController.swapToDevice(deviceId: deviceId)
780
+
781
+ DispatchQueue.main.async {
782
+ self.cameraController.previewLayer?.frame = self.previewView.bounds
783
+ self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
784
+ self.previewView.isUserInteractionEnabled = true
785
+
786
+ // Ensure webview remains transparent after device switch
787
+ self.makeWebViewTransparent()
788
+
789
+ call.resolve()
790
+ }
791
+ } catch {
792
+ DispatchQueue.main.async {
793
+ self.previewView.isUserInteractionEnabled = true
794
+ call.reject("Failed to swap to device \(deviceId): \(error.localizedDescription)")
795
+ }
796
+ }
797
+ }
798
+ }
799
+ }
800
+
801
+ @objc func getDeviceId(_ call: CAPPluginCall) {
802
+ guard isInitialized else {
803
+ call.reject("Camera not initialized")
804
+ return
805
+ }
806
+
807
+ do {
808
+ let deviceId = try self.cameraController.getCurrentDeviceId()
809
+ call.resolve(["deviceId": deviceId])
810
+ } catch {
811
+ call.reject("Failed to get device ID: \(error.localizedDescription)")
812
+ }
813
+ }
814
+
815
+
816
+
511
817
  }
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+ @testable import CameraViewPlugin
3
+
4
+ class CameraViewTests: XCTestCase {
5
+ func testEcho() {
6
+ // This is an example of a functional test case for a plugin.
7
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
8
+
9
+ let implementation = CameraView()
10
+ let value = "Hello, World!"
11
+ let result = implementation.echo(value)
12
+
13
+ XCTAssertEqual(value, result)
14
+ }
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.3.9",
3
+ "version": "7.4.0-beta.2",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {