expo-camera 55.0.4 → 55.0.6

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/android/build.gradle +2 -2
  3. package/expo-module.config.json +1 -1
  4. package/ios/CameraViewModule.swift +17 -5
  5. package/ios/Common/CameraExceptions.swift +7 -1
  6. package/ios/Common/DeviceDiscovery.swift +63 -25
  7. package/ios/Current/BarcodeRecord.swift +3 -3
  8. package/ios/Current/CameraSessionManager.swift +83 -37
  9. package/ios/Current/CameraView.swift +19 -8
  10. package/ios/Current/MetaDataDelegate.swift +3 -6
  11. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.aar +0 -0
  12. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.aar.md5 +1 -0
  13. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.aar.sha1 +1 -0
  14. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.aar.sha256 +1 -0
  15. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.aar.sha512 +1 -0
  16. package/local-maven-repo/host/exp/exponent/expo.modules.camera/{55.0.4/expo.modules.camera-55.0.4.module → 55.0.6/expo.modules.camera-55.0.6.module} +17 -17
  17. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.module.md5 +1 -0
  18. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.module.sha1 +1 -0
  19. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.module.sha256 +1 -0
  20. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.module.sha512 +1 -0
  21. package/local-maven-repo/host/exp/exponent/expo.modules.camera/{55.0.4/expo.modules.camera-55.0.4.pom → 55.0.6/expo.modules.camera-55.0.6.pom} +1 -1
  22. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.pom.md5 +1 -0
  23. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.pom.sha1 +1 -0
  24. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.pom.sha256 +1 -0
  25. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.6/expo.modules.camera-55.0.6.pom.sha512 +1 -0
  26. package/local-maven-repo/host/exp/exponent/expo.modules.camera/maven-metadata.xml +4 -4
  27. package/local-maven-repo/host/exp/exponent/expo.modules.camera/maven-metadata.xml.md5 +1 -1
  28. package/local-maven-repo/host/exp/exponent/expo.modules.camera/maven-metadata.xml.sha1 +1 -1
  29. package/local-maven-repo/host/exp/exponent/expo.modules.camera/maven-metadata.xml.sha256 +1 -1
  30. package/local-maven-repo/host/exp/exponent/expo.modules.camera/maven-metadata.xml.sha512 +1 -1
  31. package/package.json +2 -2
  32. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.aar +0 -0
  33. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.aar.md5 +0 -1
  34. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.aar.sha1 +0 -1
  35. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.aar.sha256 +0 -1
  36. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.aar.sha512 +0 -1
  37. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.module.md5 +0 -1
  38. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.module.sha1 +0 -1
  39. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.module.sha256 +0 -1
  40. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.module.sha512 +0 -1
  41. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.pom.md5 +0 -1
  42. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.pom.sha1 +0 -1
  43. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.pom.sha256 +0 -1
  44. package/local-maven-repo/host/exp/exponent/expo.modules.camera/55.0.4/expo.modules.camera-55.0.4.pom.sha512 +0 -1
  45. /package/local-maven-repo/host/exp/exponent/expo.modules.camera/{55.0.4/expo.modules.camera-55.0.4-sources.jar → 55.0.6/expo.modules.camera-55.0.6-sources.jar} +0 -0
  46. /package/local-maven-repo/host/exp/exponent/expo.modules.camera/{55.0.4/expo.modules.camera-55.0.4-sources.jar.md5 → 55.0.6/expo.modules.camera-55.0.6-sources.jar.md5} +0 -0
  47. /package/local-maven-repo/host/exp/exponent/expo.modules.camera/{55.0.4/expo.modules.camera-55.0.4-sources.jar.sha1 → 55.0.6/expo.modules.camera-55.0.6-sources.jar.sha1} +0 -0
  48. /package/local-maven-repo/host/exp/exponent/expo.modules.camera/{55.0.4/expo.modules.camera-55.0.4-sources.jar.sha256 → 55.0.6/expo.modules.camera-55.0.6-sources.jar.sha256} +0 -0
  49. /package/local-maven-repo/host/exp/exponent/expo.modules.camera/{55.0.4/expo.modules.camera-55.0.4-sources.jar.sha512 → 55.0.6/expo.modules.camera-55.0.6-sources.jar.sha512} +0 -0
