expo-camera 16.0.15 → 16.0.17

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/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 16.0.17 — 2025-02-19
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [Android] Attempt to fix `setLinearZoom` incompatability with some devices. ([#34757](https://github.com/expo/expo/pull/34757) by [@alanjhughes](https://github.com/alanjhughes))
18
+ - [Android] Fix flash. ([#34893](https://github.com/expo/expo/pull/34893) by [@alanjhughes](https://github.com/alanjhughes))
19
+ - [iOS] Attempts to address crash that occurs more frequently on iPads. ([#34915](https://github.com/expo/expo/pull/34915) by [@alanjhughes](https://github.com/alanjhughes))
20
+
21
+ ## 16.0.16 — 2025-02-10
22
+
23
+ _This version does not introduce any user-facing changes._
24
+
13
25
  ## 16.0.15 — 2025-02-06
14
26
 
15
27
  _This version does not introduce any user-facing changes._
@@ -36,6 +48,8 @@ _This version does not introduce any user-facing changes._
36
48
  - [Android] Fix an issue where the camera image is pixelated when using the gl integration. ([#34174](https://github.com/expo/expo/pull/34174) by [@alanjhughes](https://github.com/alanjhughes))
37
49
  - [Android] Fix setting the camera preview provider. ([#34302](https://github.com/expo/expo/pull/34302) by [@alanjhughes](https://github.com/alanjhughes))
38
50
  - [iOS] Fix initial roation when used with `react-native-screens`. ([#34721](https://github.com/expo/expo/pull/34721) by [@alanjhughes](https://github.com/alanjhughes))
51
+ - [iOS] Fix performance regression. ([#34750](https://github.com/expo/expo/pull/34750) by [@alanjhughes](https://github.com/alanjhughes))
52
+ - [Android] Attempt to fix `setLinearZoom` incompatability with some devices. ([#34757](https://github.com/expo/expo/pull/34757) by [@alanjhughes](https://github.com/alanjhughes))
39
53
 
40
54
  ## 16.0.10 — 2024-12-16
41
55
 
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'host.exp.exponent'
4
- version = '16.0.15'
4
+ version = '16.0.17'
5
5
 
6
6
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
7
  apply from: expoModulesCorePlugin
@@ -14,12 +14,12 @@ android {
14
14
  namespace "expo.modules.camera"
15
15
  defaultConfig {
16
16
  versionCode 32
17
- versionName "16.0.15"
17
+ versionName "16.0.17"
18
18
  }
19
19
  }
20
20
 
21
21
  dependencies {
22
- def camerax_version = "1.4.0-beta02"
22
+ def camerax_version = "1.4.1"
23
23
 
24
24
  api "androidx.exifinterface:exifinterface:1.3.7"
25
25
  api "androidx.appcompat:appcompat:1.1.0"
@@ -115,20 +115,38 @@ class CameraViewModule : Module() {
115
115
 
116
116
  Prop("facing") { view, facing: CameraType? ->
117
117
  facing?.let {
118
- if (view.lensFacing != facing) {
118
+ if (view.lensFacing != it) {
119
119
  view.lensFacing = it
120
120
  }
121
+ } ?: run {
122
+ if (view.lensFacing != CameraType.BACK) {
123
+ view.lensFacing = CameraType.BACK
124
+ }
121
125
  }
122
126
  }
123
127
 
124
128
  Prop("flashMode") { view, flashMode: FlashMode? ->
125
129
  flashMode?.let {
126
- view.setCameraFlashMode(it)
130
+ if (view.flashMode != it) {
131
+ view.flashMode = it
132
+ }
133
+ } ?: run {
134
+ if (view.flashMode != FlashMode.OFF) {
135
+ view.flashMode = FlashMode.OFF
136
+ }
127
137
  }
128
138
  }
129
139
 
130
140
  Prop("enableTorch") { view, enabled: Boolean? ->
131
- view.enableTorch = enabled ?: false
141
+ enabled?.let {
142
+ if (view.enableTorch != it) {
143
+ view.enableTorch = it
144
+ }
145
+ } ?: run {
146
+ if (view.enableTorch) {
147
+ view.enableTorch = false
148
+ }
149
+ }
132
150
  }
133
151
 
134
152
  Prop("animateShutter") { view, animate: Boolean? ->
@@ -136,42 +154,49 @@ class CameraViewModule : Module() {
136
154
  }
137
155
 
138
156
  Prop("zoom") { view, zoom: Float? ->
139
- view.zoom = zoom ?: 0f
157
+ zoom?.let {
158
+ if (view.zoom != it) {
159
+ view.zoom = it
160
+ }
161
+ } ?: run {
162
+ if (view.zoom != 0f) {
163
+ view.zoom = 0f
164
+ }
165
+ }
140
166
  }
141
167
 
142
168
  Prop("mode") { view, mode: CameraMode? ->
143
169
  mode?.let {
144
- if (view.cameraMode != mode) {
170
+ if (view.cameraMode != it) {
145
171
  view.cameraMode = it
146
172
  }
173
+ } ?: run {
174
+ if (view.cameraMode != CameraMode.PICTURE) {
175
+ view.cameraMode = CameraMode.PICTURE
176
+ }
147
177
  }
148
178
  }
149
179
 
150
180
  Prop("mute") { view, muted: Boolean? ->
151
- muted?.let {
152
- if (it != view.mute) {
153
- view.mute = it
154
- }
155
- }
181
+ view.mute = muted ?: false
156
182
  }
157
183
 
158
184
  Prop("videoQuality") { view, quality: VideoQuality? ->
159
185
  quality?.let {
160
- if (view.videoQuality != quality) {
186
+ if (view.videoQuality != it) {
161
187
  view.videoQuality = it
162
188
  }
163
- return@Prop
164
- }
165
- if (view.videoQuality != VideoQuality.VIDEO1080P) {
166
- view.videoQuality = VideoQuality.VIDEO1080P
189
+ } ?: run {
190
+ if (view.videoQuality != VideoQuality.VIDEO1080P) {
191
+ view.videoQuality = VideoQuality.VIDEO1080P
192
+ }
167
193
  }
168
194
  }
169
195
 
170
196
  Prop("barcodeScannerSettings") { view, settings: BarcodeSettings? ->
171
- if (settings == null) {
172
- return@Prop
197
+ settings?.let {
198
+ view.setBarcodeScannerSettings(it)
173
199
  }
174
- view.setBarcodeScannerSettings(settings)
175
200
  }
176
201
 
177
202
  Prop("barcodeScannerEnabled") { view, enabled: Boolean? ->
@@ -182,45 +207,61 @@ class CameraViewModule : Module() {
182
207
 
183
208
  Prop("pictureSize") { view, pictureSize: String? ->
184
209
  pictureSize?.let {
185
- if (view.pictureSize != pictureSize) {
210
+ if (view.pictureSize != it) {
186
211
  view.pictureSize = it
187
212
  }
188
- return@Prop
189
- }
190
- if (view.pictureSize.isNotEmpty()) {
191
- view.pictureSize = ""
213
+ } ?: run {
214
+ if (view.pictureSize.isNotEmpty()) {
215
+ view.pictureSize = ""
216
+ }
192
217
  }
193
218
  }
194
219
 
195
220
  Prop("autoFocus") { view, autoFocus: FocusMode? ->
196
- view.autoFocus = autoFocus ?: FocusMode.OFF
221
+ autoFocus?.let {
222
+ if (view.autoFocus != it) {
223
+ view.autoFocus = it
224
+ }
225
+ } ?: run {
226
+ if (view.autoFocus != FocusMode.OFF) {
227
+ view.autoFocus = FocusMode.OFF
228
+ }
229
+ }
197
230
  }
198
231
 
199
232
  Prop("ratio") { view, ratio: CameraRatio? ->
200
- if (view.ratio != ratio) {
201
- view.ratio = ratio
233
+ ratio?.let {
234
+ if (view.ratio != it) {
235
+ view.ratio = it
236
+ }
237
+ } ?: run {
238
+ if (view.ratio != null) {
239
+ view.ratio = null
240
+ }
202
241
  }
203
242
  }
204
243
 
205
244
  Prop("mirror") { view, mirror: Boolean? ->
206
245
  mirror?.let {
207
- if (view.mirror != mirror) {
246
+ if (view.mirror != it) {
208
247
  view.mirror = it
209
- return@Prop
210
248
  }
211
- }
212
- if (view.mirror != false) {
213
- view.mirror = false
249
+ } ?: run {
250
+ if (view.mirror) {
251
+ view.mirror = false
252
+ }
214
253
  }
215
254
  }
216
255
 
217
256
  Prop("videoBitrate") { view, bitrate: Int? ->
218
257
  bitrate?.let {
219
- view.videoEncodingBitrate = it
220
- return@Prop
221
- }
222
- if (view.videoEncodingBitrate != null) {
223
- view.videoEncodingBitrate = null
258
+ if (view.videoEncodingBitrate != it) {
259
+ view.videoEncodingBitrate = it
260
+ }
261
+ } ?: run {
262
+ if (view.videoEncodingBitrate != null) {
263
+ view.videoEncodingBitrate = null
264
+ }
224
265
  }
225
266
  }
226
267
 
@@ -81,6 +81,8 @@ import kotlinx.coroutines.Dispatchers
81
81
  import kotlinx.coroutines.cancel
82
82
  import kotlinx.coroutines.launch
83
83
  import java.io.File
84
+ import java.lang.Float.max
85
+ import java.lang.Float.min
84
86
  import kotlin.math.roundToInt
85
87
  import kotlin.properties.Delegates
86
88
 
@@ -141,6 +143,12 @@ class ExpoCameraView(
141
143
  shouldCreateCamera = true
142
144
  }
143
145
 
146
+ var flashMode = FlashMode.OFF
147
+ set(value) {
148
+ field = value
149
+ setCameraFlashMode(value)
150
+ }
151
+
144
152
  var cameraMode: CameraMode = CameraMode.PICTURE
145
153
  set(value) {
146
154
  field = value
@@ -150,7 +158,7 @@ class ExpoCameraView(
150
158
  var zoom: Float = 0f
151
159
  set(value) {
152
160
  field = value
153
- camera?.cameraControl?.setLinearZoom(value.coerceIn(0f, 1f))
161
+ setCameraZoom(value)
154
162
  }
155
163
 
156
164
  var autoFocus: FocusMode = FocusMode.OFF
@@ -299,9 +307,7 @@ class ExpoCameraView(
299
307
  }
300
308
 
301
309
  fun setCameraFlashMode(mode: FlashMode) {
302
- if (imageCaptureUseCase?.flashMode != mode.mapToLens()) {
303
- imageCaptureUseCase?.flashMode = mode.mapToLens()
304
- }
310
+ imageCaptureUseCase?.flashMode = mode.mapToLens()
305
311
  }
306
312
 
307
313
  private fun setTorchEnabled(enabled: Boolean) {
@@ -405,6 +411,7 @@ class ExpoCameraView(
405
411
 
406
412
  imageCaptureUseCase = ImageCapture.Builder()
407
413
  .setResolutionSelector(resolutionSelector)
414
+ .setFlashMode(flashMode.mapToLens())
408
415
  .build()
409
416
 
410
417
  val videoCapture = createVideoCapture()
@@ -431,7 +438,7 @@ class ExpoCameraView(
431
438
  observeCameraState(it.cameraInfo)
432
439
  }
433
440
  // Set the previous zoom level after recreating the camera
434
- camera?.cameraControl?.setLinearZoom(zoom.coerceIn(0f, 1f))
441
+ setCameraZoom(zoom)
435
442
  this.cameraProvider = cameraProvider
436
443
  } catch (e: Exception) {
437
444
  onMountError(
@@ -544,6 +551,12 @@ class ExpoCameraView(
544
551
  }
545
552
  }
546
553
 
554
+ private fun setCameraZoom(value: Float) {
555
+ val maxZoomRatio = camera?.cameraInfo?.zoomState?.value?.maxZoomRatio ?: 1f
556
+ val targetZoomRatio = max(1f, min(maxZoomRatio, value.coerceIn(0f, 1f) * maxZoomRatio))
557
+ camera?.cameraControl?.setZoomRatio(targetZoomRatio)
558
+ }
559
+
547
560
  private fun observeCameraState(cameraInfo: CameraInfo) {
548
561
  cameraInfo.cameraState.observe(currentActivity) {
549
562
  when (it.type) {
@@ -76,27 +76,48 @@ public final class CameraViewModule: Module, ScannerResultHandler {
76
76
  if let type, view.presetCamera != type.toPosition() {
77
77
  view.presetCamera = type.toPosition()
78
78
  }
79
+ // the prop has been removed, reset to default
80
+ if type == nil && view.presetCamera != .back {
81
+ view.presetCamera = .back
82
+ }
79
83
  }
80
84
 
81
85
  Prop("flashMode") { (view, flashMode: FlashMode?) in
82
86
  if let flashMode, view.flashMode != flashMode {
83
87
  view.flashMode = flashMode
84
88
  }
89
+ if flashMode == nil && view.flashMode != .auto {
90
+ view.flashMode = .auto
91
+ }
85
92
  }
86
93
 
87
94
  Prop("enableTorch") { (view, enabled: Bool?) in
88
- view.torchEnabled = enabled ?? false
95
+ if let enabled, view.torchEnabled != enabled {
96
+ view.torchEnabled = enabled
97
+ return
98
+ }
99
+ if enabled == nil && view.torchEnabled {
100
+ view.torchEnabled = false
101
+ }
89
102
  }
90
103
 
91
104
  Prop("pictureSize") { (view, pictureSize: PictureSize?) in
92
105
  if let pictureSize, view.pictureSize != pictureSize {
93
106
  view.pictureSize = pictureSize
107
+ return
108
+ }
109
+ if pictureSize == nil && view.pictureSize != .high {
110
+ view.pictureSize = .high
94
111
  }
95
112
  }
96
113
 
97
114
  Prop("zoom") { (view, zoom: Double?) in
98
115
  if let zoom, fabs(view.zoom - zoom) > Double.ulpOfOne {
99
116
  view.zoom = zoom
117
+ return
118
+ }
119
+ if zoom == nil && view.zoom != 0 {
120
+ view.zoom = 0
100
121
  }
101
122
  }
102
123
 
@@ -104,11 +125,18 @@ public final class CameraViewModule: Module, ScannerResultHandler {
104
125
  if let mode, view.mode != mode {
105
126
  view.mode = mode
106
127
  }
128
+ if mode == nil && view.mode != .picture {
129
+ view.mode = .picture
130
+ }
107
131
  }
108
132
 
109
133
  Prop("barcodeScannerEnabled") { (view, scanBarcodes: Bool?) in
110
134
  if let scanBarcodes, view.isScanningBarcodes != scanBarcodes {
111
135
  view.isScanningBarcodes = scanBarcodes
136
+ return
137
+ }
138
+ if scanBarcodes == nil && view.isScanningBarcodes != false {
139
+ view.isScanningBarcodes = false
112
140
  }
113
141
  }
114
142
 
@@ -119,56 +147,71 @@ public final class CameraViewModule: Module, ScannerResultHandler {
119
147
  }
120
148
 
121
149
  Prop("mute") { (view, muted: Bool?) in
122
- view.isMuted = muted ?? false
150
+ if let muted, view.isMuted != muted {
151
+ view.isMuted = muted
152
+ return
153
+ }
154
+ if muted == nil && view.isMuted != false {
155
+ view.isMuted = false
156
+ }
123
157
  }
124
158
 
125
159
  Prop("animateShutter") { (view, animate: Bool?) in
160
+ // Does not call anything that immediately causes any configuration changes
161
+ // so it's fine to just set it
126
162
  view.animateShutter = animate ?? true
127
163
  }
128
164
 
129
165
  Prop("videoQuality") { (view, quality: VideoQuality?) in
130
- view.videoQuality = quality ?? .video1080p
166
+ if let quality, view.videoQuality != quality {
167
+ view.videoQuality = quality
168
+ return
169
+ }
170
+ if quality == nil && view.videoQuality != .video1080p {
171
+ view.videoQuality = .video1080p
172
+ }
131
173
  }
132
174
 
133
175
  Prop("autoFocus") { (view, focusMode: FocusMode?) in
134
- view.autoFocus = focusMode?.toAVCaptureFocusMode() ?? .continuousAutoFocus
176
+ if let focusMode, view.autoFocus != focusMode.toAVCaptureFocusMode() {
177
+ view.autoFocus = focusMode.toAVCaptureFocusMode()
178
+ return
179
+ }
180
+ if focusMode == nil && view.autoFocus != .continuousAutoFocus {
181
+ view.autoFocus = .continuousAutoFocus
182
+ }
135
183
  }
136
184
 
137
185
  Prop("responsiveOrientationWhenOrientationLocked") { (view, responsiveOrientation: Bool?) in
138
186
  if let responsiveOrientation, view.responsiveWhenOrientationLocked != responsiveOrientation {
139
187
  view.responsiveWhenOrientationLocked = responsiveOrientation
188
+ return
189
+ }
190
+ if responsiveOrientation == nil && view.responsiveWhenOrientationLocked != false {
191
+ view.responsiveWhenOrientationLocked = false
140
192
  }
141
193
  }
142
194
 
143
195
  Prop("mirror") { (view, mirror: Bool?) in
144
- if let mirror {
145
- view.mirror = mirror
146
- return
147
- }
148
- view.mirror = false
196
+ // Does not call anything that immediately causes any configuration changes
197
+ // so it's fine to just set it
198
+ view.mirror = mirror ?? false
149
199
  }
150
200
 
151
201
  Prop("active") { (view, active: Bool?) in
152
- if let active {
202
+ if let active, view.active != active {
153
203
  view.active = active
154
204
  return
155
205
  }
156
- }
157
-
158
- Prop("videoBitrate") { (view, bitrate: Int?) in
159
- if let bitrate {
160
- view.videoBitrate = bitrate
161
- return
162
- }
163
- if view.videoBitrate != nil {
164
- view.videoBitrate = nil
206
+ if active == nil && view.active != true {
207
+ view.active = true
165
208
  }
166
209
  }
167
210
 
168
- OnViewDidUpdateProps { view in
169
- Task {
170
- await view.initCamera()
171
- }
211
+ Prop("videoBitrate") { (view, bitrate: Int?) in
212
+ // Does not call anything that immediately causes any configuration changes
213
+ // so it's fine to just set it
214
+ view.videoBitrate = bitrate
172
215
  }
173
216
 
174
217
  AsyncFunction("resumePreview") { view in
@@ -108,9 +108,6 @@ actor BarcodeScanner: NSObject, BarcodeScanningResponseHandler {
108
108
  }
109
109
 
110
110
  private func addOutputs() async {
111
- session.beginConfiguration()
112
- defer { session.commitConfiguration() }
113
-
114
111
  delegate = MetaDataDelegate(
115
112
  settings: settings,
116
113
  previewLayer: previewLayer,
@@ -118,6 +115,7 @@ actor BarcodeScanner: NSObject, BarcodeScanningResponseHandler {
118
115
  zxingEnabled: zxingEnabled,
119
116
  metadataResultHandler: self)
120
117
 
118
+ session.beginConfiguration()
121
119
  if metadataOutput == nil {
122
120
  let output = AVCaptureMetadataOutput()
123
121
  output.setMetadataObjectsDelegate(delegate, queue: sessionQueue)
@@ -137,6 +135,7 @@ actor BarcodeScanner: NSObject, BarcodeScanningResponseHandler {
137
135
  videoDataOutput = output
138
136
  }
139
137
  }
138
+ session.commitConfiguration()
140
139
  }
141
140
 
142
141
  private func removeOutputs() async {
@@ -26,7 +26,6 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
26
26
  mm.gyroUpdateInterval = 0.2
27
27
  return mm
28
28
  }()
29
- private var cameraShouldInit = true
30
29
  private var isSessionPaused = false
31
30
 
32
31
  // MARK: Property Observers
@@ -39,8 +38,8 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
39
38
 
40
39
  var videoQuality: VideoQuality = .video1080p {
41
40
  didSet {
42
- Task {
43
- await updateSessionPreset(preset: videoQuality.toPreset())
41
+ sessionQueue.async {
42
+ self.updateSessionPreset(preset: self.videoQuality.toPreset())
44
43
  }
45
44
  }
46
45
  }
@@ -57,7 +56,9 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
57
56
 
58
57
  var presetCamera = AVCaptureDevice.Position.back {
59
58
  didSet {
60
- updateType()
59
+ sessionQueue.async {
60
+ self.updateDevice()
61
+ }
61
62
  }
62
63
  }
63
64
 
@@ -77,31 +78,31 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
77
78
 
78
79
  var pictureSize = PictureSize.high {
79
80
  didSet {
80
- Task {
81
- await updatePictureSize()
82
- }
81
+ updatePictureSize()
83
82
  }
84
83
  }
85
84
 
86
85
  var mode = CameraMode.picture {
87
86
  didSet {
88
- Task {
89
- await setCameraMode()
87
+ sessionQueue.async {
88
+ self.setCameraMode()
90
89
  }
91
90
  }
92
91
  }
93
92
 
94
93
  var isMuted = false {
95
94
  didSet {
96
- Task {
97
- await updateSessionAudioIsMuted()
95
+ sessionQueue.async {
96
+ self.updateSessionAudioIsMuted()
98
97
  }
99
98
  }
100
99
  }
101
100
 
102
101
  var active = true {
103
102
  didSet {
104
- updateCameraIsActive()
103
+ sessionQueue.async {
104
+ self.updateCameraIsActive()
105
+ }
105
106
  }
106
107
  }
107
108
 
@@ -154,6 +155,7 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
154
155
  name: UIDevice.orientationDidChangeNotification,
155
156
  object: nil)
156
157
  lifecycleManager?.register(self)
158
+ initializeCaptureSessionInput()
157
159
  }
158
160
 
159
161
  private func setupPreview() {
@@ -161,44 +163,63 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
161
163
  previewLayer.needsDisplayOnBoundsChange = true
162
164
  }
163
165
 
164
- func initCamera() async {
165
- guard cameraShouldInit else {
166
+ private func updateDevice() {
167
+ guard let device = ExpoCameraUtils.device(with: .video, preferring: presetCamera) else {
166
168
  return
167
169
  }
168
- cameraShouldInit = false
169
- await initializeCaptureSessionInput()
170
- }
171
170
 
172
- private func updateType() {
173
- cameraShouldInit = true
171
+ session.beginConfiguration()
172
+ defer { session.commitConfiguration() }
173
+ if let captureDeviceInput {
174
+ session.removeInput(captureDeviceInput)
175
+ }
176
+
177
+ do {
178
+ let deviceInput = try AVCaptureDeviceInput(device: device)
179
+ if session.canAddInput(deviceInput) {
180
+ session.addInput(deviceInput)
181
+ captureDeviceInput = deviceInput
182
+ updateZoom()
183
+ }
184
+ } catch {
185
+ onMountError(["message": "Camera could not be started - \(error.localizedDescription)"])
186
+ }
174
187
  }
175
188
 
176
189
  public func onAppForegrounded() {
177
- if !session.isRunning && isSessionPaused {
178
- isSessionPaused = false
179
- sessionQueue.async {
180
- self.session.startRunning()
181
- self.enableTorch()
190
+ sessionQueue.async { [weak self] in
191
+ guard let self else {
192
+ return
193
+ }
194
+ if !session.isRunning && isSessionPaused {
195
+ isSessionPaused = false
196
+ session.startRunning()
197
+ if torchEnabled {
198
+ enableTorch()
199
+ }
182
200
  }
183
201
  }
184
202
  }
185
203
 
186
204
  public func onAppBackgrounded() {
187
- if session.isRunning && !isSessionPaused {
188
- isSessionPaused = true
189
- sessionQueue.async {
190
- self.session.stopRunning()
205
+ sessionQueue.async { [weak self] in
206
+ guard let self else {
207
+ return
208
+ }
209
+ if session.isRunning && !isSessionPaused {
210
+ isSessionPaused = true
211
+ session.stopRunning()
191
212
  }
192
213
  }
193
214
  }
194
215
 
195
- private func updatePictureSize() async {
216
+ private func updatePictureSize() {
196
217
  #if !targetEnvironment(simulator)
197
- session.beginConfiguration()
198
- defer { session.commitConfiguration() }
199
- let preset = pictureSize.toCapturePreset()
200
- if session.canSetSessionPreset(preset) {
201
- session.sessionPreset = preset
218
+ sessionQueue.async {
219
+ let preset = self.pictureSize.toCapturePreset()
220
+ if self.session.canSetSessionPreset(preset) {
221
+ self.session.sessionPreset = preset
222
+ }
202
223
  }
203
224
  #endif
204
225
  }
@@ -236,18 +257,18 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
236
257
  device.unlockForConfiguration()
237
258
  }
238
259
 
239
- private func setCameraMode() async {
260
+ private func setCameraMode() {
240
261
  if mode == .video {
241
262
  if videoFileOutput == nil {
242
- await setupMovieFileCapture()
263
+ setupMovieFileCapture()
243
264
  }
244
- await updateSessionAudioIsMuted()
265
+ updateSessionAudioIsMuted()
245
266
  } else {
246
- await cleanupMovieFileCapture()
267
+ cleanupMovieFileCapture()
247
268
  }
248
269
  }
249
270
 
250
- private func startSession() async {
271
+ private func startSession() {
251
272
  #if targetEnvironment(simulator)
252
273
  return
253
274
  #else
@@ -262,17 +283,19 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
262
283
 
263
284
  let photoOutput = AVCapturePhotoOutput()
264
285
  photoOutput.isLivePhotoCaptureEnabled = false
286
+ session.beginConfiguration()
265
287
  if session.canAddOutput(photoOutput) {
266
288
  session.addOutput(photoOutput)
267
289
  self.photoOutput = photoOutput
268
290
  }
269
291
 
270
292
  session.sessionPreset = mode == .video ? pictureSize.toCapturePreset() : .photo
271
- addErrorNotification()
272
- await changePreviewOrientation()
273
-
274
- await barcodeScanner?.maybeStartBarcodeScanning()
275
293
  session.commitConfiguration()
294
+ addErrorNotification()
295
+ changePreviewOrientation()
296
+ Task.detached(priority: .userInitiated, operation: {
297
+ await self.barcodeScanner?.maybeStartBarcodeScanning()
298
+ })
276
299
  updateCameraIsActive()
277
300
  onCameraReady()
278
301
  enableTorch()
@@ -303,7 +326,9 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
303
326
  if !session.isRunning {
304
327
  session.startRunning()
305
328
  }
306
- await updateSessionAudioIsMuted()
329
+ sessionQueue.async {
330
+ self.updateSessionAudioIsMuted()
331
+ }
307
332
  onCameraReady()
308
333
  }
309
334
  }
@@ -593,14 +618,14 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
593
618
  self.videoCodecType = codecType
594
619
  } else {
595
620
  promise.reject(CameraRecordingException(options.codec?.rawValue))
596
- await cleanupMovieFileCapture()
621
+ cleanupMovieFileCapture()
597
622
  videoRecordedPromise = nil
598
623
  isValidVideoOptions = false
599
624
  }
600
625
  }
601
626
  }
602
627
 
603
- func updateSessionAudioIsMuted() async {
628
+ func updateSessionAudioIsMuted() {
604
629
  session.beginConfiguration()
605
630
  defer { session.commitConfiguration() }
606
631
 
@@ -629,21 +654,17 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
629
654
  }
630
655
  }
631
656
 
632
- func setupMovieFileCapture() async {
657
+ func setupMovieFileCapture() {
633
658
  let output = AVCaptureMovieFileOutput()
634
659
  if session.canAddOutput(output) {
635
- session.beginConfiguration()
636
- defer { session.commitConfiguration() }
637
660
  session.addOutput(output)
638
661
  videoFileOutput = output
639
662
  }
640
663
  }
641
664
 
642
- func cleanupMovieFileCapture() async {
665
+ func cleanupMovieFileCapture() {
643
666
  if let videoFileOutput {
644
667
  if session.outputs.contains(videoFileOutput) {
645
- session.beginConfiguration()
646
- defer { session.commitConfiguration() }
647
668
  session.removeOutput(videoFileOutput)
648
669
  self.videoFileOutput = nil
649
670
  }
@@ -659,8 +680,8 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
659
680
 
660
681
  public override func removeFromSuperview() {
661
682
  super.removeFromSuperview()
662
- Task {
663
- await stopSession()
683
+ sessionQueue.async {
684
+ self.stopSession()
664
685
  }
665
686
  lifecycleManager?.unregisterAppLifecycleListener(self)
666
687
  UIDevice.current.endGeneratingDeviceOrientationNotifications()
@@ -704,15 +725,11 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
704
725
  videoCodecType = nil
705
726
  }
706
727
 
707
- func setPresetCamera(presetCamera: AVCaptureDevice.Position) {
708
- self.presetCamera = presetCamera
709
- }
710
-
711
728
  func stopRecording() {
712
729
  videoFileOutput?.stopRecording()
713
730
  }
714
731
 
715
- func updateSessionPreset(preset: AVCaptureSession.Preset) async {
732
+ func updateSessionPreset(preset: AVCaptureSession.Preset) {
716
733
  #if !targetEnvironment(simulator)
717
734
  if session.canSetSessionPreset(preset) {
718
735
  if session.sessionPreset != preset {
@@ -720,36 +737,25 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
720
737
  defer { session.commitConfiguration() }
721
738
  session.sessionPreset = preset
722
739
  }
740
+ } else {
741
+ // The selected preset cannot be used on the current device so we fall back to the highest available.
742
+ if session.sessionPreset != .high {
743
+ session.beginConfiguration()
744
+ defer { session.commitConfiguration() }
745
+ session.sessionPreset = .high
746
+ }
723
747
  }
724
748
  #endif
725
749
  }
726
750
 
727
- func initializeCaptureSessionInput() async {
728
- session.beginConfiguration()
729
-
730
- guard let device = ExpoCameraUtils.device(with: .video, preferring: presetCamera) else {
731
- return
732
- }
733
-
734
- if let captureDeviceInput {
735
- session.removeInput(captureDeviceInput)
736
- }
737
-
738
- do {
739
- let deviceInput = try AVCaptureDeviceInput(device: device)
740
-
741
- if session.canAddInput(deviceInput) {
742
- session.addInput(deviceInput)
743
- captureDeviceInput = deviceInput
744
- updateZoom()
745
- }
746
- } catch {
747
- onMountError(["message": "Camera could not be started - \(error.localizedDescription)"])
751
+ func initializeCaptureSessionInput() {
752
+ sessionQueue.async {
753
+ self.updateDevice()
754
+ self.startSession()
748
755
  }
749
- await startSession()
750
756
  }
751
757
 
752
- private func stopSession() async {
758
+ private func stopSession() {
753
759
  #if targetEnvironment(simulator)
754
760
  return
755
761
  #else
@@ -761,7 +767,9 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
761
767
  for output in session.outputs {
762
768
  session.removeOutput(output)
763
769
  }
764
- await barcodeScanner?.stopBarcodeScanning()
770
+ Task {
771
+ await barcodeScanner?.stopBarcodeScanning()
772
+ }
765
773
  session.commitConfiguration()
766
774
 
767
775
  motionManager.stopAccelerometerUpdates()
@@ -780,18 +788,17 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
780
788
  }
781
789
 
782
790
  @objc func orientationChanged() {
783
- Task {
784
- await changePreviewOrientation()
785
- }
791
+ changePreviewOrientation()
786
792
  }
787
793
 
788
- @MainActor
789
- func changePreviewOrientation() async {
794
+ func changePreviewOrientation() {
790
795
  // We shouldn't access the device orientation anywhere but on the main thread
791
- let videoOrientation = ExpoCameraUtils.videoOrientation(for: deviceOrientation)
792
- if (previewLayer.connection?.isVideoOrientationSupported) == true {
793
- physicalOrientation = ExpoCameraUtils.physicalOrientation(for: deviceOrientation)
794
- previewLayer.connection?.videoOrientation = videoOrientation
796
+ Task { @MainActor in
797
+ let videoOrientation = ExpoCameraUtils.videoOrientation(for: deviceOrientation)
798
+ if (previewLayer.connection?.isVideoOrientationSupported) == true {
799
+ physicalOrientation = ExpoCameraUtils.physicalOrientation(for: deviceOrientation)
800
+ previewLayer.connection?.videoOrientation = videoOrientation
801
+ }
795
802
  }
796
803
  }
797
804
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-camera",
3
- "version": "16.0.15",
3
+ "version": "16.0.17",
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",
@@ -37,7 +37,7 @@
37
37
  "invariant": "^2.2.4"
38
38
  },
39
39
  "devDependencies": {
40
- "expo-module-scripts": "^4.0.3"
40
+ "expo-module-scripts": "^4.0.4"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "expo": "*",
@@ -50,5 +50,5 @@
50
50
  "optional": true
51
51
  }
52
52
  },
53
- "gitHead": "9442f00874e0cd738030abae80e5bdef184a2581"
53
+ "gitHead": "c01c449a1d6e6e8690bfcc88a778b46781a59674"
54
54
  }