capacitor-community-multilens-camerapreview 7.0.1 → 7.1.1
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/ios/Plugin/CameraController.swift +130 -52
- package/ios/Plugin/Plugin.swift +33 -4
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Plugin
|
|
4
4
|
//
|
|
5
5
|
// Created by Ariel Hernandez Musa on 7/14/19.
|
|
6
|
-
//
|
|
6
|
+
// Modified by Pete Kraguljac on 11/5/2025
|
|
7
7
|
//
|
|
8
8
|
|
|
9
9
|
import AVFoundation
|
|
@@ -192,10 +192,18 @@ extension CameraController {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
func updateVideoOrientation() {
|
|
195
|
-
assert(Thread.isMainThread)
|
|
195
|
+
assert(Thread.isMainThread)
|
|
196
|
+
|
|
197
|
+
// Prefer the interfaceOrientation from the active window scene (iOS 13+)
|
|
198
|
+
let interfaceOrientation: UIInterfaceOrientation = (
|
|
199
|
+
UIApplication.shared.connectedScenes
|
|
200
|
+
.compactMap { $0 as? UIWindowScene }
|
|
201
|
+
.first(where: { $0.activationState == .foregroundActive })?
|
|
202
|
+
.interfaceOrientation
|
|
203
|
+
) ?? UIApplication.shared.windows.first?.windowScene?.interfaceOrientation ?? .portrait
|
|
196
204
|
|
|
197
205
|
let videoOrientation: AVCaptureVideoOrientation
|
|
198
|
-
switch
|
|
206
|
+
switch interfaceOrientation {
|
|
199
207
|
case .portrait:
|
|
200
208
|
videoOrientation = .portrait
|
|
201
209
|
case .landscapeLeft:
|
|
@@ -204,9 +212,7 @@ extension CameraController {
|
|
|
204
212
|
videoOrientation = .landscapeRight
|
|
205
213
|
case .portraitUpsideDown:
|
|
206
214
|
videoOrientation = .portraitUpsideDown
|
|
207
|
-
|
|
208
|
-
fallthrough
|
|
209
|
-
@unknown default:
|
|
215
|
+
default:
|
|
210
216
|
videoOrientation = .portrait
|
|
211
217
|
}
|
|
212
218
|
|
|
@@ -216,14 +222,22 @@ extension CameraController {
|
|
|
216
222
|
}
|
|
217
223
|
|
|
218
224
|
func switchCameras() throws {
|
|
219
|
-
guard let currentCameraPosition = currentCameraPosition, let captureSession = self.captureSession, captureSession.isRunning else {
|
|
225
|
+
guard let currentCameraPosition = currentCameraPosition, let captureSession = self.captureSession, captureSession.isRunning else {
|
|
226
|
+
throw CameraControllerError.captureSessionIsMissing
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Stop the capture session
|
|
230
|
+
captureSession.stopRunning()
|
|
220
231
|
|
|
221
232
|
captureSession.beginConfiguration()
|
|
222
233
|
|
|
223
234
|
func switchToFrontCamera() throws {
|
|
224
235
|
|
|
225
236
|
guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput),
|
|
226
|
-
let frontCamera = self.frontCamera else {
|
|
237
|
+
let frontCamera = self.frontCamera else {
|
|
238
|
+
captureSession.startRunning() // Restart before throwing
|
|
239
|
+
throw CameraControllerError.invalidOperation
|
|
240
|
+
}
|
|
227
241
|
|
|
228
242
|
self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
|
|
229
243
|
|
|
@@ -234,6 +248,7 @@ extension CameraController {
|
|
|
234
248
|
|
|
235
249
|
self.currentCameraPosition = .front
|
|
236
250
|
} else {
|
|
251
|
+
captureSession.startRunning() // Restart before throwing
|
|
237
252
|
throw CameraControllerError.invalidOperation
|
|
238
253
|
}
|
|
239
254
|
}
|
|
@@ -241,7 +256,10 @@ extension CameraController {
|
|
|
241
256
|
func switchToRearCamera() throws {
|
|
242
257
|
|
|
243
258
|
guard let frontCameraInput = self.frontCameraInput, captureSession.inputs.contains(frontCameraInput),
|
|
244
|
-
let rearCamera = self.rearCamera else {
|
|
259
|
+
let rearCamera = self.rearCamera else {
|
|
260
|
+
captureSession.startRunning() // Restart before throwing
|
|
261
|
+
throw CameraControllerError.invalidOperation
|
|
262
|
+
}
|
|
245
263
|
|
|
246
264
|
self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
|
|
247
265
|
|
|
@@ -251,7 +269,10 @@ extension CameraController {
|
|
|
251
269
|
captureSession.addInput(self.rearCameraInput!)
|
|
252
270
|
|
|
253
271
|
self.currentCameraPosition = .rear
|
|
254
|
-
} else {
|
|
272
|
+
} else {
|
|
273
|
+
captureSession.startRunning() // Restart before throwing
|
|
274
|
+
throw CameraControllerError.invalidOperation
|
|
275
|
+
}
|
|
255
276
|
}
|
|
256
277
|
|
|
257
278
|
switch currentCameraPosition {
|
|
@@ -263,41 +284,73 @@ extension CameraController {
|
|
|
263
284
|
}
|
|
264
285
|
|
|
265
286
|
captureSession.commitConfiguration()
|
|
287
|
+
|
|
288
|
+
// Restart the capture session
|
|
289
|
+
captureSession.startRunning()
|
|
266
290
|
}
|
|
267
291
|
func setZoom(lens: String) throws {
|
|
268
292
|
print(lens)
|
|
269
|
-
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
293
|
+
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
294
|
+
throw CameraControllerError.captureSessionIsMissing
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Stop the capture session
|
|
298
|
+
captureSession.stopRunning()
|
|
270
299
|
|
|
271
300
|
captureSession.beginConfiguration()
|
|
272
301
|
|
|
273
|
-
guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput) else {
|
|
302
|
+
guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput) else {
|
|
303
|
+
captureSession.startRunning() // Restart before throwing
|
|
304
|
+
throw CameraControllerError.invalidOperation
|
|
305
|
+
}
|
|
274
306
|
|
|
275
|
-
var session = AVCaptureDevice.DiscoverySession(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
307
|
+
var session = AVCaptureDevice.DiscoverySession(
|
|
308
|
+
deviceTypes: [.builtInWideAngleCamera, .builtInUltraWideCamera, .builtInTelephotoCamera],
|
|
309
|
+
mediaType: AVMediaType.video,
|
|
310
|
+
position: .unspecified
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if lens == "ultra" {
|
|
314
|
+
session = AVCaptureDevice.DiscoverySession(
|
|
315
|
+
deviceTypes: [.builtInWideAngleCamera, .builtInUltraWideCamera],
|
|
316
|
+
mediaType: AVMediaType.video,
|
|
317
|
+
position: .unspecified
|
|
318
|
+
)
|
|
319
|
+
} else if lens == "wide" {
|
|
320
|
+
session = AVCaptureDevice.DiscoverySession(
|
|
321
|
+
deviceTypes: [.builtInWideAngleCamera],
|
|
322
|
+
mediaType: AVMediaType.video,
|
|
323
|
+
position: .unspecified
|
|
324
|
+
)
|
|
325
|
+
} else if lens == "tele" {
|
|
326
|
+
session = AVCaptureDevice.DiscoverySession(
|
|
327
|
+
deviceTypes: [.builtInWideAngleCamera, .builtInTelephotoCamera],
|
|
328
|
+
mediaType: AVMediaType.video,
|
|
329
|
+
position: .unspecified
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
let cameras = session.devices.compactMap { $0 }
|
|
334
|
+
guard !cameras.isEmpty else {
|
|
335
|
+
captureSession.startRunning() // Restart before throwing
|
|
336
|
+
throw CameraControllerError.noCamerasAvailable
|
|
337
|
+
}
|
|
285
338
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
339
|
+
for camera in cameras {
|
|
340
|
+
if camera.position == .front {
|
|
341
|
+
self.frontCamera = camera
|
|
342
|
+
}
|
|
290
343
|
|
|
291
|
-
|
|
292
|
-
|
|
344
|
+
if camera.position == .back {
|
|
345
|
+
self.rearCamera = camera
|
|
293
346
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
camera.unlockForConfiguration()
|
|
347
|
+
try camera.lockForConfiguration()
|
|
348
|
+
if camera.isFocusModeSupported(.continuousAutoFocus) {
|
|
349
|
+
camera.focusMode = .continuousAutoFocus
|
|
299
350
|
}
|
|
351
|
+
camera.unlockForConfiguration()
|
|
300
352
|
}
|
|
353
|
+
}
|
|
301
354
|
|
|
302
355
|
if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] {
|
|
303
356
|
for input in inputs {
|
|
@@ -308,25 +361,29 @@ extension CameraController {
|
|
|
308
361
|
|
|
309
362
|
self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera!)
|
|
310
363
|
|
|
311
|
-
//captureSession.removeInput(frontCameraInput)
|
|
312
|
-
|
|
313
364
|
if captureSession.canAddInput(self.rearCameraInput!) {
|
|
314
365
|
captureSession.addInput(self.rearCameraInput!)
|
|
315
366
|
self.currentCameraPosition = .rear
|
|
316
367
|
}
|
|
317
368
|
|
|
318
369
|
do {
|
|
319
|
-
guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else {
|
|
370
|
+
guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else {
|
|
371
|
+
captureSession.startRunning() // Restart before returning
|
|
372
|
+
return
|
|
373
|
+
}
|
|
320
374
|
|
|
321
375
|
try device.lockForConfiguration()
|
|
322
376
|
defer { device.unlockForConfiguration() }
|
|
323
377
|
|
|
324
|
-
device.videoZoomFactor = 1.0
|
|
378
|
+
device.videoZoomFactor = 1.0
|
|
325
379
|
zoomFactor = device.videoZoomFactor
|
|
326
380
|
|
|
327
381
|
} catch {
|
|
328
382
|
debugPrint(error)
|
|
329
383
|
}
|
|
384
|
+
|
|
385
|
+
// Restart the capture session
|
|
386
|
+
captureSession.startRunning()
|
|
330
387
|
}
|
|
331
388
|
|
|
332
389
|
func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
|
|
@@ -395,19 +452,22 @@ extension CameraController {
|
|
|
395
452
|
|
|
396
453
|
}
|
|
397
454
|
func getSupportedCameras() throws -> [String] {
|
|
398
|
-
var supportedCameras: [String] = []
|
|
399
|
-
|
|
455
|
+
var supportedCameras: [String] = []
|
|
456
|
+
|
|
457
|
+
if AVCaptureDevice.default(.builtInUltraWideCamera, for: .video, position: .back) != nil {
|
|
400
458
|
supportedCameras.append("ultra")
|
|
401
459
|
}
|
|
402
|
-
if
|
|
460
|
+
if AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) != nil {
|
|
403
461
|
supportedCameras.append("wide")
|
|
404
462
|
}
|
|
405
|
-
if
|
|
463
|
+
if AVCaptureDevice.default(.builtInTelephotoCamera, for: .video, position: .back) != nil {
|
|
406
464
|
supportedCameras.append("tele")
|
|
407
465
|
}
|
|
408
|
-
|
|
466
|
+
|
|
467
|
+
return supportedCameras
|
|
409
468
|
}
|
|
410
469
|
|
|
470
|
+
|
|
411
471
|
func setFlashMode(flashMode: AVCaptureDevice.FlashMode) throws {
|
|
412
472
|
var currentCamera: AVCaptureDevice?
|
|
413
473
|
switch currentCameraPosition {
|
|
@@ -568,17 +628,32 @@ extension CameraController: UIGestureRecognizerDelegate {
|
|
|
568
628
|
}
|
|
569
629
|
|
|
570
630
|
extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if let error = error {
|
|
574
|
-
|
|
631
|
+
|
|
632
|
+
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
|
|
633
|
+
if let error = error {
|
|
634
|
+
self.photoCaptureCompletionBlock?(nil, error)
|
|
635
|
+
return
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Prefer modern data extraction
|
|
639
|
+
if let data = photo.fileDataRepresentation(),
|
|
640
|
+
let image = UIImage(data: data) {
|
|
575
641
|
self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
|
|
576
|
-
|
|
577
|
-
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
642
|
+
return
|
|
578
643
|
}
|
|
644
|
+
|
|
645
|
+
// Fallback to cgImageRepresentation if available
|
|
646
|
+
if let cgImage = photo.cgImageRepresentation() {
|
|
647
|
+
let image = UIImage(cgImage: cgImage)
|
|
648
|
+
self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
|
|
649
|
+
return
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
579
653
|
}
|
|
580
654
|
}
|
|
581
655
|
|
|
656
|
+
|
|
582
657
|
extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
583
658
|
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
|
|
584
659
|
guard let completion = sampleBufferCaptureCompletionBlock else { return }
|
|
@@ -697,12 +772,14 @@ extension UIImage {
|
|
|
697
772
|
// Flip image one more time if needed to, this is to prevent flipped image
|
|
698
773
|
switch imageOrientation {
|
|
699
774
|
case .upMirrored, .downMirrored:
|
|
700
|
-
transform.translatedBy(x: size.width, y: 0)
|
|
701
|
-
transform.scaledBy(x: -1, y: 1)
|
|
775
|
+
transform = transform.translatedBy(x: size.width, y: 0)
|
|
776
|
+
transform = transform.scaledBy(x: -1, y: 1)
|
|
777
|
+
transform = transform.scaledBy(x: -1, y: 1)
|
|
702
778
|
break
|
|
703
779
|
case .leftMirrored, .rightMirrored:
|
|
704
|
-
transform.translatedBy(x: size.height, y: 0)
|
|
705
|
-
transform.scaledBy(x: -1, y: 1)
|
|
780
|
+
transform = transform.translatedBy(x: size.height, y: 0)
|
|
781
|
+
transform = transform.scaledBy(x: -1, y: 1)
|
|
782
|
+
transform = transform.scaledBy(x: -1, y: 1)
|
|
706
783
|
case .up, .down, .left, .right:
|
|
707
784
|
break
|
|
708
785
|
}
|
|
@@ -730,3 +807,4 @@ extension CameraController: AVCaptureFileOutputRecordingDelegate {
|
|
|
730
807
|
}*/
|
|
731
808
|
}
|
|
732
809
|
}
|
|
810
|
+
|
package/ios/Plugin/Plugin.swift
CHANGED
|
@@ -24,15 +24,40 @@ public class CameraPreviewMultiLens: CAPPlugin {
|
|
|
24
24
|
var disableAudio: Bool = false
|
|
25
25
|
var zoomFactor = String()
|
|
26
26
|
|
|
27
|
+
private func currentInterfaceOrientation() -> UIInterfaceOrientation? {
|
|
28
|
+
if let window = self.webView?.window ?? self.bridge?.viewController?.view.window {
|
|
29
|
+
return window.windowScene?.interfaceOrientation
|
|
30
|
+
}
|
|
31
|
+
// Fallback to device orientation mapping
|
|
32
|
+
let deviceOrientation = UIDevice.current.orientation
|
|
33
|
+
switch deviceOrientation {
|
|
34
|
+
case .landscapeLeft: return .landscapeRight // device vs interface are mirrored
|
|
35
|
+
case .landscapeRight: return .landscapeLeft
|
|
36
|
+
case .portrait: return .portrait
|
|
37
|
+
case .portraitUpsideDown: return .portraitUpsideDown
|
|
38
|
+
default: return nil
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
@objc func rotated() {
|
|
28
43
|
let height = self.paddingBottom != nil ? self.height! - self.paddingBottom!: self.height!
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
// Determine orientation without using deprecated statusBarOrientation and without relying on missing viewController
|
|
46
|
+
let orientation = self.currentInterfaceOrientation()
|
|
47
|
+
let isLandscape: Bool
|
|
48
|
+
if let orientation = orientation {
|
|
49
|
+
isLandscape = orientation.isLandscape
|
|
50
|
+
} else {
|
|
51
|
+
// Default to portrait when unknown
|
|
52
|
+
isLandscape = false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if isLandscape {
|
|
31
56
|
self.previewView.frame = CGRect(x: self.y!, y: self.x!, width: max(height, self.width!), height: min(height, self.width!))
|
|
32
57
|
self.cameraController.previewLayer?.frame = self.previewView.frame
|
|
33
58
|
}
|
|
34
59
|
|
|
35
|
-
if
|
|
60
|
+
if !isLandscape {
|
|
36
61
|
if self.previewView != nil && self.x != nil && self.y != nil && self.width != nil && self.height != nil {
|
|
37
62
|
self.previewView.frame = CGRect(x: self.x!, y: self.y!, width: min(height, self.width!), height: max(height, self.width!))
|
|
38
63
|
}
|
|
@@ -102,6 +127,10 @@ public class CameraPreviewMultiLens: CAPPlugin {
|
|
|
102
127
|
|
|
103
128
|
if self.rotateWhenOrientationChanged == true {
|
|
104
129
|
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreviewMultiLens.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
|
|
130
|
+
if let scene = self.webView?.window?.windowScene ?? self.bridge?.viewController?.view.window?.windowScene {
|
|
131
|
+
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreviewMultiLens.rotated), name: UIWindowScene.willEnterForegroundNotification, object: scene)
|
|
132
|
+
NotificationCenter.default.addObserver(self, selector: #selector(CameraPreviewMultiLens.rotated), name: UIWindowScene.didActivateNotification, object: scene)
|
|
133
|
+
}
|
|
105
134
|
}
|
|
106
135
|
|
|
107
136
|
call.resolve()
|
|
@@ -197,7 +226,7 @@ public class CameraPreviewMultiLens: CAPPlugin {
|
|
|
197
226
|
}
|
|
198
227
|
|
|
199
228
|
let imageData: Data?
|
|
200
|
-
if self.
|
|
229
|
+
if self.cameraController.currentCameraPosition == .front {
|
|
201
230
|
let flippedImage = image.withHorizontallyFlippedOrientation()
|
|
202
231
|
imageData = flippedImage.jpegData(compressionQuality: CGFloat(quality!/100))
|
|
203
232
|
} else {
|
|
@@ -278,7 +307,7 @@ public class CameraPreviewMultiLens: CAPPlugin {
|
|
|
278
307
|
@objc func startRecordVideo(_ call: CAPPluginCall) {
|
|
279
308
|
DispatchQueue.main.async {
|
|
280
309
|
|
|
281
|
-
let
|
|
310
|
+
let _ = call.getInt("quality", 85)
|
|
282
311
|
|
|
283
312
|
self.cameraController.captureVideo { (image, error) in
|
|
284
313
|
|
package/package.json
CHANGED