@capgo/camera-preview 7.2.9 → 7.3.0

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.
@@ -202,21 +202,35 @@ extension CameraController {
202
202
  }
203
203
 
204
204
  func updateVideoOrientation() {
205
- assert(Thread.isMainThread) // UIApplication.statusBarOrientation requires the main thread.
205
+ if Thread.isMainThread {
206
+ updateVideoOrientationOnMainThread()
207
+ } else {
208
+ DispatchQueue.main.async { [weak self] in
209
+ self?.updateVideoOrientationOnMainThread()
210
+ }
211
+ }
212
+ }
206
213
 
214
+ private func updateVideoOrientationOnMainThread() {
207
215
  let videoOrientation: AVCaptureVideoOrientation
208
- switch UIApplication.shared.statusBarOrientation {
209
- case .portrait:
210
- videoOrientation = .portrait
211
- case .landscapeLeft:
212
- videoOrientation = .landscapeLeft
213
- case .landscapeRight:
214
- videoOrientation = .landscapeRight
215
- case .portraitUpsideDown:
216
- videoOrientation = .portraitUpsideDown
217
- case .unknown:
218
- fallthrough
219
- @unknown default:
216
+
217
+ // Use window scene interface orientation
218
+ if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
219
+ switch windowScene.interfaceOrientation {
220
+ case .portrait:
221
+ videoOrientation = .portrait
222
+ case .landscapeLeft:
223
+ videoOrientation = .landscapeLeft
224
+ case .landscapeRight:
225
+ videoOrientation = .landscapeRight
226
+ case .portraitUpsideDown:
227
+ videoOrientation = .portraitUpsideDown
228
+ case .unknown:
229
+ fallthrough
230
+ @unknown default:
231
+ videoOrientation = .portrait
232
+ }
233
+ } else {
220
234
  videoOrientation = .portrait
221
235
  }
222
236
 
@@ -226,53 +240,98 @@ extension CameraController {
226
240
  }
227
241
 
228
242
  func switchCameras() throws {
229
- guard let currentCameraPosition = currentCameraPosition, let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
230
-
243
+ guard let currentCameraPosition = currentCameraPosition,
244
+ let captureSession = self.captureSession else {
245
+ throw CameraControllerError.captureSessionIsMissing
246
+ }
247
+
248
+ // Ensure we have the necessary cameras
249
+ guard (currentCameraPosition == .front && rearCamera != nil) ||
250
+ (currentCameraPosition == .rear && frontCamera != nil) else {
251
+ throw CameraControllerError.noCamerasAvailable
252
+ }
253
+
254
+ // Store the current running state
255
+ let wasRunning = captureSession.isRunning
256
+ if wasRunning {
257
+ captureSession.stopRunning()
258
+ }
259
+
260
+ // Begin configuration
231
261
  captureSession.beginConfiguration()
262
+ defer {
263
+ captureSession.commitConfiguration()
264
+ // Restart the session if it was running before
265
+ if wasRunning {
266
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
267
+ self?.captureSession?.startRunning()
268
+ }
269
+ }
270
+ }
271
+
272
+ // Store audio input if it exists
273
+ let audioInput = captureSession.inputs.first { ($0 as? AVCaptureDeviceInput)?.device.hasMediaType(.audio) ?? false }
232
274
 
233
- func switchToFrontCamera() throws {
234
-
235
- guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput),
236
- let frontCamera = self.frontCamera else { throw CameraControllerError.invalidOperation }
237
-
238
- self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
239
-
240
- captureSession.removeInput(rearCameraInput)
241
-
242
- if captureSession.canAddInput(self.frontCameraInput!) {
243
- captureSession.addInput(self.frontCameraInput!)
275
+ // Remove only video inputs
276
+ captureSession.inputs.forEach { input in
277
+ if (input as? AVCaptureDeviceInput)?.device.hasMediaType(.video) ?? false {
278
+ captureSession.removeInput(input)
279
+ }
280
+ }
281
+
282
+ // Configure new camera
283
+ switch currentCameraPosition {
284
+ case .front:
285
+ guard let rearCamera = rearCamera else {
286
+ throw CameraControllerError.invalidOperation
287
+ }
288
+
289
+ // Configure rear camera
290
+ try rearCamera.lockForConfiguration()
291
+ if rearCamera.isFocusModeSupported(.continuousAutoFocus) {
292
+ rearCamera.focusMode = .continuousAutoFocus
293
+ }
294
+ rearCamera.unlockForConfiguration()
295
+
296
+ if let newInput = try? AVCaptureDeviceInput(device: rearCamera),
297
+ captureSession.canAddInput(newInput) {
298
+ captureSession.addInput(newInput)
299
+ rearCameraInput = newInput
300
+ self.currentCameraPosition = .rear
301
+ } else {
302
+ throw CameraControllerError.invalidOperation
303
+ }
304
+ case .rear:
305
+ guard let frontCamera = frontCamera else {
306
+ throw CameraControllerError.invalidOperation
307
+ }
244
308
 
309
+ // Configure front camera
310
+ try frontCamera.lockForConfiguration()
311
+ if frontCamera.isFocusModeSupported(.continuousAutoFocus) {
312
+ frontCamera.focusMode = .continuousAutoFocus
313
+ }
314
+ frontCamera.unlockForConfiguration()
315
+
316
+ if let newInput = try? AVCaptureDeviceInput(device: frontCamera),
317
+ captureSession.canAddInput(newInput) {
318
+ captureSession.addInput(newInput)
319
+ frontCameraInput = newInput
245
320
  self.currentCameraPosition = .front
246
321
  } else {
247
322
  throw CameraControllerError.invalidOperation
248
323
  }
249
324
  }
250
325
 
251
- func switchToRearCamera() throws {
252
-
253
- guard let frontCameraInput = self.frontCameraInput, captureSession.inputs.contains(frontCameraInput),
254
- let rearCamera = self.rearCamera else { throw CameraControllerError.invalidOperation }
255
-
256
- self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
257
-
258
- captureSession.removeInput(frontCameraInput)
259
-
260
- if captureSession.canAddInput(self.rearCameraInput!) {
261
- captureSession.addInput(self.rearCameraInput!)
262
-
263
- self.currentCameraPosition = .rear
264
- } else { throw CameraControllerError.invalidOperation }
326
+ // Re-add audio input if it existed
327
+ if let audioInput = audioInput, captureSession.canAddInput(audioInput) {
328
+ captureSession.addInput(audioInput)
265
329
  }
266
-
267
- switch currentCameraPosition {
268
- case .front:
269
- try switchToRearCamera()
270
-
271
- case .rear:
272
- try switchToFrontCamera()
330
+
331
+ // Update video orientation
332
+ DispatchQueue.main.async { [weak self] in
333
+ self?.updateVideoOrientation()
273
334
  }
274
-
275
- captureSession.commitConfiguration()
276
335
  }
277
336
 
278
337
  func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
@@ -263,11 +263,54 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
263
263
  }
264
264
 
265
265
  @objc func flip(_ call: CAPPluginCall) {
266
- do {
267
- try self.cameraController.switchCameras()
268
- call.resolve()
269
- } catch {
270
- call.reject("failed to flip camera")
266
+ guard isInitialized else {
267
+ call.reject("Camera not initialized")
268
+ return
269
+ }
270
+
271
+ DispatchQueue.main.async { [weak self] in
272
+ guard let self = self else {
273
+ call.reject("Camera controller deallocated")
274
+ return
275
+ }
276
+
277
+ // Disable user interaction during flip
278
+ self.previewView.isUserInteractionEnabled = false
279
+
280
+ // Perform camera switch on background thread
281
+ DispatchQueue.global(qos: .userInitiated).async {
282
+ var retryCount = 0
283
+ let maxRetries = 3
284
+
285
+ func attemptFlip() {
286
+ do {
287
+ try self.cameraController.switchCameras()
288
+
289
+ DispatchQueue.main.async {
290
+ self.cameraController.previewLayer?.frame = self.previewView.bounds
291
+ self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
292
+ self.previewView.isUserInteractionEnabled = true
293
+ call.resolve()
294
+ }
295
+ } catch {
296
+ retryCount += 1
297
+
298
+ if retryCount < maxRetries {
299
+ DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.5) {
300
+ attemptFlip()
301
+ }
302
+ } else {
303
+ DispatchQueue.main.async {
304
+ self.previewView.isUserInteractionEnabled = true
305
+ print("Failed to flip camera after \(maxRetries) attempts: \(error.localizedDescription)")
306
+ call.reject("Failed to flip camera: \(error.localizedDescription)")
307
+ }
308
+ }
309
+ }
310
+ }
311
+
312
+ attemptFlip()
313
+ }
271
314
  }
272
315
  }
273
316
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.2.9",
3
+ "version": "7.3.0",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {