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 +14 -0
- package/android/build.gradle +3 -3
- package/android/src/main/java/expo/modules/camera/CameraViewModule.kt +77 -36
- package/android/src/main/java/expo/modules/camera/ExpoCameraView.kt +18 -5
- package/ios/CameraViewModule.swift +66 -23
- package/ios/Current/BarcodeScanner.swift +2 -3
- package/ios/Current/CameraView.swift +99 -92
- package/package.json +3 -3
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
|
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'host.exp.exponent'
|
|
4
|
-
version = '16.0.
|
|
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.
|
|
17
|
+
versionName "16.0.17"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
dependencies {
|
|
22
|
-
def camerax_version = "1.4.
|
|
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 !=
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 !=
|
|
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
|
|
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 !=
|
|
186
|
+
if (view.videoQuality != it) {
|
|
161
187
|
view.videoQuality = it
|
|
162
188
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
172
|
-
|
|
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 !=
|
|
210
|
+
if (view.pictureSize != it) {
|
|
186
211
|
view.pictureSize = it
|
|
187
212
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
view.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 !=
|
|
246
|
+
if (view.mirror != it) {
|
|
208
247
|
view.mirror = it
|
|
209
|
-
return@Prop
|
|
210
248
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
view.videoEncodingBitrate
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
await updatePictureSize()
|
|
82
|
-
}
|
|
81
|
+
updatePictureSize()
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
var mode = CameraMode.picture {
|
|
87
86
|
didSet {
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
sessionQueue.async {
|
|
88
|
+
self.setCameraMode()
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
var isMuted = false {
|
|
95
94
|
didSet {
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
sessionQueue.async {
|
|
96
|
+
self.updateSessionAudioIsMuted()
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
var active = true {
|
|
103
102
|
didSet {
|
|
104
|
-
|
|
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
|
|
165
|
-
guard
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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()
|
|
216
|
+
private func updatePictureSize() {
|
|
196
217
|
#if !targetEnvironment(simulator)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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()
|
|
260
|
+
private func setCameraMode() {
|
|
240
261
|
if mode == .video {
|
|
241
262
|
if videoFileOutput == nil {
|
|
242
|
-
|
|
263
|
+
setupMovieFileCapture()
|
|
243
264
|
}
|
|
244
|
-
|
|
265
|
+
updateSessionAudioIsMuted()
|
|
245
266
|
} else {
|
|
246
|
-
|
|
267
|
+
cleanupMovieFileCapture()
|
|
247
268
|
}
|
|
248
269
|
}
|
|
249
270
|
|
|
250
|
-
private func startSession()
|
|
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
|
-
|
|
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
|
-
|
|
621
|
+
cleanupMovieFileCapture()
|
|
597
622
|
videoRecordedPromise = nil
|
|
598
623
|
isValidVideoOptions = false
|
|
599
624
|
}
|
|
600
625
|
}
|
|
601
626
|
}
|
|
602
627
|
|
|
603
|
-
func updateSessionAudioIsMuted()
|
|
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()
|
|
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()
|
|
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
|
-
|
|
663
|
-
|
|
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)
|
|
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()
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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()
|
|
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
|
-
|
|
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
|
-
|
|
784
|
-
await changePreviewOrientation()
|
|
785
|
-
}
|
|
791
|
+
changePreviewOrientation()
|
|
786
792
|
}
|
|
787
793
|
|
|
788
|
-
|
|
789
|
-
func changePreviewOrientation() async {
|
|
794
|
+
func changePreviewOrientation() {
|
|
790
795
|
// We shouldn't access the device orientation anywhere but on the main thread
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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.
|
|
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.
|
|
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": "
|
|
53
|
+
"gitHead": "c01c449a1d6e6e8690bfcc88a778b46781a59674"
|
|
54
54
|
}
|