package/CHANGELOG.md CHANGED
@@ -10,6 +10,16 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 55.0.6 — 2026-02-16
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 55.0.5 — 2026-02-08
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - [iOS] Fixed a number of configuration issues and error inconsistencies. ([#42926](https://github.com/expo/expo/pull/42926) by [@alanjhughes](https://github.com/alanjhughes))
22
+
13
23
  ## 55.0.4 — 2026-02-03
14
24
 
15
25
  _This version does not introduce any user-facing changes._
@@ -39,6 +49,7 @@ _This version does not introduce any user-facing changes._
39
49
  - [Android] Fix certain exif keys being dropped because of invalid values. ([#41043](https://github.com/expo/expo/pull/41043) by [@alanjhughes](https://github.com/alanjhughes))
40
50
  - [Android] Fix camera not being recreated on the old architecture. ([#41405](https://github.com/expo/expo/pull/41405) by [@alanjhughes](https://github.com/alanjhughes))
41
51
  - [iOS] Return correct size information when using image refs. ([#41658](https://github.com/expo/expo/pull/41658) by [@alanjhughes](https://github.com/alanjhughes))
52
+ - [iOS] Fix incorrect isPinchToZoomEnabled default value for scanner. ([#41803](https://github.com/expo/expo/pull/41803) by [@hssdiv](https://github.com/hssdiv))
42
53
 
43
54
  ## 17.0.10 - 2025-12-05
44
55
 
@@ -4,7 +4,7 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '55.0.4'
7
+ version = '55.0.6'
8
8
 
9
9
  def barcodeScannerEnabled = findProperty('expo.camera.barcode-scanner-enabled')
10
10
  def isBarcodeScannerEnabled = (barcodeScannerEnabled ?: "true").toString() != "false"
@@ -13,7 +13,7 @@ android {
13
13
  namespace "expo.modules.camera"
14
14
  defaultConfig {
15
15
  versionCode 32
16
- versionName "55.0.4"
16
+ versionName "55.0.6"
17
17
  }
18
18
  }
19
19
 
@@ -8,7 +8,7 @@
8
8
  "publication": {
9
9
  "groupId": "host.exp.exponent",
10
10
  "artifactId": "expo.modules.camera",
11
- "version": "55.0.4",
11
+ "version": "55.0.6",
12
12
  "repository": "local-maven-repo"
13
13
  }
14
14
  }
@@ -335,7 +335,10 @@ public final class CameraViewModule: Module, ScannerResultHandler {
335
335
 
336
336
  AsyncFunction("launchScanner") { (options: VisionScannerOptions?) in
337
337
  if #available(iOS 16.0, *) {
338
- await MainActor.run {
338
+ try await MainActor.run {
339
+ guard DataScannerViewController.isSupported, DataScannerViewController.isAvailable else {
340
+ throw CameraScannerUnavailableException()
341
+ }
339
342
  let delegate = VisionScannerDelegate(handler: self)
340
343
  scannerContext = ScannerContext(delegate: delegate)
341
344
  launchScanner(with: options)
@@ -435,22 +438,31 @@ public final class CameraViewModule: Module, ScannerResultHandler {
435
438
  guard let captureDevice = ExpoCameraUtils.device(
436
439
  with: AVMediaType.video,
437
440
  preferring: AVCaptureDevice.Position.front) else {
441
+ session.commitConfiguration()
438
442
  return []
439
443
  }
440
444
  guard let deviceInput = try? AVCaptureDeviceInput(device: captureDevice) else {
445
+ session.commitConfiguration()
441
446
  return []
442
447
  }
443
448
  if session.canAddInput(deviceInput) {
444
449
  session.addInput(deviceInput)
445
450
  }
446
451
 
447
- session.commitConfiguration()
448
-
449
452
  let movieFileOutput = AVCaptureMovieFileOutput()
450
-
451
453
  if session.canAddOutput(movieFileOutput) {
452
454
  session.addOutput(movieFileOutput)
453
455
  }
454
- return movieFileOutput.availableVideoCodecTypes.map { $0.rawValue }
456
+
457
+ session.commitConfiguration()
458
+
459
+ let codecs = movieFileOutput.availableVideoCodecTypes.map { $0.rawValue }
460
+
461
+ session.beginConfiguration()
462
+ session.removeOutput(movieFileOutput)
463
+ session.removeInput(deviceInput)
464
+ session.commitConfiguration()
465
+
466
+ return codecs
455
467
  }
456
468
  }
@@ -8,7 +8,7 @@ internal final class CameraUnmountedException: Exception {
8
8
 
9
9
  internal final class CameraNotReadyException: Exception {
10
10
  override var reason: String {
11
- "Camera unmounted during taking photo process"
11
+ "Camera is not ready yet"
12
12
  }
13
13
  }
14
14
 
@@ -59,3 +59,9 @@ internal final class CameraToggleRecordingException: Exception {
59
59
  "`toggleRecording()` is only supported on iOS 18.0 or later"
60
60
  }
61
61
  }
62
+
63
+ internal final class CameraScannerUnavailableException: Exception {
64
+ override var reason: String {
65
+ "Modern barcode scanner is not available on this device"
66
+ }
67
+ }
@@ -1,46 +1,58 @@
1
1
  import AVFoundation
2
2
  import Foundation
3
3
 
4
+ protocol DeviceDiscoveryDelegate: AnyObject {
5
+ func deviceDiscovery(_ discovery: DeviceDiscovery, didUpdateDevices devices: [AVCaptureDevice])
6
+ }
7
+
4
8
  class DeviceDiscovery {
5
9
  private let frontCameraDiscoverySession: AVCaptureDevice.DiscoverySession
6
10
  private let backCameraDiscoverySession: AVCaptureDevice.DiscoverySession
7
11
 
8
- private var allDeviceTypes: [AVCaptureDevice.DeviceType] = [
9
- // Suitable for general-purpose use.
10
- .builtInWideAngleCamera,
11
- // Longer focal length than wide angle camera
12
- .builtInTelephotoCamera,
13
- // Shorter focal length than wide angle camera
14
- .builtInUltraWideCamera,
15
- // Infrared camera provides high-quality depth information that’s synchronized and perspective corrected to the frame the YUV camera produces.
16
- .builtInTrueDepthCamera,
17
- // Virtual cameras
18
- // Type that consists of a wide-angle and telephoto camera.
19
- .builtInDualCamera,
20
- // Type that consists of two cameras of fixed focal length, one ultrawide angle and one wide angle.
21
- .builtInDualWideCamera,
22
- // Type that consists of three cameras of fixed focal length, one ultrawide angle, one wide angle, and one telephoto.
23
- .builtInTripleCamera
24
- ]
12
+ weak var delegate: DeviceDiscoveryDelegate?
13
+
14
+ private var frontDevicesObservation: NSKeyValueObservation?
15
+ private var backDevicesObservation: NSKeyValueObservation?
16
+
17
+ private static var allDeviceTypes: [AVCaptureDevice.DeviceType] {
18
+ var types: [AVCaptureDevice.DeviceType] = [
19
+ // Primary camera - suitable for general-purpose use
20
+ .builtInWideAngleCamera,
21
+ // Longer focal length than wide angle camera
22
+ .builtInTelephotoCamera,
23
+ // Shorter focal length than wide angle camera
24
+ .builtInUltraWideCamera,
25
+ // TrueDepth camera for Face ID devices
26
+ .builtInTrueDepthCamera,
27
+ // Virtual cameras - these combine multiple physical cameras
28
+ // Triple camera (ultrawide + wide + telephoto)
29
+ .builtInTripleCamera,
30
+ // Dual camera (wide + telephoto)
31
+ .builtInDualCamera,
32
+ // Dual wide camera (ultrawide + wide)
33
+ .builtInDualWideCamera
34
+ ]
25
35
 
26
- init() {
27
- if #available(iOS 17, *) {
28
- allDeviceTypes.append(.continuityCamera)
29
- }
30
36
  if #available(iOS 15.4, *) {
31
37
  // LiDAR camera provides high-quality, high-accuracy depth information by measuring the round trip of an artificial light signal that a laser emits.
32
- allDeviceTypes.append(.builtInLiDARDepthCamera)
38
+ types.append(.builtInLiDARDepthCamera)
33
39
  }
34
40
 
41
+ return types
42
+ }
43
+
44
+ init() {
35
45
  backCameraDiscoverySession = AVCaptureDevice.DiscoverySession(
36
- deviceTypes: allDeviceTypes,
46
+ deviceTypes: Self.allDeviceTypes,
37
47
  mediaType: .video,
38
48
  position: .back)
39
49
  frontCameraDiscoverySession = AVCaptureDevice.DiscoverySession(
40
- deviceTypes: allDeviceTypes,
50
+ deviceTypes: Self.allDeviceTypes,
41
51
  mediaType: .video,
42
52
  position: .front)
43
53
 
54
+ setupDeviceObservation()
55
+
44
56
  if #available(iOS 17.0, *) {
45
57
  if AVCaptureDevice.systemPreferredCamera == nil {
46
58
  AVCaptureDevice.userPreferredCamera = backCameraDiscoverySession.devices.first
@@ -48,6 +60,25 @@ class DeviceDiscovery {
48
60
  }
49
61
  }
50
62
 
63
+ deinit {
64
+ frontDevicesObservation?.invalidate()
65
+ backDevicesObservation?.invalidate()
66
+ }
67
+
68
+ private func setupDeviceObservation() {
69
+ backDevicesObservation = backCameraDiscoverySession.observe(\.devices, options: [.new]) { [weak self] _, _ in
70
+ self?.notifyDevicesChanged()
71
+ }
72
+
73
+ frontDevicesObservation = frontCameraDiscoverySession.observe(\.devices, options: [.new]) { [weak self] _, _ in
74
+ self?.notifyDevicesChanged()
75
+ }
76
+ }
77
+
78
+ private func notifyDevicesChanged() {
79
+ delegate?.deviceDiscovery(self, didUpdateDevices: cameras)
80
+ }
81
+
51
82
  private var cameras: [AVCaptureDevice] {
52
83
  var cameras: [AVCaptureDevice] = []
53
84
  cameras.append(contentsOf: backCameraDiscoverySession.devices)
@@ -59,7 +90,8 @@ class DeviceDiscovery {
59
90
  }
60
91
  #endif
61
92
 
62
- return Array(Set(cameras))
93
+ var seen = Set<String>()
94
+ return cameras.filter { seen.insert($0.uniqueID).inserted }
63
95
  }
64
96
 
65
97
  var frontCameraLenses: [AVCaptureDevice] {
@@ -75,6 +107,12 @@ class DeviceDiscovery {
75
107
  }
76
108
 
77
109
  var defaultBackCamera: AVCaptureDevice? {
110
+ if let standardBack = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {
111
+ if self.backCameraLenses.contains(standardBack) {
112
+ return standardBack
113
+ }
114
+ }
115
+
78
116
  // iOS 17+: Check system preferred camera
79
117
  if #available(iOS 17.0, *) {
80
118
  if let preferred = AVCaptureDevice.systemPreferredCamera, preferred.position == .back {
@@ -147,9 +147,9 @@ enum VNBarcodeType: String, Enumerable {
147
147
 
148
148
  struct VisionScannerOptions: Record {
149
149
  @Field var barcodeTypes: [VNBarcodeType] = []
150
- @Field var isPinchToZoomEnabled: Bool = false
151
- @Field var isGuidanceEnabled: Bool = true
152
- @Field var isHighlightingEnabled: Bool = false
150
+ @Field var isPinchToZoomEnabled: Bool?
151
+ @Field var isGuidanceEnabled: Bool?
152
+ @Field var isHighlightingEnabled: Bool?
153
153
 
154
154
  @available(iOS 16.0, *)
155
155
  func toSymbology() -> [VNBarcodeSymbology] {
@@ -24,7 +24,7 @@ protocol CameraSessionManagerDelegate: AnyObject {
24
24
  func changePreviewOrientation()
25
25
  }
26
26
 
27
- class CameraSessionManager: NSObject {
27
+ class CameraSessionManager: NSObject, DeviceDiscoveryDelegate {
28
28
  weak var delegate: CameraSessionManagerDelegate?
29
29
 
30
30
  let session = AVCaptureSession()
@@ -33,10 +33,12 @@ class CameraSessionManager: NSObject {
33
33
  private var captureDeviceInput: AVCaptureDeviceInput?
34
34
  private var photoOutput: AVCapturePhotoOutput?
35
35
  private var videoFileOutput: AVCaptureMovieFileOutput?
36
+ private var runtimeErrorTask: Task<Void, Never>?
36
37
 
37
38
  init(delegate: CameraSessionManagerDelegate) {
38
39
  self.delegate = delegate
39
40
  super.init()
41
+ deviceDiscovery.delegate = self
40
42
  }
41
43
 
42
44
  func initializeCaptureSessionInput() {
@@ -49,20 +51,28 @@ class CameraSessionManager: NSObject {
49
51
  }
50
52
  }
51
53
 
52
- func updateSessionPreset(preset: AVCaptureSession.Preset) {
54
+ func updateSessionPreset(preset: AVCaptureSession.Preset, withSessionConfiguration: Bool = true) {
53
55
  #if !targetEnvironment(simulator)
54
56
  if session.canSetSessionPreset(preset) {
55
57
  if session.sessionPreset != preset {
56
- session.beginConfiguration()
57
- defer { session.commitConfiguration() }
58
+ if withSessionConfiguration {
59
+ session.beginConfiguration()
60
+ }
58
61
  session.sessionPreset = preset
62
+ if withSessionConfiguration {
63
+ session.commitConfiguration()
64
+ }
59
65
  }
60
66
  } else {
61
67
  // The selected preset cannot be used on the current device so we fall back to the highest available.
62
68
  if session.sessionPreset != .high {
63
- session.beginConfiguration()
64
- defer { session.commitConfiguration() }
69
+ if withSessionConfiguration {
70
+ session.beginConfiguration()
71
+ }
65
72
  session.sessionPreset = .high
73
+ if withSessionConfiguration {
74
+ session.commitConfiguration()
75
+ }
66
76
  }
67
77
  }
68
78
  #endif
@@ -98,14 +108,12 @@ class CameraSessionManager: NSObject {
98
108
  guard let delegate else {
99
109
  return
100
110
  }
101
- delegate.sessionQueue.async {
102
- if delegate.active {
103
- if !self.session.isRunning {
104
- self.session.startRunning()
105
- }
106
- } else {
107
- self.session.stopRunning()
111
+ if delegate.active {
112
+ if !self.session.isRunning {
113
+ self.session.startRunning()
108
114
  }
115
+ } else {
116
+ self.session.stopRunning()
109
117
  }
110
118
  }
111
119
 
@@ -113,24 +121,33 @@ class CameraSessionManager: NSObject {
113
121
  guard let delegate else {
114
122
  return
115
123
  }
116
-
124
+ self.session.beginConfiguration()
125
+ defer { self.session.commitConfiguration() }
117
126
  if delegate.mode == .video {
118
- if videoFileOutput == nil {
119
- setupMovieFileCapture()
127
+ if self.videoFileOutput == nil {
128
+ self.setupMovieFileCapture(withSessionConfiguration: false)
120
129
  }
121
- updateSessionAudioIsMuted()
130
+ self.updateSessionAudioIsMuted(withSessionConfiguration: false)
131
+ self.updateSessionPreset(preset: delegate.videoQuality.toPreset(), withSessionConfiguration: false)
122
132
  } else {
123
- cleanupMovieFileCapture()
133
+ self.cleanupMovieFileCapture(withSessionConfiguration: false)
134
+ self.updateSessionPreset(preset: delegate.pictureSize.toCapturePreset(), withSessionConfiguration: false)
124
135
  }
125
136
  }
126
137
 
127
- func updateSessionAudioIsMuted() {
138
+ func updateSessionAudioIsMuted(withSessionConfiguration: Bool = true) {
128
139
  guard let delegate else {
129
140
  return
130
141
  }
131
142
 
132
- session.beginConfiguration()
133
- defer { session.commitConfiguration() }
143
+ if withSessionConfiguration {
144
+ session.beginConfiguration()
145
+ }
146
+ defer {
147
+ if withSessionConfiguration {
148
+ session.commitConfiguration()
149
+ }
150
+ }
134
151
 
135
152
  if delegate.isMuted {
136
153
  for input in session.inputs {
@@ -164,13 +181,13 @@ class CameraSessionManager: NSObject {
164
181
 
165
182
  do {
166
183
  try device.lockForConfiguration()
184
+ defer { device.unlockForConfiguration() }
167
185
  if device.hasTorch && device.isTorchModeSupported(.on) {
168
186
  device.torchMode = delegate.torchEnabled ? .on : .off
169
187
  }
170
188
  } catch {
171
189
  log.info("\(#function): \(error.localizedDescription)")
172
190
  }
173
- device.unlockForConfiguration()
174
191
  }
175
192
 
176
193
  func setFocusMode() {
@@ -180,14 +197,13 @@ class CameraSessionManager: NSObject {
180
197
 
181
198
  do {
182
199
  try device.lockForConfiguration()
200
+ defer { device.unlockForConfiguration() }
183
201
  if device.isFocusModeSupported(delegate.autoFocus), device.focusMode != delegate.autoFocus {
184
202
  device.focusMode = delegate.autoFocus
185
203
  }
186
204
  } catch {
187
205
  log.info("\(#function): \(error.localizedDescription)")
188
- return
189
206
  }
190
- device.unlockForConfiguration()
191
207
  }
192
208
 
193
209
  func updateZoom() {
@@ -197,13 +213,12 @@ class CameraSessionManager: NSObject {
197
213
 
198
214
  do {
199
215
  try device.lockForConfiguration()
216
+ defer { device.unlockForConfiguration() }
200
217
  let minZoom = 1.0
201
218
  device.videoZoomFactor = minZoom * pow(device.activeFormat.videoMaxZoomFactor / minZoom, delegate.zoom)
202
219
  } catch {
203
220
  log.info("\(#function): \(error.localizedDescription)")
204
221
  }
205
-
206
- device.unlockForConfiguration()
207
222
  }
208
223
 
209
224
  func getAvailableLenses() -> [String] {
@@ -222,27 +237,46 @@ class CameraSessionManager: NSObject {
222
237
  }
223
238
  }
224
239
 
225
- func setupMovieFileCapture() {
240
+ func setupMovieFileCapture(withSessionConfiguration: Bool = true) {
226
241
  let output = AVCaptureMovieFileOutput()
242
+ if withSessionConfiguration {
243
+ session.beginConfiguration()
244
+ }
245
+ defer {
246
+ if withSessionConfiguration {
247
+ session.commitConfiguration()
248
+ }
249
+ }
227
250
  if session.canAddOutput(output) {
228
251
  session.addOutput(output)
229
252
  videoFileOutput = output
230
253
  }
231
254
  }
232
255
 
233
- func cleanupMovieFileCapture() {
234
- if let videoFileOutput {
235
- if session.outputs.contains(videoFileOutput) {
236
- session.removeOutput(videoFileOutput)
237
- self.videoFileOutput = nil
256
+ func cleanupMovieFileCapture(withSessionConfiguration: Bool = true) {
257
+ guard let videoFileOutput else {
258
+ return
259
+ }
260
+ if withSessionConfiguration {
261
+ session.beginConfiguration()
262
+ }
263
+ defer {
264
+ if withSessionConfiguration {
265
+ session.commitConfiguration()
238
266
  }
239
267
  }
268
+ if session.outputs.contains(videoFileOutput) {
269
+ session.removeOutput(videoFileOutput)
270
+ self.videoFileOutput = nil
271
+ }
240
272
  }
241
273
 
242
274
  func stopSession() {
243
275
  #if targetEnvironment(simulator)
244
276
  return
245
277
  #else
278
+ runtimeErrorTask?.cancel()
279
+ runtimeErrorTask = nil
246
280
  session.beginConfiguration()
247
281
  for input in self.session.inputs {
248
282
  session.removeInput(input)
@@ -264,14 +298,15 @@ class CameraSessionManager: NSObject {
264
298
  return
265
299
  }
266
300
 
267
- Task {
301
+ runtimeErrorTask?.cancel()
302
+ runtimeErrorTask = Task {
268
303
  let errors = NotificationCenter.default.notifications(named: .AVCaptureSessionRuntimeError, object: self.session)
269
304
  .compactMap({ $0.userInfo?[AVCaptureSessionErrorKey] as? AVError })
270
305
  for await error in errors where error.code == .mediaServicesWereReset {
271
- if !session.isRunning {
272
- session.startRunning()
273
- }
274
306
  delegate.sessionQueue.async {
307
+ if !self.session.isRunning {
308
+ self.session.startRunning()
309
+ }
275
310
  self.updateSessionAudioIsMuted()
276
311
  }
277
312
  delegate.onMountError(["message": "Camera session was reset"])
@@ -279,6 +314,10 @@ class CameraSessionManager: NSObject {
279
314
  }
280
315
  }
281
316
 
317
+ deinit {
318
+ runtimeErrorTask?.cancel()
319
+ }
320
+
282
321
  var currentPhotoOutput: AVCapturePhotoOutput? {
283
322
  return photoOutput
284
323
  }
@@ -317,6 +356,12 @@ class CameraSessionManager: NSObject {
317
356
  }
318
357
  }
319
358
 
359
+ func deviceDiscovery(_ discovery: DeviceDiscovery, didUpdateDevices devices: [AVCaptureDevice]) {
360
+ DispatchQueue.main.async { [weak self] in
361
+ self?.delegate?.emitAvailableLenses()
362
+ }
363
+ }
364
+
320
365
  private func startSession() {
321
366
  #if targetEnvironment(simulator)
322
367
  return
@@ -341,9 +386,10 @@ class CameraSessionManager: NSObject {
341
386
  self.photoOutput = photoOutput
342
387
  }
343
388
 
344
- session.sessionPreset = delegate.mode == .video
389
+ let preset = delegate.mode == .video
345
390
  ? delegate.videoQuality.toPreset()
346
391
  : delegate.pictureSize.toCapturePreset()
392
+ updateSessionPreset(preset: preset, withSessionConfiguration: false)
347
393
 
348
394
  session.commitConfiguration()
349
395
  addErrorNotification()
@@ -72,13 +72,17 @@ public class CameraView: ExpoView, EXAppLifecycleListener, EXCameraInterface, Ca
72
72
 
73
73
  var torchEnabled = false {
74
74
  didSet {
75
- sessionManager.enableTorch()
75
+ sessionQueue.async {
76
+ self.sessionManager.enableTorch()
77
+ }
76
78
  }
77
79
  }
78
80
 
79
81
  var autoFocus = AVCaptureDevice.FocusMode.continuousAutoFocus {
80
82
  didSet {
81
- sessionManager.setFocusMode()
83
+ sessionQueue.async {
84
+ self.sessionManager.setFocusMode()
85
+ }
82
86
  }
83
87
  }
84
88
 
@@ -92,8 +96,6 @@ public class CameraView: ExpoView, EXAppLifecycleListener, EXCameraInterface, Ca
92
96
  didSet {
93
97
  sessionQueue.async {
94
98
  self.sessionManager.setCameraMode()
95
- let preset = self.mode == .video ? self.videoQuality.toPreset() : self.pictureSize.toCapturePreset()
96
- self.sessionManager.updateSessionPreset(preset: preset)
97
99
  }
98
100
  }
99
101
  }
@@ -127,7 +129,9 @@ public class CameraView: ExpoView, EXAppLifecycleListener, EXCameraInterface, Ca
127
129
 
128
130
  var zoom: CGFloat = 0 {
129
131
  didSet {
130
- sessionManager.updateZoom()
132
+ sessionQueue.async {
133
+ self.sessionManager.updateZoom()
134
+ }
131
135
  }
132
136
  }
133
137
 
@@ -292,14 +296,20 @@ public class CameraView: ExpoView, EXAppLifecycleListener, EXCameraInterface, Ca
292
296
  super.layoutSubviews()
293
297
  self.backgroundColor = .black
294
298
  previewLayer.frame = self.bounds
295
- self.layer.insertSublayer(previewLayer, at: 0)
299
+ if previewLayer.superlayer == nil {
300
+ self.layer.insertSublayer(previewLayer, at: 0)
301
+ } else if previewLayer.superlayer !== self.layer {
302
+ previewLayer.removeFromSuperlayer()
303
+ self.layer.insertSublayer(previewLayer, at: 0)
304
+ }
296
305
  }
297
306
 
298
307
  public override func removeFromSuperview() {
299
308
  super.removeFromSuperview()
300
- sessionQueue.async {
301
- self.sessionManager.stopSession()
309
+ sessionQueue.async { [weak self] in
310
+ self?.sessionManager.stopSession()
302
311
  }
312
+ motionManager.stopAccelerometerUpdates()
303
313
  lifecycleManager?.unregisterAppLifecycleListener(self)
304
314
  UIDevice.current.endGeneratingDeviceOrientationNotifications()
305
315
  NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
@@ -349,6 +359,7 @@ public class CameraView: ExpoView, EXAppLifecycleListener, EXCameraInterface, Ca
349
359
  }
350
360
 
351
361
  deinit {
362
+ motionManager.stopAccelerometerUpdates()
352
363
  photoCapture.cleanup()
353
364
  videoRecording.cleanup()
354
365
  }
@@ -8,6 +8,7 @@ class MetaDataDelegate: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCapt
8
8
  private var zxingBarcodeReaders: [AVMetadataObject.ObjectType: ZXReader]
9
9
  private var zxingEnabled = true
10
10
  private var zxingFPSProcessed = 6.0
11
+ private var lastFrameTimeStamp = 0.0
11
12
  private let responseHandler: BarcodeScanningResponseHandler
12
13
 
13
14
  init(
@@ -56,13 +57,9 @@ class MetaDataDelegate: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCapt
56
57
 
57
58
  let kMinMargin = 1.0 / zxingFPSProcessed
58
59
  let presentTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
60
+ let curFrameTimeStamp = Double(presentTimeStamp.value) / Double(presentTimeStamp.timescale)
59
61
 
60
- var curFrameTimeStamp = 0.0
61
- var lastFrameTimeStamp = 0.0
62
-
63
- curFrameTimeStamp = Double(presentTimeStamp.value) / Double(presentTimeStamp.timescale)
64
-
65
- if curFrameTimeStamp - lastFrameTimeStamp > Double(kMinMargin) {
62
+ if curFrameTimeStamp - lastFrameTimeStamp > kMinMargin {
66
63
  lastFrameTimeStamp = curFrameTimeStamp
67
64
 
68
65
  if let videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer),
@@ -0,0 +1 @@
1
+ c5862a1aec8e9c9780077404b671a91b1c43209ba35b7b6bd2ee27881b688034
@@ -0,0 +1 @@
1
+ 52edba7d79334c74c7a17c171defae32841e30da027539fadc0591440ed07bb3f027f9de158d170d4943fa7a4a2aed1bb034845309037186e0b9354d9d16dd6f
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "host.exp.exponent",
5
5
  "module": "expo.modules.camera",
6
- "version": "55.0.4",
6
+ "version": "55.0.6",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -40,13 +40,13 @@
40
40
  ],
41
41
  "files": [
42
42
  {
43
- "name": "expo.modules.camera-55.0.4.aar",
44
- "url": "expo.modules.camera-55.0.4.aar",
45
- "size": 247936,
46
- "sha512": "956eb45fdc76aaefe16b953f95ea4a8a9a91b61e4bfb1862160677b446687885d4d4b913c112c9f0132b6ebf3ca8b9c7be7cc578638a526467d04c249f913225",
47
- "sha256": "31abe6afee3cb3d63616342eb71af92a63542a47dbf92237f04c8ae4cf767c7b",
48
- "sha1": "9da5cb490f6f883c94c336f27a6d847a143c71cd",
49
- "md5": "4329b4177e02c2c6b885e5a4d9990c93"
43
+ "name": "expo.modules.camera-55.0.6.aar",
44
+ "url": "expo.modules.camera-55.0.6.aar",
45
+ "size": 247827,
46
+ "sha512": "52edba7d79334c74c7a17c171defae32841e30da027539fadc0591440ed07bb3f027f9de158d170d4943fa7a4a2aed1bb034845309037186e0b9354d9d16dd6f",
47
+ "sha256": "c5862a1aec8e9c9780077404b671a91b1c43209ba35b7b6bd2ee27881b688034",
48
+ "sha1": "9e79bb8d7f3cffdd210804da3d34f41887eee0f2",
49
+ "md5": "f2e5e5f43e46b84969ebd3d1accafcab"
50
50
  }
51
51
  ]
52
52
  },
@@ -146,13 +146,13 @@
146
146
  ],
147
147
  "files": [
148
148
  {
149
- "name": "expo.modules.camera-55.0.4.aar",
150
- "url": "expo.modules.camera-55.0.4.aar",
151
- "size": 247936,
152
- "sha512": "956eb45fdc76aaefe16b953f95ea4a8a9a91b61e4bfb1862160677b446687885d4d4b913c112c9f0132b6ebf3ca8b9c7be7cc578638a526467d04c249f913225",
153
- "sha256": "31abe6afee3cb3d63616342eb71af92a63542a47dbf92237f04c8ae4cf767c7b",
154
- "sha1": "9da5cb490f6f883c94c336f27a6d847a143c71cd",
155
- "md5": "4329b4177e02c2c6b885e5a4d9990c93"
149
+ "name": "expo.modules.camera-55.0.6.aar",
150
+ "url": "expo.modules.camera-55.0.6.aar",
151
+ "size": 247827,
152
+ "sha512": "52edba7d79334c74c7a17c171defae32841e30da027539fadc0591440ed07bb3f027f9de158d170d4943fa7a4a2aed1bb034845309037186e0b9354d9d16dd6f",
153
+ "sha256": "c5862a1aec8e9c9780077404b671a91b1c43209ba35b7b6bd2ee27881b688034",
154
+ "sha1": "9e79bb8d7f3cffdd210804da3d34f41887eee0f2",
155
+ "md5": "f2e5e5f43e46b84969ebd3d1accafcab"
156
156
  }
157
157
  ]
158
158
  },
@@ -166,8 +166,8 @@
166
166
  },
167
167
  "files": [
168
168
  {
169
- "name": "expo.modules.camera-55.0.4-sources.jar",
170
- "url": "expo.modules.camera-55.0.4-sources.jar",
169
+ "name": "expo.modules.camera-55.0.6-sources.jar",
170
+ "url": "expo.modules.camera-55.0.6-sources.jar",
171
171
  "size": 29117,
172
172
  "sha512": "588b5107a1aa960f4b443bd2fb749f14fdc77988f3bb82fc9b7300546a4c634fc33c2dfdb36aa67de582b8d48fd6818c88d1ebb3d0b9bb42785e1027393f8530",
173
173
  "sha256": "d2df99aaed8dcc4cf3fd143d77514085b3cedec8ff655efe80c7750f49d2c756",
@@ -0,0 +1 @@
1
+ e0b12e467c704eb2ac83c572ea7dc5313bb8887d80010e50cecea19ffc1d0f7e
@@ -0,0 +1 @@
1
+ e2bf1aab22c8910371425f6e09909e087b9e17adbd9f6bf83cc87b2f3f7d0f5eb706684c9e5ed5d2db22b7098d4703985a2182627fb8bc327c75a0998cab16b4
@@ -9,7 +9,7 @@
9
9
  <modelVersion>4.0.0</modelVersion>
10
10
  <groupId>host.exp.exponent</groupId>
11
11
  <artifactId>expo.modules.camera</artifactId>
12
- <version>55.0.4</version>
12
+ <version>55.0.6</version>
13
13
  <packaging>aar</packaging>
14
14
  <name>expo.modules.camera</name>
15
15
  <url>https://github.com/expo/expo</url>
@@ -0,0 +1 @@
1
+ 80ed1206e60f864e1110f4091e43adfaad861d91d4675251dbf06516f8db9278
@@ -0,0 +1 @@
1
+ dc35afa2efb112f58ed88e260595db25c86a863da16fbcf72173fb294ec17536bc76180a53e261b67a1683cf56d4a2a0531a44ffcf4effb1227a25ab60832a8c
@@ -3,11 +3,11 @@
3
3
  <groupId>host.exp.exponent</groupId>
4
4
  <artifactId>expo.modules.camera</artifactId>
5
5
  <versioning>
6
- <latest>55.0.4</latest>
7
- <release>55.0.4</release>
6
+ <latest>55.0.6</latest>
7
+ <release>55.0.6</release>
8
8
  <versions>
9
- <version>55.0.4</version>
9
+ <version>55.0.6</version>
10
10
  </versions>
11
- <lastUpdated>20260203054521</lastUpdated>
11
+ <lastUpdated>20260216170148</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- ea3df9f1c609aec94cde163c8cc6fdf4
1
+ 4d6c3b253773ba47896ad53d6cdade84
@@ -1 +1 @@
1
- b6ed907b52a8e5e331704999ef90b8c22dc7cce0
1
+ 4cb463f8124266375ad7c03d61d3a059e8390172
@@ -1 +1 @@
1
- c0d3adac94c1e75bbe360160b557a9c4b0bbd9ac517da1ab442cc96163c61425
1
+ 8db07f0eeca7da619d1dc33fc7b508fc008a8d65c72b3d98abbc6ff5fe1d512c
@@ -1 +1 @@
1
- dd888d1672c0fa9a163fadbc6af4606a977186618ba9bd6c5185e6a5099c72f42cd1ffb47b89f2e1592cb01cd368bb61b13266533df21de0d9cbc68deed1c12b
1
+ 57d76948d057b63dc0ba5bb0783e5de3279343c0cdc03737d98dcdc9fac1f75057d68c1573b2784214561a232da97932078c8bf4c25ae586d3d460fe4dcb805d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-camera",
3
- "version": "55.0.4",
3
+ "version": "55.0.6",
4
4
  "description": "A React component that renders a preview for the device's either front or back camera. Camera's parameters like zoom, auto focus, white balance and flash mode are adjustable. With expo-camera, one can also take photos and record videos that are saved to the app's cache. Morever, the component is also capable of detecting faces and bar codes appearing on the preview.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -50,5 +50,5 @@
50
50
  "optional": true
51
51
  }
52
52
  },
53
- "gitHead": "436ffb4355d5207f4a03fbc3568cd33424a40f3e"
53
+ "gitHead": "928cc951854450f3c72e00e8e420e567fabd1f8c"
54
54
  }
@@ -1 +0,0 @@
1
- 9da5cb490f6f883c94c336f27a6d847a143c71cd
@@ -1 +0,0 @@
1
- 31abe6afee3cb3d63616342eb71af92a63542a47dbf92237f04c8ae4cf767c7b
@@ -1 +0,0 @@
1
- 956eb45fdc76aaefe16b953f95ea4a8a9a91b61e4bfb1862160677b446687885d4d4b913c112c9f0132b6ebf3ca8b9c7be7cc578638a526467d04c249f913225
@@ -1 +0,0 @@
1
- e513ce84a70551ac84bfb490837f96f45b5fa92ef2da9e6b19cbd55487072a9d
@@ -1 +0,0 @@
1
- 5e4a2d488154a31e8b70a43c67254b1d60ade3182dd73a1daa64b0b7b31f780daec9b7de40a00384306df829d497b986c7f9559946da5657bafbeb58b12d7a62
@@ -1 +0,0 @@
1
- d15a9e5bb9462c89a45a237439e108bdc9c02e92
@@ -1 +0,0 @@
1
- eca6b9917eb45f409beef9e3f78535a55b25adde0251af937e87578dae78d9c8
@@ -1 +0,0 @@
1
- 7970a6f54400d6a0971d6778fc2bd2afb7b737324c4866f0de610f2ab26e24cdf49a65a6d8d1d6e4ea58861a687b3400f98304d9e3a0da8aa8c39d463273991b