@capgo/camera-preview 8.11.1 → 8.11.2

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.
@@ -82,8 +82,8 @@ class CameraController: NSObject {
82
82
  }
83
83
  }
84
84
 
85
- // 2. Reset Exposure to the center ONLY if it is not explicitly locked
86
- if device.exposureMode != .locked {
85
+ // 2. Reset Exposure to the center ONLY if it is not explicitly locked by the user
86
+ if self.shouldAutoAdjustExposureOnFocus {
87
87
  if device.isExposureModeSupported(.continuousAutoExposure) {
88
88
  device.exposureMode = .continuousAutoExposure
89
89
  if device.isExposurePointOfInterestSupported {
@@ -105,6 +105,14 @@ class CameraController: NSObject {
105
105
 
106
106
  var captureSession: AVCaptureSession?
107
107
  var disableFocusIndicator: Bool = false
108
+ /// App-level exposure preference. AVFoundation's `.autoExpose` auto-transitions to
109
+ /// `.locked` after one-shot metering, so device.exposureMode alone cannot tell whether
110
+ /// the user explicitly locked exposure via setExposureMode("LOCK").
111
+ private var preferredExposureMode: String = "CONTINUOUS"
112
+ /// AUTO/CONTINUOUS allow tap/restore to reset metering + EV; LOCK/CUSTOM must keep user settings.
113
+ private var shouldAutoAdjustExposureOnFocus: Bool {
114
+ preferredExposureMode == "AUTO" || preferredExposureMode == "CONTINUOUS"
115
+ }
108
116
  private var focusExposureRestoreGeneration: UInt = 0
109
117
  private var focusExposureRestoreTimeoutWorkItem: DispatchWorkItem?
110
118
  private let focusExposureRestoreTimeout: TimeInterval = 3.0
@@ -1290,12 +1298,13 @@ extension CameraController {
1290
1298
  self.rearCameraInput = newInput
1291
1299
  self.currentCameraPosition = .rear
1292
1300
  }
1293
- // (Lightweight focus config; non-fatal on failure)
1301
+ // (Lightweight focus/exposure config; non-fatal on failure)
1294
1302
  try? targetDevice.lockForConfiguration()
1295
1303
  if targetDevice.isFocusModeSupported(.continuousAutoFocus) {
1296
1304
  targetDevice.focusMode = .continuousAutoFocus
1297
1305
  }
1298
1306
  targetDevice.unlockForConfiguration()
1307
+ try? self.applyPreferredExposureMode(to: targetDevice)
1299
1308
 
1300
1309
  // Restore audio input if it existed
1301
1310
  if let audioInput = existingAudioInput, captureSession.canAddInput(audioInput) {
@@ -2224,7 +2233,8 @@ extension CameraController {
2224
2233
  device.focusPointOfInterest = point
2225
2234
  }
2226
2235
 
2227
- if adjustExposure, device.exposureMode != .locked {
2236
+ // Prefer app-level mode: `.autoExpose` leaves device.exposureMode as `.locked`.
2237
+ if adjustExposure, self.shouldAutoAdjustExposureOnFocus {
2228
2238
  if device.isExposurePointOfInterestSupported, device.isExposureModeSupported(.autoExpose) {
2229
2239
  device.exposureMode = .autoExpose
2230
2240
  device.setExposureTargetBias(0.0) { _ in }
@@ -2282,7 +2292,9 @@ extension CameraController {
2282
2292
  }
2283
2293
  }
2284
2294
 
2285
- if device.exposureMode != .locked {
2295
+ // Restore continuous AE for AUTO/CONTINUOUS only (preserve LOCK/CUSTOM).
2296
+ // Do not trust device.exposureMode here: one-shot `.autoExpose` ends in `.locked`.
2297
+ if self.shouldAutoAdjustExposureOnFocus {
2286
2298
  if device.isExposureModeSupported(.continuousAutoExposure) {
2287
2299
  device.exposureMode = .continuousAutoExposure
2288
2300
  if device.isExposurePointOfInterestSupported {
@@ -2302,13 +2314,7 @@ extension CameraController {
2302
2314
  guard let device = self.activeVideoDevice() else { return }
2303
2315
 
2304
2316
  let centerPoint = CGPoint(x: 0.5, y: 0.5)
2305
- let adjustExposure: Bool
2306
- do {
2307
- let exposureMode = try self.getExposureMode()
2308
- adjustExposure = exposureMode == "AUTO" || exposureMode == "CONTINUOUS"
2309
- } catch {
2310
- adjustExposure = true
2311
- }
2317
+ let adjustExposure = self.shouldAutoAdjustExposureOnFocus
2312
2318
 
2313
2319
  do {
2314
2320
  try self.applyOneShotFocusAndExposure(at: centerPoint, on: device, adjustExposure: adjustExposure)
@@ -2498,14 +2504,15 @@ extension CameraController {
2498
2504
  self.rearCameraInput = newInput
2499
2505
  self.rearCamera = targetDevice
2500
2506
  self.currentCameraPosition = .rear
2507
+ }
2501
2508
 
2502
- // Configure rear camera
2503
- try targetDevice.lockForConfiguration()
2504
- if targetDevice.isFocusModeSupported(.continuousAutoFocus) {
2505
- targetDevice.focusMode = .continuousAutoFocus
2506
- }
2507
- targetDevice.unlockForConfiguration()
2509
+ // Lightweight focus/exposure config; non-fatal on failure
2510
+ try? targetDevice.lockForConfiguration()
2511
+ if targetDevice.isFocusModeSupported(.continuousAutoFocus) {
2512
+ targetDevice.focusMode = .continuousAutoFocus
2508
2513
  }
2514
+ targetDevice.unlockForConfiguration()
2515
+ try? self.applyPreferredExposureMode(to: targetDevice)
2509
2516
  } else {
2510
2517
  throw CameraControllerError.invalidOperation
2511
2518
  }
@@ -2564,6 +2571,7 @@ extension CameraController {
2564
2571
 
2565
2572
  self.captureSession = nil
2566
2573
  self.currentCameraPosition = nil
2574
+ self.preferredExposureMode = "CONTINUOUS"
2567
2575
 
2568
2576
  // Reset output preparation status
2569
2577
  self.outputsPrepared = false
@@ -2576,6 +2584,39 @@ extension CameraController {
2576
2584
 
2577
2585
  // MARK: - Exposure Controls
2578
2586
 
2587
+ private func applyPreferredExposureMode(to device: AVCaptureDevice) throws {
2588
+ let mode = self.preferredExposureMode
2589
+ let desiredMode: AVCaptureDevice.ExposureMode
2590
+ switch mode {
2591
+ case "LOCK":
2592
+ desiredMode = .locked
2593
+ case "AUTO":
2594
+ desiredMode = .autoExpose
2595
+ case "CUSTOM":
2596
+ desiredMode = .custom
2597
+ default:
2598
+ desiredMode = .continuousAutoExposure
2599
+ }
2600
+
2601
+ guard device.isExposureModeSupported(desiredMode) else {
2602
+ // Fall back to continuous when the preferred mode is unavailable on this lens.
2603
+ if device.isExposureModeSupported(.continuousAutoExposure) {
2604
+ try device.lockForConfiguration()
2605
+ device.exposureMode = .continuousAutoExposure
2606
+ device.setExposureTargetBias(0.0) { _ in }
2607
+ device.unlockForConfiguration()
2608
+ }
2609
+ return
2610
+ }
2611
+
2612
+ try device.lockForConfiguration()
2613
+ device.exposureMode = desiredMode
2614
+ if desiredMode == .autoExpose || desiredMode == .continuousAutoExposure {
2615
+ device.setExposureTargetBias(0.0) { _ in }
2616
+ }
2617
+ device.unlockForConfiguration()
2618
+ }
2619
+
2579
2620
  func getExposureModes() throws -> [String] {
2580
2621
  var currentCamera: AVCaptureDevice?
2581
2622
  switch currentCameraPosition {
@@ -2610,22 +2651,12 @@ extension CameraController {
2610
2651
  break
2611
2652
  }
2612
2653
 
2613
- guard let device = currentCamera else {
2654
+ guard currentCamera != nil else {
2614
2655
  throw CameraControllerError.noCamerasAvailable
2615
2656
  }
2616
2657
 
2617
- switch device.exposureMode {
2618
- case .locked:
2619
- return "LOCK"
2620
- case .autoExpose:
2621
- return "AUTO"
2622
- case .continuousAutoExposure:
2623
- return "CONTINUOUS"
2624
- case .custom:
2625
- return "CUSTOM"
2626
- @unknown default:
2627
- return "CONTINUOUS"
2628
- }
2658
+ // App preference, not device.exposureMode: one-shot `.autoExpose` ends as `.locked`.
2659
+ return self.preferredExposureMode
2629
2660
  }
2630
2661
 
2631
2662
  func setExposureMode(mode: String) throws {
@@ -2662,14 +2693,15 @@ extension CameraController {
2662
2693
  throw CameraControllerError.invalidOperation
2663
2694
  }
2664
2695
 
2696
+ switch normalized {
2697
+ case "LOCK", "AUTO", "CONTINUOUS", "CUSTOM":
2698
+ self.preferredExposureMode = normalized
2699
+ default:
2700
+ self.preferredExposureMode = "CONTINUOUS"
2701
+ }
2702
+
2665
2703
  do {
2666
- try device.lockForConfiguration()
2667
- device.exposureMode = finalMode
2668
- // Reset EV to 0 when switching to AUTO or CONTINUOUS
2669
- if finalMode == .autoExpose || finalMode == .continuousAutoExposure {
2670
- device.setExposureTargetBias(0.0) { _ in }
2671
- }
2672
- device.unlockForConfiguration()
2704
+ try self.applyPreferredExposureMode(to: device)
2673
2705
  } catch {
2674
2706
  throw CameraControllerError.invalidOperation
2675
2707
  }
@@ -35,7 +35,7 @@ extension UIWindow {
35
35
  */
36
36
  @objc(CameraPreview)
37
37
  public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelegate {
38
- private let pluginVersion: String = "8.11.1"
38
+ private let pluginVersion: String = "8.11.2"
39
39
  public let identifier = "CameraPreviewPlugin"
40
40
  public let jsName = "CameraPreview"
41
41
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "8.11.1",
3
+ "version": "8.11.2",
4
4
  "description": "Camera preview",
5
5
  "license": "MPL-2.0",
6
6
  "repository": {