@capgo/camera-preview 7.14.0 → 7.14.5
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 +1 -1
- package/android/.project +11 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +69 -23
- package/dist/docs.json +0 -4
- package/dist/esm/definitions.d.ts +1 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.js +3 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +3 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +3 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +181 -141
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +32 -41
- package/package.json +1 -1
|
@@ -3,6 +3,39 @@ import UIKit
|
|
|
3
3
|
import CoreLocation
|
|
4
4
|
|
|
5
5
|
class CameraController: NSObject {
|
|
6
|
+
private func getVideoOrientation() -> AVCaptureVideoOrientation {
|
|
7
|
+
var orientation: AVCaptureVideoOrientation = .portrait
|
|
8
|
+
if Thread.isMainThread {
|
|
9
|
+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
10
|
+
switch windowScene.interfaceOrientation {
|
|
11
|
+
case .portrait: orientation = .portrait
|
|
12
|
+
case .landscapeLeft: orientation = .landscapeLeft
|
|
13
|
+
case .landscapeRight: orientation = .landscapeRight
|
|
14
|
+
case .portraitUpsideDown: orientation = .portraitUpsideDown
|
|
15
|
+
case .unknown: fallthrough
|
|
16
|
+
@unknown default: orientation = .portrait
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
21
|
+
DispatchQueue.main.async {
|
|
22
|
+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
23
|
+
switch windowScene.interfaceOrientation {
|
|
24
|
+
case .portrait: orientation = .portrait
|
|
25
|
+
case .landscapeLeft: orientation = .landscapeLeft
|
|
26
|
+
case .landscapeRight: orientation = .landscapeRight
|
|
27
|
+
case .portraitUpsideDown: orientation = .portraitUpsideDown
|
|
28
|
+
case .unknown: fallthrough
|
|
29
|
+
@unknown default: orientation = .portrait
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
semaphore.signal()
|
|
33
|
+
}
|
|
34
|
+
_ = semaphore.wait(timeout: .now() + 0.1) // Timeout after 100ms to prevent deadlocks
|
|
35
|
+
}
|
|
36
|
+
return orientation
|
|
37
|
+
}
|
|
38
|
+
|
|
6
39
|
var captureSession: AVCaptureSession?
|
|
7
40
|
var disableFocusIndicator: Bool = false
|
|
8
41
|
|
|
@@ -101,25 +134,9 @@ class CameraController: NSObject {
|
|
|
101
134
|
let components = aspectRatio.split(separator: ":").compactMap { Float(String($0)) }
|
|
102
135
|
guard components.count == 2 else { return nil }
|
|
103
136
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
print("[CameraPreview] parseAspectRatio - windowScene.interfaceOrientation: \(windowScene.interfaceOrientation)")
|
|
108
|
-
switch windowScene.interfaceOrientation {
|
|
109
|
-
case .portrait, .portraitUpsideDown:
|
|
110
|
-
isPortrait = true
|
|
111
|
-
case .landscapeLeft, .landscapeRight:
|
|
112
|
-
isPortrait = false
|
|
113
|
-
case .unknown:
|
|
114
|
-
// Fallback to device orientation
|
|
115
|
-
isPortrait = UIDevice.current.orientation.isPortrait
|
|
116
|
-
@unknown default:
|
|
117
|
-
isPortrait = UIDevice.current.orientation.isPortrait
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
// Fallback to device orientation
|
|
121
|
-
isPortrait = UIDevice.current.orientation.isPortrait
|
|
122
|
-
}
|
|
137
|
+
// Get orientation in a thread-safe way
|
|
138
|
+
let orientation = self.getVideoOrientation()
|
|
139
|
+
let isPortrait = (orientation == .portrait || orientation == .portraitUpsideDown)
|
|
123
140
|
|
|
124
141
|
let originalWidth = CGFloat(components[0])
|
|
125
142
|
let originalHeight = CGFloat(components[1])
|
|
@@ -180,7 +197,7 @@ extension CameraController {
|
|
|
180
197
|
// Log all found devices for debugging
|
|
181
198
|
|
|
182
199
|
for camera in cameras {
|
|
183
|
-
|
|
200
|
+
_ = camera.isVirtualDevice ? camera.constituentDevices.count : 1
|
|
184
201
|
|
|
185
202
|
}
|
|
186
203
|
|
|
@@ -257,20 +274,43 @@ extension CameraController {
|
|
|
257
274
|
let layer = AVCaptureVideoPreviewLayer()
|
|
258
275
|
// Configure orientation immediately
|
|
259
276
|
if let connection = layer.connection {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
277
|
+
// Ensure UI calls are made on the main thread
|
|
278
|
+
if Thread.isMainThread {
|
|
279
|
+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
280
|
+
switch windowScene.interfaceOrientation {
|
|
281
|
+
case .portrait:
|
|
282
|
+
connection.videoOrientation = .portrait
|
|
283
|
+
case .landscapeLeft:
|
|
284
|
+
connection.videoOrientation = .landscapeLeft
|
|
285
|
+
case .landscapeRight:
|
|
286
|
+
connection.videoOrientation = .landscapeRight
|
|
287
|
+
case .portraitUpsideDown:
|
|
288
|
+
connection.videoOrientation = .portraitUpsideDown
|
|
289
|
+
case .unknown:
|
|
290
|
+
fallthrough
|
|
291
|
+
@unknown default:
|
|
292
|
+
connection.videoOrientation = .portrait
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
// If not on main thread, use a sync call to get the orientation
|
|
297
|
+
DispatchQueue.main.sync {
|
|
298
|
+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
299
|
+
switch windowScene.interfaceOrientation {
|
|
300
|
+
case .portrait:
|
|
301
|
+
connection.videoOrientation = .portrait
|
|
302
|
+
case .landscapeLeft:
|
|
303
|
+
connection.videoOrientation = .landscapeLeft
|
|
304
|
+
case .landscapeRight:
|
|
305
|
+
connection.videoOrientation = .landscapeRight
|
|
306
|
+
case .portraitUpsideDown:
|
|
307
|
+
connection.videoOrientation = .portraitUpsideDown
|
|
308
|
+
case .unknown:
|
|
309
|
+
fallthrough
|
|
310
|
+
@unknown default:
|
|
311
|
+
connection.videoOrientation = .portrait
|
|
312
|
+
}
|
|
313
|
+
}
|
|
274
314
|
}
|
|
275
315
|
}
|
|
276
316
|
}
|
|
@@ -283,7 +323,7 @@ extension CameraController {
|
|
|
283
323
|
}
|
|
284
324
|
|
|
285
325
|
func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, aspectRatio: String? = nil, initialZoomLevel: Float?, disableFocusIndicator: Bool = false, completionHandler: @escaping (Error?) -> Void) {
|
|
286
|
-
print("[CameraPreview] 🎬 Starting prepare - position: \(cameraPosition), deviceId: \(deviceId ?? "nil"), disableAudio: \(disableAudio), cameraMode: \(cameraMode), aspectRatio: \(aspectRatio ?? "nil"), zoom: \(initialZoomLevel)")
|
|
326
|
+
print("[CameraPreview] 🎬 Starting prepare - position: \(cameraPosition), deviceId: \(deviceId ?? "nil"), disableAudio: \(disableAudio), cameraMode: \(cameraMode), aspectRatio: \(aspectRatio ?? "nil"), zoom: \(initialZoomLevel ?? 1)")
|
|
287
327
|
|
|
288
328
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
289
329
|
guard let self = self else {
|
|
@@ -321,18 +361,8 @@ extension CameraController {
|
|
|
321
361
|
|
|
322
362
|
// Add ALL outputs BEFORE starting session to avoid flashes from reconfiguration
|
|
323
363
|
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
327
|
-
switch windowScene.interfaceOrientation {
|
|
328
|
-
case .portrait: videoOrientation = .portrait
|
|
329
|
-
case .landscapeLeft: videoOrientation = .landscapeLeft
|
|
330
|
-
case .landscapeRight: videoOrientation = .landscapeRight
|
|
331
|
-
case .portraitUpsideDown: videoOrientation = .portraitUpsideDown
|
|
332
|
-
case .unknown: fallthrough
|
|
333
|
-
@unknown default: videoOrientation = .portrait
|
|
334
|
-
}
|
|
335
|
-
}
|
|
364
|
+
// Get orientation in a thread-safe way
|
|
365
|
+
let videoOrientation = self.getVideoOrientation()
|
|
336
366
|
|
|
337
367
|
// Add data output for preview
|
|
338
368
|
if let dataOutput = self.dataOutput, captureSession.canAddOutput(dataOutput) {
|
|
@@ -706,25 +736,8 @@ extension CameraController {
|
|
|
706
736
|
}
|
|
707
737
|
|
|
708
738
|
func updateVideoOrientation() {
|
|
709
|
-
// Get orientation
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
713
|
-
switch windowScene.interfaceOrientation {
|
|
714
|
-
case .portrait:
|
|
715
|
-
videoOrientation = .portrait
|
|
716
|
-
case .landscapeLeft:
|
|
717
|
-
videoOrientation = .landscapeLeft
|
|
718
|
-
case .landscapeRight:
|
|
719
|
-
videoOrientation = .landscapeRight
|
|
720
|
-
case .portraitUpsideDown:
|
|
721
|
-
videoOrientation = .portraitUpsideDown
|
|
722
|
-
case .unknown:
|
|
723
|
-
fallthrough
|
|
724
|
-
@unknown default:
|
|
725
|
-
videoOrientation = .portrait
|
|
726
|
-
}
|
|
727
|
-
}
|
|
739
|
+
// Get orientation in a thread-safe way
|
|
740
|
+
let videoOrientation = self.getVideoOrientation()
|
|
728
741
|
|
|
729
742
|
// Apply orientation asynchronously on main thread
|
|
730
743
|
let updateBlock = { [weak self] in
|
|
@@ -768,102 +781,131 @@ extension CameraController {
|
|
|
768
781
|
}
|
|
769
782
|
}
|
|
770
783
|
|
|
784
|
+
// Helper: pick the best preset the TARGET device supports for a given aspect ratio
|
|
785
|
+
private func bestPreset(for aspectRatio: String?, on device: AVCaptureDevice) -> AVCaptureSession.Preset {
|
|
786
|
+
// Preference order depends on aspect ratio
|
|
787
|
+
if aspectRatio == "16:9" {
|
|
788
|
+
// Prefer 4K → 1080p → 720p → high → photo → vga
|
|
789
|
+
if device.supportsSessionPreset(.hd4K3840x2160) { return .hd4K3840x2160 }
|
|
790
|
+
if device.supportsSessionPreset(.hd1920x1080) { return .hd1920x1080 }
|
|
791
|
+
if device.supportsSessionPreset(.hd1280x720) { return .hd1280x720 }
|
|
792
|
+
if device.supportsSessionPreset(.high) { return .high }
|
|
793
|
+
if device.supportsSessionPreset(.photo) { return .photo } // safe, though 4:3
|
|
794
|
+
return .vga640x480
|
|
795
|
+
} else {
|
|
796
|
+
// 4:3 or unknown: prefer photo → high → 1080p → 720p → vga
|
|
797
|
+
if device.supportsSessionPreset(.photo) { return .photo }
|
|
798
|
+
if device.supportsSessionPreset(.high) { return .high }
|
|
799
|
+
if device.supportsSessionPreset(.hd1920x1080){ return .hd1920x1080 }
|
|
800
|
+
if device.supportsSessionPreset(.hd1280x720) { return .hd1280x720 }
|
|
801
|
+
return .vga640x480
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
771
805
|
func switchCameras() throws {
|
|
772
806
|
guard let currentCameraPosition = currentCameraPosition,
|
|
773
807
|
let captureSession = self.captureSession else {
|
|
774
808
|
throw CameraControllerError.captureSessionIsMissing
|
|
775
809
|
}
|
|
776
810
|
|
|
777
|
-
//
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
811
|
+
// Determine the device we’re switching TO
|
|
812
|
+
let targetDevice: AVCaptureDevice
|
|
813
|
+
switch currentCameraPosition {
|
|
814
|
+
case .front:
|
|
815
|
+
guard let rear = rearCamera else { throw CameraControllerError.invalidOperation }
|
|
816
|
+
targetDevice = rear
|
|
817
|
+
case .rear:
|
|
818
|
+
guard let front = frontCamera else { throw CameraControllerError.invalidOperation }
|
|
819
|
+
targetDevice = front
|
|
781
820
|
}
|
|
782
821
|
|
|
783
|
-
//
|
|
784
|
-
let
|
|
785
|
-
if wasRunning {
|
|
786
|
-
captureSession.stopRunning()
|
|
787
|
-
}
|
|
822
|
+
// Compute the desired preset for the TARGET device up front
|
|
823
|
+
let desiredPreset = bestPreset(for: self.requestedAspectRatio, on: targetDevice)
|
|
788
824
|
|
|
789
|
-
//
|
|
825
|
+
// Keep the preview layer visually stable during the swap
|
|
826
|
+
let savedPreviewFrame = self.previewLayer?.frame
|
|
827
|
+
CATransaction.begin()
|
|
828
|
+
CATransaction.setDisableActions(true)
|
|
829
|
+
self.previewLayer?.connection?.isEnabled = false // reduce visible glitching
|
|
830
|
+
|
|
831
|
+
// No need to stopRunning; Apple recommends reconfiguring within begin/commit
|
|
790
832
|
captureSession.beginConfiguration()
|
|
791
833
|
defer {
|
|
792
834
|
captureSession.commitConfiguration()
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
835
|
+
self.previewLayer?.connection?.isEnabled = true
|
|
836
|
+
// Restore frame (it shouldn't change, but this ensures zero animation)
|
|
837
|
+
if let f = savedPreviewFrame { self.previewLayer?.frame = f }
|
|
838
|
+
CATransaction.commit()
|
|
839
|
+
DispatchQueue.main.async { [weak self] in
|
|
840
|
+
self?.setDefaultZoomAfterFlip() // normalize zoom (UI 1.0x)
|
|
798
841
|
}
|
|
799
842
|
}
|
|
800
843
|
|
|
801
|
-
//
|
|
802
|
-
let
|
|
844
|
+
// Preserve audio input (if any)
|
|
845
|
+
let existingAudioInput = captureSession.inputs.first {
|
|
846
|
+
($0 as? AVCaptureDeviceInput)?.device.hasMediaType(.audio) ?? false
|
|
847
|
+
}
|
|
803
848
|
|
|
804
|
-
// Remove
|
|
805
|
-
captureSession.inputs
|
|
849
|
+
// Remove ONLY video inputs
|
|
850
|
+
for input in captureSession.inputs {
|
|
806
851
|
if (input as? AVCaptureDeviceInput)?.device.hasMediaType(.video) ?? false {
|
|
807
852
|
captureSession.removeInput(input)
|
|
808
853
|
}
|
|
809
854
|
}
|
|
810
855
|
|
|
811
|
-
//
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if let newInput = try? AVCaptureDeviceInput(device: rearCamera),
|
|
826
|
-
captureSession.canAddInput(newInput) {
|
|
827
|
-
captureSession.addInput(newInput)
|
|
828
|
-
rearCameraInput = newInput
|
|
829
|
-
self.currentCameraPosition = .rear
|
|
830
|
-
} else {
|
|
831
|
-
throw CameraControllerError.invalidOperation
|
|
832
|
-
}
|
|
833
|
-
case .rear:
|
|
834
|
-
guard let frontCamera = frontCamera else {
|
|
835
|
-
throw CameraControllerError.invalidOperation
|
|
856
|
+
// Only downgrade to a safe preset if the TARGET cannot support the CURRENT one
|
|
857
|
+
let currentPreset = captureSession.sessionPreset
|
|
858
|
+
let targetSupportsCurrent = targetDevice.supportsSessionPreset(currentPreset)
|
|
859
|
+
if !targetSupportsCurrent {
|
|
860
|
+
// Choose the first preset supported by BOTH the target device and the session
|
|
861
|
+
let fallbacks: [AVCaptureSession.Preset] =
|
|
862
|
+
(self.requestedAspectRatio == "16:9")
|
|
863
|
+
? [.hd4K3840x2160, .hd1920x1080, .hd1280x720, .high, .photo, .vga640x480]
|
|
864
|
+
: [.photo, .high, .hd1920x1080, .hd1280x720, .vga640x480]
|
|
865
|
+
for p in fallbacks {
|
|
866
|
+
if targetDevice.supportsSessionPreset(p), captureSession.canSetSessionPreset(p) {
|
|
867
|
+
captureSession.sessionPreset = p
|
|
868
|
+
break
|
|
869
|
+
}
|
|
836
870
|
}
|
|
871
|
+
}
|
|
837
872
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
873
|
+
// Add the new video input
|
|
874
|
+
let newInput = try AVCaptureDeviceInput(device: targetDevice)
|
|
875
|
+
guard captureSession.canAddInput(newInput) else {
|
|
876
|
+
throw CameraControllerError.invalidOperation
|
|
877
|
+
}
|
|
878
|
+
captureSession.addInput(newInput)
|
|
844
879
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
880
|
+
// Update pointers / focus defaults
|
|
881
|
+
if targetDevice.position == .front {
|
|
882
|
+
self.frontCameraInput = newInput
|
|
883
|
+
self.currentCameraPosition = .front
|
|
884
|
+
} else {
|
|
885
|
+
self.rearCameraInput = newInput
|
|
886
|
+
self.currentCameraPosition = .rear
|
|
887
|
+
}
|
|
888
|
+
// (Lightweight focus config; non-fatal on failure)
|
|
889
|
+
try? targetDevice.lockForConfiguration()
|
|
890
|
+
if targetDevice.isFocusModeSupported(.continuousAutoFocus) {
|
|
891
|
+
targetDevice.focusMode = .continuousAutoFocus
|
|
853
892
|
}
|
|
893
|
+
targetDevice.unlockForConfiguration()
|
|
854
894
|
|
|
855
|
-
//
|
|
856
|
-
if let audioInput =
|
|
895
|
+
// Restore audio input if it existed
|
|
896
|
+
if let audioInput = existingAudioInput, captureSession.canAddInput(audioInput) {
|
|
857
897
|
captureSession.addInput(audioInput)
|
|
858
898
|
}
|
|
859
899
|
|
|
860
|
-
//
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
self?.setDefaultZoomAfterFlip()
|
|
900
|
+
// Now apply the BEST preset for the target device & requested AR
|
|
901
|
+
if captureSession.sessionPreset != desiredPreset,
|
|
902
|
+
targetDevice.supportsSessionPreset(desiredPreset),
|
|
903
|
+
captureSession.canSetSessionPreset(desiredPreset) {
|
|
904
|
+
captureSession.sessionPreset = desiredPreset
|
|
866
905
|
}
|
|
906
|
+
|
|
907
|
+
// Keep orientation correct
|
|
908
|
+
self.updateVideoOrientation()
|
|
867
909
|
}
|
|
868
910
|
|
|
869
911
|
func captureImage(width: Int?, height: Int?, quality: Float, gpsLocation: CLLocation?, completion: @escaping (UIImage?, Data?, [AnyHashable: Any]?, Error?) -> Void) {
|
|
@@ -874,11 +916,9 @@ extension CameraController {
|
|
|
874
916
|
|
|
875
917
|
let settings = AVCapturePhotoSettings()
|
|
876
918
|
// Configure photo capture settings optimized for speed
|
|
877
|
-
if
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
settings.isHighResolutionPhotoEnabled = shouldUseHighRes
|
|
881
|
-
}
|
|
919
|
+
// Only use high res if explicitly requesting large dimensions
|
|
920
|
+
let shouldUseHighRes = width.map { $0 > 1920 } ?? false || height.map { $0 > 1920 } ?? false
|
|
921
|
+
settings.isHighResolutionPhotoEnabled = shouldUseHighRes
|
|
882
922
|
if #available(iOS 15.0, *) {
|
|
883
923
|
// Prioritize speed over quality
|
|
884
924
|
settings.photoQualityPrioritization = .speed
|
|
@@ -2207,11 +2247,11 @@ extension UIImage {
|
|
|
2207
2247
|
// Flip image one more time if needed to, this is to prevent flipped image
|
|
2208
2248
|
switch imageOrientation {
|
|
2209
2249
|
case .upMirrored, .downMirrored:
|
|
2210
|
-
transform.translatedBy(x: size.width, y: 0)
|
|
2211
|
-
transform.scaledBy(x: -1, y: 1)
|
|
2250
|
+
_ = transform.translatedBy(x: size.width, y: 0)
|
|
2251
|
+
_ = transform.scaledBy(x: -1, y: 1)
|
|
2212
2252
|
case .leftMirrored, .rightMirrored:
|
|
2213
|
-
transform.translatedBy(x: size.height, y: 0)
|
|
2214
|
-
transform.scaledBy(x: -1, y: 1)
|
|
2253
|
+
_ = transform.translatedBy(x: size.height, y: 0)
|
|
2254
|
+
_ = transform.scaledBy(x: -1, y: 1)
|
|
2215
2255
|
case .up, .down, .left, .right:
|
|
2216
2256
|
break
|
|
2217
2257
|
@unknown default:
|
|
@@ -8,26 +8,22 @@ import MobileCoreServices
|
|
|
8
8
|
|
|
9
9
|
extension UIWindow {
|
|
10
10
|
static var isLandscape: Bool {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return UIApplication.shared.statusBarOrientation.isLandscape
|
|
19
|
-
}
|
|
11
|
+
// iOS 14+ only: derive from the active window scene's interface orientation
|
|
12
|
+
let scene = UIApplication.shared
|
|
13
|
+
.connectedScenes
|
|
14
|
+
.compactMap { $0 as? UIWindowScene }
|
|
15
|
+
.first { $0.activationState == .foregroundActive }
|
|
16
|
+
|
|
17
|
+
return scene?.interfaceOrientation.isLandscape ?? false
|
|
20
18
|
}
|
|
21
19
|
static var isPortrait: Bool {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return UIApplication.shared.statusBarOrientation.isPortrait
|
|
30
|
-
}
|
|
20
|
+
// iOS 14+ only: derive from the active window scene's interface orientation
|
|
21
|
+
let scene = UIApplication.shared
|
|
22
|
+
.connectedScenes
|
|
23
|
+
.compactMap { $0 as? UIWindowScene }
|
|
24
|
+
.first { $0.activationState == .foregroundActive }
|
|
25
|
+
|
|
26
|
+
return scene?.interfaceOrientation.isPortrait ?? false
|
|
31
27
|
}
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -142,7 +138,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
142
138
|
guard let webView = self.webView else { return }
|
|
143
139
|
|
|
144
140
|
DispatchQueue.main.async {
|
|
145
|
-
|
|
141
|
+
_ = CFAbsoluteTimeGetCurrent()
|
|
146
142
|
|
|
147
143
|
// Define a recursive function to traverse the view hierarchy
|
|
148
144
|
func makeSubviewsTransparent(_ view: UIView) {
|
|
@@ -225,15 +221,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
225
221
|
let displayMultiplier = self.cameraController.getDisplayZoomMultiplier()
|
|
226
222
|
var teleStep: Float
|
|
227
223
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
teleStep = maxSwitch * displayMultiplier
|
|
234
|
-
} else {
|
|
235
|
-
teleStep = Float(device.maxAvailableVideoZoomFactor) * displayMultiplier
|
|
236
|
-
}
|
|
224
|
+
let switchFactors = device.virtualDeviceSwitchOverVideoZoomFactors
|
|
225
|
+
if !switchFactors.isEmpty {
|
|
226
|
+
// Choose the highest switch-over factor (typically the wide->tele threshold)
|
|
227
|
+
let maxSwitch = switchFactors.map { $0.floatValue }.max() ?? Float(device.maxAvailableVideoZoomFactor)
|
|
228
|
+
teleStep = maxSwitch * displayMultiplier
|
|
237
229
|
} else {
|
|
238
230
|
teleStep = Float(device.maxAvailableVideoZoomFactor) * displayMultiplier
|
|
239
231
|
}
|
|
@@ -268,15 +260,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
268
260
|
}
|
|
269
261
|
|
|
270
262
|
@objc func rotated() {
|
|
271
|
-
guard let previewView = self.previewView
|
|
272
|
-
let posX = self.posX,
|
|
273
|
-
let posY = self.posY,
|
|
274
|
-
let width = self.width,
|
|
275
|
-
let heightValue = self.height else {
|
|
263
|
+
guard let previewView = self.previewView else {
|
|
276
264
|
return
|
|
277
265
|
}
|
|
278
|
-
let paddingBottom = self.paddingBottom ?? 0
|
|
279
|
-
let height = heightValue - paddingBottom
|
|
280
266
|
|
|
281
267
|
// Handle auto-centering during rotation
|
|
282
268
|
// Always use the factorized method for consistent positioning
|
|
@@ -786,7 +772,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
786
772
|
}
|
|
787
773
|
|
|
788
774
|
@objc func capture(_ call: CAPPluginCall) {
|
|
789
|
-
print("[CameraPreview] capture called with options: \(call.options)")
|
|
775
|
+
print("[CameraPreview] capture called with options: \(call.options ?? [:])")
|
|
790
776
|
let withExifLocation = call.getBool("withExifLocation", false)
|
|
791
777
|
print("[CameraPreview] capture called, withExifLocation: \(withExifLocation)")
|
|
792
778
|
|
|
@@ -857,7 +843,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
857
843
|
|
|
858
844
|
private func performCapture(call: CAPPluginCall) {
|
|
859
845
|
print("[CameraPreview] performCapture called")
|
|
860
|
-
print("[CameraPreview] Call parameters: \(call.options)")
|
|
846
|
+
print("[CameraPreview] Call parameters: \(call.options ?? [:])")
|
|
861
847
|
let quality = call.getFloat("quality", 85)
|
|
862
848
|
let saveToGallery = call.getBool("saveToGallery", false)
|
|
863
849
|
let withExifLocation = call.getBool("withExifLocation", false)
|
|
@@ -1016,8 +1002,13 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1016
1002
|
notchInset = safeAreaInsets.top
|
|
1017
1003
|
}
|
|
1018
1004
|
} else {
|
|
1019
|
-
// Fallback
|
|
1020
|
-
|
|
1005
|
+
// Fallback for iOS 14+: try to derive from any available window's safe area
|
|
1006
|
+
let anyWindow = UIApplication.shared
|
|
1007
|
+
.connectedScenes
|
|
1008
|
+
.compactMap { $0 as? UIWindowScene }
|
|
1009
|
+
.flatMap { $0.windows }
|
|
1010
|
+
.first
|
|
1011
|
+
notchInset = anyWindow?.safeAreaInsets.top ?? 0
|
|
1021
1012
|
}
|
|
1022
1013
|
|
|
1023
1014
|
let result: [String: Any] = [
|
|
@@ -1681,7 +1672,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1681
1672
|
}
|
|
1682
1673
|
|
|
1683
1674
|
private func updateCameraFrame() {
|
|
1684
|
-
guard let
|
|
1675
|
+
guard let posX = self.posX, let posY = self.posY else {
|
|
1685
1676
|
return
|
|
1686
1677
|
}
|
|
1687
1678
|
|
|
@@ -1940,7 +1931,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1940
1931
|
private var lastOrientation: String?
|
|
1941
1932
|
|
|
1942
1933
|
@objc private func handleOrientationChange() {
|
|
1943
|
-
|
|
1934
|
+
let currentOrientation = self.currentOrientationString()
|
|
1944
1935
|
if currentOrientation == "portrait-upside-down" || currentOrientation == lastOrientation {
|
|
1945
1936
|
return
|
|
1946
1937
|
}
|