@capgo/camera-preview 7.4.0-alpha.0 → 7.4.0-alpha.11
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 +59 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +63 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +67 -8
- package/dist/docs.json +49 -0
- package/dist/esm/definitions.d.ts +15 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +24 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +8 -0
- package/dist/esm/web.js +18 -5
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +41 -5
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +41 -5
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +65 -30
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +280 -104
- package/package.json +1 -1
|
@@ -25,7 +25,7 @@ class CameraController: NSObject {
|
|
|
25
25
|
var focusIndicatorView: UIView?
|
|
26
26
|
|
|
27
27
|
var flashMode = AVCaptureDevice.FlashMode.off
|
|
28
|
-
var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
28
|
+
var photoCaptureCompletionBlock: ((UIImage?, Data?, [AnyHashable: Any]?, Error?) -> Void)?
|
|
29
29
|
|
|
30
30
|
var sampleBufferCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
31
31
|
|
|
@@ -51,6 +51,25 @@ class CameraController: NSObject {
|
|
|
51
51
|
// A rear multi-lens virtual camera will have a min zoom of 1.0 but support wider angles
|
|
52
52
|
return device.position == .back && device.isVirtualDevice && device.constituentDevices.count > 1
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
// Returns the display zoom multiplier introduced in iOS 18 to map between
|
|
56
|
+
// native zoom factor and the UI-displayed zoom factor. Falls back to 1.0 on
|
|
57
|
+
// older systems or if the property is unavailable.
|
|
58
|
+
func getDisplayZoomMultiplier() -> Float {
|
|
59
|
+
var multiplier: Float = 1.0
|
|
60
|
+
// Use KVC to avoid compile-time dependency on the iOS 18 SDK symbol
|
|
61
|
+
let device = (currentCameraPosition == .rear) ? rearCamera : frontCamera
|
|
62
|
+
if #available(iOS 18.0, *), let device = device {
|
|
63
|
+
if let value = device.value(forKey: "displayVideoZoomFactorMultiplier") as? NSNumber {
|
|
64
|
+
let m = value.floatValue
|
|
65
|
+
if m > 0 { multiplier = m }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return multiplier
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Track whether an aspect ratio was explicitly requested
|
|
72
|
+
var requestedAspectRatio: String?
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
extension CameraController {
|
|
@@ -68,7 +87,7 @@ extension CameraController {
|
|
|
68
87
|
discoverAndConfigureCameras()
|
|
69
88
|
}
|
|
70
89
|
|
|
71
|
-
|
|
90
|
+
private func discoverAndConfigureCameras() {
|
|
72
91
|
let deviceTypes: [AVCaptureDevice.DeviceType] = [
|
|
73
92
|
.builtInWideAngleCamera,
|
|
74
93
|
.builtInUltraWideCamera,
|
|
@@ -82,8 +101,6 @@ extension CameraController {
|
|
|
82
101
|
let session = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: .unspecified)
|
|
83
102
|
let cameras = session.devices.compactMap { $0 }
|
|
84
103
|
|
|
85
|
-
|
|
86
|
-
|
|
87
104
|
// Store all discovered devices for fast lookup later
|
|
88
105
|
self.allDiscoveredDevices = cameras
|
|
89
106
|
|
|
@@ -94,7 +111,7 @@ extension CameraController {
|
|
|
94
111
|
|
|
95
112
|
}
|
|
96
113
|
|
|
97
|
-
|
|
114
|
+
// Set front camera (usually just one option)
|
|
98
115
|
self.frontCamera = cameras.first(where: { $0.position == .front })
|
|
99
116
|
|
|
100
117
|
// Find rear camera - prefer tripleCamera for multi-lens support
|
|
@@ -171,7 +188,7 @@ extension CameraController {
|
|
|
171
188
|
self.outputsPrepared = true
|
|
172
189
|
}
|
|
173
190
|
|
|
174
|
-
func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, aspectRatio: String? = nil, initialZoomLevel: Float
|
|
191
|
+
func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, aspectRatio: String? = nil, initialZoomLevel: Float?, completionHandler: @escaping (Error?) -> Void) {
|
|
175
192
|
print("[CameraPreview] 🎬 Starting prepare - position: \(cameraPosition), deviceId: \(deviceId ?? "nil"), disableAudio: \(disableAudio), cameraMode: \(cameraMode), aspectRatio: \(aspectRatio ?? "nil"), zoom: \(initialZoomLevel)")
|
|
176
193
|
|
|
177
194
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
@@ -198,7 +215,8 @@ extension CameraController {
|
|
|
198
215
|
// Configure the session
|
|
199
216
|
captureSession.beginConfiguration()
|
|
200
217
|
|
|
201
|
-
// Set aspect ratio preset
|
|
218
|
+
// Set aspect ratio preset and remember requested ratio
|
|
219
|
+
self.requestedAspectRatio = aspectRatio
|
|
202
220
|
self.configureSessionPreset(for: aspectRatio)
|
|
203
221
|
|
|
204
222
|
// Configure device inputs
|
|
@@ -255,15 +273,18 @@ extension CameraController {
|
|
|
255
273
|
guard let captureSession = self.captureSession else { return }
|
|
256
274
|
|
|
257
275
|
var targetPreset: AVCaptureSession.Preset = .high
|
|
258
|
-
|
|
259
276
|
if let aspectRatio = aspectRatio {
|
|
260
277
|
switch aspectRatio {
|
|
261
278
|
case "16:9":
|
|
262
|
-
|
|
279
|
+
if captureSession.canSetSessionPreset(.hd4K3840x2160) {
|
|
280
|
+
targetPreset = .hd4K3840x2160
|
|
281
|
+
} else if captureSession.canSetSessionPreset(.hd1920x1080) {
|
|
282
|
+
targetPreset = .hd1920x1080
|
|
283
|
+
}
|
|
263
284
|
case "4:3":
|
|
264
|
-
targetPreset = captureSession.canSetSessionPreset(.
|
|
285
|
+
targetPreset = captureSession.canSetSessionPreset(.high) ? .high : captureSession.sessionPreset
|
|
265
286
|
default:
|
|
266
|
-
targetPreset = .high
|
|
287
|
+
targetPreset = captureSession.canSetSessionPreset(.high) ? .high : captureSession.sessionPreset
|
|
267
288
|
}
|
|
268
289
|
}
|
|
269
290
|
|
|
@@ -272,7 +293,7 @@ extension CameraController {
|
|
|
272
293
|
}
|
|
273
294
|
}
|
|
274
295
|
|
|
275
|
-
private func setInitialZoom(level: Float) {
|
|
296
|
+
private func setInitialZoom(level: Float?) {
|
|
276
297
|
let device = (currentCameraPosition == .rear) ? rearCamera : frontCamera
|
|
277
298
|
guard let device = device else {
|
|
278
299
|
print("[CameraPreview] No device available for initial zoom")
|
|
@@ -282,7 +303,12 @@ extension CameraController {
|
|
|
282
303
|
let minZoom = device.minAvailableVideoZoomFactor
|
|
283
304
|
let maxZoom = min(device.maxAvailableVideoZoomFactor, saneMaxZoomFactor)
|
|
284
305
|
|
|
285
|
-
|
|
306
|
+
// Compute UI-level default = 1 * multiplier when not provided
|
|
307
|
+
let multiplier = self.getDisplayZoomMultiplier()
|
|
308
|
+
// if level is nil, it's the initial zoom
|
|
309
|
+
let uiLevel: Float = level ?? (2.0 * multiplier)
|
|
310
|
+
// Map UI/display zoom to native zoom using iOS 18+ multiplier
|
|
311
|
+
let adjustedLevel = multiplier != 1.0 ? (uiLevel / multiplier) : uiLevel
|
|
286
312
|
|
|
287
313
|
guard CGFloat(adjustedLevel) >= minZoom && CGFloat(adjustedLevel) <= maxZoom else {
|
|
288
314
|
print("[CameraPreview] Initial zoom level \(adjustedLevel) out of range (\(minZoom)-\(maxZoom))")
|
|
@@ -378,7 +404,8 @@ extension CameraController {
|
|
|
378
404
|
}
|
|
379
405
|
|
|
380
406
|
// Fast configuration without CATransaction overhead
|
|
381
|
-
|
|
407
|
+
// Use resizeAspect to avoid crop when no aspect ratio is requested; otherwise fill
|
|
408
|
+
previewLayer.videoGravity = (requestedAspectRatio == nil) ? .resizeAspect : .resizeAspectFill
|
|
382
409
|
previewLayer.frame = view.bounds
|
|
383
410
|
|
|
384
411
|
// Insert layer immediately (only if new)
|
|
@@ -557,15 +584,22 @@ extension CameraController {
|
|
|
557
584
|
self.updateVideoOrientation()
|
|
558
585
|
}
|
|
559
586
|
|
|
560
|
-
func captureImage(width: Int?, height: Int?, aspectRatio: String?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Error?) -> Void) {
|
|
587
|
+
func captureImage(width: Int?, height: Int?, aspectRatio: String?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Data?, [AnyHashable: Any]?, Error?) -> Void) {
|
|
561
588
|
print("[CameraPreview] captureImage called - width: \(width ?? -1), height: \(height ?? -1), aspectRatio: \(aspectRatio ?? "nil")")
|
|
562
589
|
|
|
563
590
|
guard let photoOutput = self.photoOutput else {
|
|
564
|
-
completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
|
|
591
|
+
completion(nil, nil, nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
|
|
565
592
|
return
|
|
566
593
|
}
|
|
567
594
|
|
|
568
595
|
let settings = AVCapturePhotoSettings()
|
|
596
|
+
// Request highest quality photo capture
|
|
597
|
+
if #available(iOS 13.0, *) {
|
|
598
|
+
settings.isHighResolutionPhotoEnabled = true
|
|
599
|
+
}
|
|
600
|
+
if #available(iOS 15.0, *) {
|
|
601
|
+
settings.photoQualityPrioritization = .balanced
|
|
602
|
+
}
|
|
569
603
|
|
|
570
604
|
// Apply the current flash mode to the photo settings
|
|
571
605
|
// Check if the current device supports flash
|
|
@@ -587,14 +621,14 @@ extension CameraController {
|
|
|
587
621
|
}
|
|
588
622
|
}
|
|
589
623
|
|
|
590
|
-
self.photoCaptureCompletionBlock = { (image, error) in
|
|
624
|
+
self.photoCaptureCompletionBlock = { (image, photoData, metadata, error) in
|
|
591
625
|
if let error = error {
|
|
592
|
-
completion(nil, error)
|
|
626
|
+
completion(nil, nil, nil, error)
|
|
593
627
|
return
|
|
594
628
|
}
|
|
595
629
|
|
|
596
630
|
guard let image = image else {
|
|
597
|
-
completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to capture image"]))
|
|
631
|
+
completion(nil, nil, nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to capture image"]))
|
|
598
632
|
return
|
|
599
633
|
}
|
|
600
634
|
|
|
@@ -648,7 +682,7 @@ extension CameraController {
|
|
|
648
682
|
}
|
|
649
683
|
}
|
|
650
684
|
|
|
651
|
-
completion(finalImage, nil)
|
|
685
|
+
completion(finalImage, photoData, metadata, nil)
|
|
652
686
|
}
|
|
653
687
|
|
|
654
688
|
photoOutput.capturePhoto(with: settings, delegate: self)
|
|
@@ -740,8 +774,6 @@ extension CameraController {
|
|
|
740
774
|
|
|
741
775
|
}
|
|
742
776
|
|
|
743
|
-
|
|
744
|
-
|
|
745
777
|
// Create the cropped image
|
|
746
778
|
guard let cgImage = image.cgImage,
|
|
747
779
|
let croppedCGImage = cgImage.cropping(to: cropRect) else {
|
|
@@ -751,7 +783,6 @@ extension CameraController {
|
|
|
751
783
|
|
|
752
784
|
let result = UIImage(cgImage: croppedCGImage, scale: image.scale, orientation: image.imageOrientation)
|
|
753
785
|
|
|
754
|
-
|
|
755
786
|
return result
|
|
756
787
|
}
|
|
757
788
|
|
|
@@ -809,6 +840,7 @@ extension CameraController {
|
|
|
809
840
|
return supportedFlashModesAsStrings
|
|
810
841
|
|
|
811
842
|
}
|
|
843
|
+
|
|
812
844
|
func getHorizontalFov() throws -> Float {
|
|
813
845
|
var currentCamera: AVCaptureDevice?
|
|
814
846
|
switch currentCameraPosition {
|
|
@@ -835,6 +867,7 @@ extension CameraController {
|
|
|
835
867
|
|
|
836
868
|
return adjustedFov
|
|
837
869
|
}
|
|
870
|
+
|
|
838
871
|
func setFlashMode(flashMode: AVCaptureDevice.FlashMode) throws {
|
|
839
872
|
var currentCamera: AVCaptureDevice?
|
|
840
873
|
switch currentCameraPosition {
|
|
@@ -908,6 +941,7 @@ extension CameraController {
|
|
|
908
941
|
|
|
909
942
|
func getZoom() throws -> (min: Float, max: Float, current: Float) {
|
|
910
943
|
var currentCamera: AVCaptureDevice?
|
|
944
|
+
|
|
911
945
|
switch currentCameraPosition {
|
|
912
946
|
case .front:
|
|
913
947
|
currentCamera = self.frontCamera
|
|
@@ -962,9 +996,9 @@ extension CameraController {
|
|
|
962
996
|
self.zoomFactor = zoomLevel
|
|
963
997
|
|
|
964
998
|
// Trigger autofocus after zoom if requested
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
999
|
+
if autoFocus {
|
|
1000
|
+
self.triggerAutoFocus()
|
|
1001
|
+
}
|
|
968
1002
|
} catch {
|
|
969
1003
|
throw CameraControllerError.invalidOperation
|
|
970
1004
|
}
|
|
@@ -1465,22 +1499,23 @@ extension CameraController: UIGestureRecognizerDelegate {
|
|
|
1465
1499
|
extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
1466
1500
|
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
|
|
1467
1501
|
if let error = error {
|
|
1468
|
-
self.photoCaptureCompletionBlock?(nil, error)
|
|
1502
|
+
self.photoCaptureCompletionBlock?(nil, nil, nil, error)
|
|
1469
1503
|
return
|
|
1470
1504
|
}
|
|
1471
1505
|
|
|
1472
1506
|
// Get the photo data using the modern API
|
|
1473
1507
|
guard let imageData = photo.fileDataRepresentation() else {
|
|
1474
|
-
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
1508
|
+
self.photoCaptureCompletionBlock?(nil, nil, nil, CameraControllerError.unknown)
|
|
1475
1509
|
return
|
|
1476
1510
|
}
|
|
1477
1511
|
|
|
1478
1512
|
guard let image = UIImage(data: imageData) else {
|
|
1479
|
-
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
1513
|
+
self.photoCaptureCompletionBlock?(nil, nil, nil, CameraControllerError.unknown)
|
|
1480
1514
|
return
|
|
1481
1515
|
}
|
|
1482
1516
|
|
|
1483
|
-
|
|
1517
|
+
// Pass through original file data and metadata so callers can preserve EXIF
|
|
1518
|
+
self.photoCaptureCompletionBlock?(image.fixedOrientation(), imageData, photo.metadata, nil)
|
|
1484
1519
|
}
|
|
1485
1520
|
}
|
|
1486
1521
|
|