@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.
- package/CapgoCameraPreview.podspec +16 -13
- package/README.md +306 -70
- package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
- package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.14.2/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
- package/android/.gradle/8.14.2/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
- package/android/.gradle/file-system.probe +0 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +9 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +260 -551
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +968 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +54 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +70 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +65 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +34 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +34 -0
- package/dist/docs.json +729 -153
- package/dist/esm/definitions.d.ts +337 -80
- package/dist/esm/definitions.js +10 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +27 -1
- package/dist/esm/web.js +248 -4
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +256 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +256 -4
- package/dist/plugin.js.map +1 -1
- package/ios/{Plugin → Sources/CapgoCameraPreview}/CameraController.swift +359 -34
- package/ios/{Plugin → Sources/CapgoCameraPreview}/Plugin.swift +348 -42
- package/ios/Tests/CameraPreviewPluginTests/CameraPreviewPluginTests.swift +15 -0
- package/package.json +1 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +0 -1279
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +0 -29
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +0 -39
- package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +0 -461
- package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +0 -24
- package/ios/Plugin/Info.plist +0 -24
- package/ios/Plugin/Plugin.h +0 -10
- package/ios/Plugin/Plugin.m +0 -18
- package/ios/Plugin.xcodeproj/project.pbxproj +0 -593
- package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
- package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/PluginTests/Info.plist +0 -22
- package/ios/PluginTests/PluginTests.swift +0 -83
- package/ios/Podfile +0 -13
- 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") ??
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
self.
|
|
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
|
|
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
|
-
|
|
367
|
-
|
|
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
|
-
|
|
376
|
-
if
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
+
}
|