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.
@@ -3,7 +3,7 @@
3
3
  // Plugin
4
4
  //
5
5
  // Created by Ariel Hernandez Musa on 7/14/19.
6
- // Copyright © 2019 Max Lynch. All rights reserved.
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) // UIApplication.statusBarOrientation requires the main thread.
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 UIApplication.shared.statusBarOrientation {
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
- case .unknown:
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 { throw CameraControllerError.captureSessionIsMissing }
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 { throw CameraControllerError.invalidOperation }
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 { throw CameraControllerError.invalidOperation }
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 { throw CameraControllerError.invalidOperation }
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 { throw CameraControllerError.captureSessionIsMissing }
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 { throw CameraControllerError.invalidOperation }
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(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
276
- if(lens == "ultra"){
277
- session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera], mediaType: AVMediaType.video, position: .unspecified)
278
- } else if(lens == "wide"){
279
- session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
280
- } else if(lens == "tele"){
281
- session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
282
- }
283
- let cameras = session.devices.compactMap { $0 }
284
- guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
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
- for camera in cameras {
287
- if camera.position == .front {
288
- self.frontCamera = camera
289
- }
339
+ for camera in cameras {
340
+ if camera.position == .front {
341
+ self.frontCamera = camera
342
+ }
290
343
 
291
- if camera.position == .back {
292
- self.rearCamera = camera
344
+ if camera.position == .back {
345
+ self.rearCamera = camera
293
346
 
294
- try camera.lockForConfiguration()
295
- if camera.isFocusModeSupported(.continuousAutoFocus){
296
- camera.focusMode = .continuousAutoFocus
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 { return }
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
- if let device = AVCaptureDevice.default(.builtInUltraWideCamera, for: AVMediaType.video, position: .back) {
455
+ var supportedCameras: [String] = []
456
+
457
+ if AVCaptureDevice.default(.builtInUltraWideCamera, for: .video, position: .back) != nil {
400
458
  supportedCameras.append("ultra")
401
459
  }
402
- if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back) {
460
+ if AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) != nil {
403
461
  supportedCameras.append("wide")
404
462
  }
405
- if let device = AVCaptureDevice.default(.builtInTelephotoCamera, for: AVMediaType.video, position: .back) {
463
+ if AVCaptureDevice.default(.builtInTelephotoCamera, for: .video, position: .back) != nil {
406
464
  supportedCameras.append("tele")
407
465
  }
408
- return supportedCameras;
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
- public func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?,
572
- resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) {
573
- if let error = error { self.photoCaptureCompletionBlock?(nil, error) } else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil),
574
- let image = UIImage(data: data) {
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
- } else {
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
+
@@ -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
- if UIApplication.shared.statusBarOrientation.isLandscape {
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 UIApplication.shared.statusBarOrientation.isPortrait {
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.cameraPosition == "front" {
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 quality: Int? = call.getInt("quality", 85)
310
+ let _ = call.getInt("quality", 85)
282
311
 
283
312
  self.cameraController.captureVideo { (image, error) in
284
313
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-community-multilens-camerapreview",
3
- "version": "7.0.1",
3
+ "version": "7.1.1",
4
4
  "description": "fork of capacitor community camera preview with support for switchting lenses",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",