expo-camera 16.0.1 → 16.0.3

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,16 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 16.0.3 — 2024-10-28
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [iOS] Fix build issue with after switch to Swift concurrency. ([#32379](https://github.com/expo/expo/pull/32379) by [@Eingin](https://github.com/Eingin))
18
+
19
+ ## 16.0.2 — 2024-10-25
20
+
21
+ _This version does not introduce any user-facing changes._
22
+
13
23
  ## 16.0.1 — 2024-10-24
14
24
 
15
25
  _This version does not introduce any user-facing changes._
@@ -33,6 +43,8 @@ _This version does not introduce any user-facing changes._
33
43
  - Fixed issue regarding using the "back"-facing on mobile web browswer. ([#30811](https://github.com/expo/expo/pull/30811) by [@entiendoNull](https://github.com/entiendoNull))
34
44
  - Fix `takePictureAsync` `quality` option when set to 0. ([#31587](https://github.com/expo/expo/pull/31587) by [@davidavz](https://github.com/davidavz))
35
45
  - [iOS] Fix crash related to `sublayers` on 0.75 and above on the new architecture. ([#32194](https://github.com/expo/expo/pull/32194) by [@alanjhughes](https://github.com/alanjhughes))
46
+ - [iOS] Fix regression in running the cameras cleanup function. ([#32333](https://github.com/expo/expo/pull/32333) by [@alanjhughes](https://github.com/alanjhughes))
47
+ - [iOS] Use an `Actor` to ensure correct order of changes to the barcode scanners outputs. ([#32353](https://github.com/expo/expo/pull/32353) by [@alanjhughes](https://github.com/alanjhughes))
36
48
 
37
49
  ### 💡 Others
38
50
 
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'host.exp.exponent'
4
- version = '16.0.1'
4
+ version = '16.0.3'
5
5
 
6
6
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
7
  apply from: expoModulesCorePlugin
@@ -14,7 +14,7 @@ android {
14
14
  namespace "expo.modules.camera"
15
15
  defaultConfig {
16
16
  versionCode 32
17
- versionName "16.0.1"
17
+ versionName "16.0.3"
18
18
  }
19
19
  }
20
20
 
@@ -155,7 +155,6 @@ public final class CameraViewModule: Module, ScannerResultHandler {
155
155
  view.active = active
156
156
  return
157
157
  }
158
- view.active = true
159
158
  }
160
159
 
161
160
  OnViewDidUpdateProps { view in
@@ -3,8 +3,8 @@ import AVFoundation
3
3
 
4
4
  let BARCODE_TYPES_KEY = "barcodeTypes"
5
5
 
6
- class BarcodeScanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate {
7
- var onBarcodeScanned: (([String: Any]?) -> Void)?
6
+ actor BarcodeScanner: NSObject, BarcodeScanningResponseHandler {
7
+ private var onBarcodeScanned: (([String: Any]?) -> Void)?
8
8
  var isScanningBarcodes = false
9
9
 
10
10
  // MARK: - Properties
@@ -21,8 +21,8 @@ class BarcodeScanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptur
21
21
  AVMetadataObject.ObjectType.code39: ZXCode39Reader()
22
22
  ]
23
23
  private var previewLayer: AVCaptureVideoPreviewLayer?
24
- private var zxingFPSProcessed = 6.0
25
24
  private var zxingEnabled = true
25
+ private var delegate: MetatDataDelegate?
26
26
 
27
27
  init(session: AVCaptureSession, sessionQueue: DispatchQueue) {
28
28
  self.session = session
@@ -42,7 +42,7 @@ class BarcodeScanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptur
42
42
  let zxingCoveredTypes = Set(zxingBarcodeReaders.keys)
43
43
  zxingEnabled = !zxingCoveredTypes.isDisjoint(with: newTypes)
44
44
  Task {
45
- await self.maybeStartBarcodeScanning()
45
+ await maybeStartBarcodeScanning()
46
46
  }
47
47
  }
48
48
  }
@@ -52,23 +52,21 @@ class BarcodeScanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptur
52
52
  self.previewLayer = layer
53
53
  }
54
54
 
55
- func setIsEnabled(_ enabled: Bool) {
55
+ func setIsEnabled(_ enabled: Bool) async {
56
56
  guard isScanningBarcodes != enabled else {
57
57
  return
58
58
  }
59
59
 
60
60
  isScanningBarcodes = enabled
61
- Task {
62
- if isScanningBarcodes {
63
- if metadataOutput != nil {
64
- setConnection(enabled: true)
65
- } else {
66
- await maybeStartBarcodeScanning()
67
- }
61
+ if isScanningBarcodes {
62
+ if metadataOutput != nil {
63
+ setConnection(enabled: true)
68
64
  } else {
69
- setConnection(enabled: false)
70
- await stopBarcodeScanning()
65
+ await maybeStartBarcodeScanning()
71
66
  }
67
+ } else {
68
+ setConnection(enabled: false)
69
+ await stopBarcodeScanning()
72
70
  }
73
71
  }
74
72
 
@@ -78,6 +76,10 @@ class BarcodeScanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptur
78
76
  }
79
77
  }
80
78
 
79
+ func setOnBarcodeScanned(_ onBarcodeScanned: @escaping ([String: Any]?) -> Void) {
80
+ self.onBarcodeScanned = onBarcodeScanned
81
+ }
82
+
81
83
  func maybeStartBarcodeScanning() async {
82
84
  guard isScanningBarcodes else {
83
85
  return
@@ -105,43 +107,20 @@ class BarcodeScanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptur
105
107
  }
106
108
  }
107
109
 
108
- func scanBarcodes(from image: CGImage, completion: @escaping (ZXResult) -> Void) {
109
- let source = ZXCGImageLuminanceSource(cgImage: image)
110
- let binarizer = ZXHybridBinarizer(source: source)
111
- let bitmap = ZXBinaryBitmap(binarizer: binarizer)
112
-
113
- var result: ZXResult?
114
-
115
- for reader in zxingBarcodeReaders.values {
116
- result = try? reader.decode(bitmap, hints: nil)
117
- if result != nil {
118
- break
119
- }
120
- }
121
-
122
- if result == nil && bitmap?.rotateSupported == true {
123
- if let rotatedBitmap = bitmap?.rotateCounterClockwise() {
124
- for reader in zxingBarcodeReaders.values {
125
- result = try? reader.decode(rotatedBitmap, hints: nil)
126
- if result != nil {
127
- break
128
- }
129
- }
130
- }
131
- }
132
-
133
- if let result {
134
- completion(result)
135
- }
136
- }
137
-
138
110
  private func addOutputs() async {
139
111
  session.beginConfiguration()
140
112
  defer { session.commitConfiguration() }
141
113
 
114
+ delegate = MetatDataDelegate(
115
+ settings: settings,
116
+ previewLayer: previewLayer,
117
+ zxingBarcodeReaders: zxingBarcodeReaders,
118
+ zxingEnabled: zxingEnabled,
119
+ metadataResultHandler: self)
120
+
142
121
  if metadataOutput == nil {
143
122
  let output = AVCaptureMetadataOutput()
144
- output.setMetadataObjectsDelegate(self, queue: sessionQueue)
123
+ output.setMetadataObjectsDelegate(delegate, queue: sessionQueue)
145
124
  if session.canAddOutput(output) {
146
125
  session.addOutput(output)
147
126
  metadataOutput = output
@@ -152,7 +131,7 @@ class BarcodeScanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptur
152
131
  let output = AVCaptureVideoDataOutput()
153
132
  output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
154
133
  output.alwaysDiscardsLateVideoFrames = true
155
- output.setSampleBufferDelegate(self, queue: zxingCaptureQueue)
134
+ output.setSampleBufferDelegate(delegate, queue: zxingCaptureQueue)
156
135
  if session.canAddOutput(output) {
157
136
  session.addOutput(output)
158
137
  videoDataOutput = output
@@ -179,55 +158,7 @@ class BarcodeScanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptur
179
158
  }
180
159
  }
181
160
 
182
- func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
183
- guard let settings = settings[BARCODE_TYPES_KEY], let metadataOutput else {
184
- return
185
- }
186
-
187
- for metadata in metadataObjects {
188
- var codeMetadata = metadata as? AVMetadataMachineReadableCodeObject
189
- if let previewLayer {
190
- codeMetadata = previewLayer.transformedMetadataObject(for: metadata) as? AVMetadataMachineReadableCodeObject
191
- }
192
-
193
- for barcodeType in settings {
194
- if zxingBarcodeReaders[barcodeType] != nil {
195
- continue
196
- }
197
-
198
- if let codeMetadata {
199
- if codeMetadata.stringValue != nil && codeMetadata.type == barcodeType {
200
- onBarcodeScanned?(BarcodeScannerUtils.avMetadataCodeObjectToDictionary(codeMetadata))
201
- }
202
- }
203
- }
204
- }
205
- }
206
-
207
- func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
208
- guard let barcodeTypes = settings[BARCODE_TYPES_KEY],
209
- let metadataOutput,
210
- zxingEnabled else {
211
- return
212
- }
213
-
214
- let kMinMargin = 1.0 / zxingFPSProcessed
215
- let presentTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
216
-
217
- var curFrameTimeStamp = 0.0
218
- var lastFrameTimeStamp = 0.0
219
-
220
- curFrameTimeStamp = Double(presentTimeStamp.value) / Double(presentTimeStamp.timescale)
221
-
222
- if curFrameTimeStamp - lastFrameTimeStamp > Double(kMinMargin) {
223
- lastFrameTimeStamp = curFrameTimeStamp
224
-
225
- if let videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer),
226
- let videoFrameImage = ZXCGImageLuminanceSource.createImage(from: videoFrame) {
227
- self.scanBarcodes(from: videoFrameImage) { barcodeScannerResult in
228
- self.onBarcodeScanned?(BarcodeScannerUtils.zxResultToDictionary(barcodeScannerResult))
229
- }
230
- }
231
- }
161
+ func onScanningResult(_ result: [String: Any]) {
162
+ self.onBarcodeScanned?(result)
232
163
  }
233
164
  }
@@ -49,7 +49,9 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
49
49
 
50
50
  var isScanningBarcodes = false {
51
51
  didSet {
52
- barcodeScanner.setIsEnabled(isScanningBarcodes)
52
+ Task {
53
+ await barcodeScanner.setIsEnabled(isScanningBarcodes)
54
+ }
53
55
  }
54
56
  }
55
57
 
@@ -145,7 +147,7 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
145
147
  UIDevice.current.beginGeneratingDeviceOrientationNotifications()
146
148
  NotificationCenter.default.addObserver(
147
149
  self,
148
- selector: #selector(orientationChanged(notification:)),
150
+ selector: #selector(orientationChanged),
149
151
  name: UIDevice.orientationDidChangeNotification,
150
152
  object: nil)
151
153
  lifecycleManager?.register(self)
@@ -303,7 +305,9 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
303
305
  }
304
306
 
305
307
  func setBarcodeScannerSettings(settings: BarcodeSettings) {
306
- barcodeScanner.setSettings([BARCODE_TYPES_KEY: settings.toMetadataObjectType()])
308
+ Task {
309
+ await barcodeScanner.setSettings([BARCODE_TYPES_KEY: settings.toMetadataObjectType()])
310
+ }
307
311
  }
308
312
 
309
313
  func updateResponsiveOrientation() {
@@ -592,12 +596,6 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
592
596
  }
593
597
  }
594
598
 
595
- public override func didMoveToWindow() {
596
- if window == nil {
597
- cleanupCamera()
598
- }
599
- }
600
-
601
599
  func updateSessionAudioIsMuted() async {
602
600
  session.beginConfiguration()
603
601
  defer { session.commitConfiguration() }
@@ -655,12 +653,12 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
655
653
  self.layer.insertSublayer(previewLayer, at: 0)
656
654
  }
657
655
 
658
- private func cleanupCamera() {
659
- cameraShouldInit = true
660
- lifecycleManager?.unregisterAppLifecycleListener(self)
656
+ public override func removeFromSuperview() {
657
+ super.removeFromSuperview()
661
658
  Task {
662
659
  await stopSession()
663
660
  }
661
+ lifecycleManager?.unregisterAppLifecycleListener(self)
664
662
  UIDevice.current.endGeneratingDeviceOrientationNotifications()
665
663
  NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
666
664
  }
@@ -751,8 +749,6 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
751
749
  #if targetEnvironment(simulator)
752
750
  return
753
751
  #endif
754
- // self.previewLayer.layer.removeFromSuperlayer()
755
-
756
752
  session.beginConfiguration()
757
753
  for input in self.session.inputs {
758
754
  session.removeInput(input)
@@ -778,8 +774,10 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
778
774
  previewLayer.connection?.isEnabled = false
779
775
  }
780
776
 
781
- @objc func orientationChanged(notification: Notification) async {
782
- await changePreviewOrientation()
777
+ @objc func orientationChanged() {
778
+ Task {
779
+ await changePreviewOrientation()
780
+ }
783
781
  }
784
782
 
785
783
  @MainActor
@@ -794,13 +792,16 @@ public class CameraView: ExpoView, EXAppLifecycleListener,
794
792
 
795
793
  private func createBarcodeScanner() -> BarcodeScanner {
796
794
  let scanner = BarcodeScanner(session: session, sessionQueue: sessionQueue)
797
- scanner.setPreviewLayer(layer: previewLayer)
798
- scanner.onBarcodeScanned = { [weak self] body in
799
- guard let self else {
800
- return
801
- }
802
- if let body {
803
- self.onBarcodeScanned(body)
795
+
796
+ Task {
797
+ await scanner.setPreviewLayer(layer: previewLayer)
798
+ await scanner.setOnBarcodeScanned { [weak self] body in
799
+ guard let self else {
800
+ return
801
+ }
802
+ if let body {
803
+ self.onBarcodeScanned(body)
804
+ }
804
805
  }
805
806
  }
806
807
 
@@ -0,0 +1,112 @@
1
+ import ZXingObjC
2
+
3
+ protocol BarcodeScanningResponseHandler {
4
+ func onScanningResult(_ result: [String: Any]) async
5
+ }
6
+
7
+ class MetatDataDelegate: NSObject, AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate {
8
+ private var settings: [String: [AVMetadataObject.ObjectType]]
9
+ private var previewLayer: AVCaptureVideoPreviewLayer?
10
+ private var zxingBarcodeReaders: [AVMetadataObject.ObjectType: ZXReader]
11
+ private var zxingEnabled = true
12
+ private var zxingFPSProcessed = 6.0
13
+ private let responseHandler: BarcodeScanningResponseHandler
14
+
15
+ init(
16
+ settings: [String: [AVMetadataObject.ObjectType]],
17
+ previewLayer: AVCaptureVideoPreviewLayer?,
18
+ zxingBarcodeReaders: [AVMetadataObject.ObjectType: ZXReader],
19
+ zxingEnabled: Bool,
20
+ metadataResultHandler: BarcodeScanningResponseHandler
21
+ ) {
22
+ self.settings = settings
23
+ self.previewLayer = previewLayer
24
+ self.zxingEnabled = zxingEnabled
25
+ self.responseHandler = metadataResultHandler
26
+ self.zxingBarcodeReaders = zxingBarcodeReaders
27
+ }
28
+
29
+ func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
30
+ guard let settings = settings[BARCODE_TYPES_KEY] else {
31
+ return
32
+ }
33
+
34
+ for metadata in metadataObjects {
35
+ var codeMetadata = metadata as? AVMetadataMachineReadableCodeObject
36
+ if let previewLayer {
37
+ codeMetadata = previewLayer.transformedMetadataObject(for: metadata) as? AVMetadataMachineReadableCodeObject
38
+ }
39
+
40
+ for barcodeType in settings {
41
+ if zxingBarcodeReaders[barcodeType] != nil {
42
+ continue
43
+ }
44
+
45
+ if let codeMetadata {
46
+ if codeMetadata.stringValue != nil && codeMetadata.type == barcodeType {
47
+ Task {
48
+ await responseHandler.onScanningResult(BarcodeScannerUtils.avMetadataCodeObjectToDictionary(codeMetadata))
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
57
+ guard zxingEnabled else {
58
+ return
59
+ }
60
+
61
+ let kMinMargin = 1.0 / zxingFPSProcessed
62
+ let presentTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
63
+
64
+ var curFrameTimeStamp = 0.0
65
+ var lastFrameTimeStamp = 0.0
66
+
67
+ curFrameTimeStamp = Double(presentTimeStamp.value) / Double(presentTimeStamp.timescale)
68
+
69
+ if curFrameTimeStamp - lastFrameTimeStamp > Double(kMinMargin) {
70
+ lastFrameTimeStamp = curFrameTimeStamp
71
+
72
+ if let videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer),
73
+ let videoFrameImage = ZXCGImageLuminanceSource.createImage(from: videoFrame) {
74
+ self.scanBarcodes(from: videoFrameImage) { barcodeScannerResult in
75
+ Task {
76
+ await self.responseHandler.onScanningResult(BarcodeScannerUtils.zxResultToDictionary(barcodeScannerResult))
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ func scanBarcodes(from image: CGImage, completion: @escaping (ZXResult) -> Void) {
84
+ let source = ZXCGImageLuminanceSource(cgImage: image)
85
+ let binarizer = ZXHybridBinarizer(source: source)
86
+ let bitmap = ZXBinaryBitmap(binarizer: binarizer)
87
+
88
+ var result: ZXResult?
89
+
90
+ for reader in zxingBarcodeReaders.values {
91
+ result = try? reader.decode(bitmap, hints: nil)
92
+ if result != nil {
93
+ break
94
+ }
95
+ }
96
+
97
+ if result == nil && bitmap?.rotateSupported == true {
98
+ if let rotatedBitmap = bitmap?.rotateCounterClockwise() {
99
+ for reader in zxingBarcodeReaders.values {
100
+ result = try? reader.decode(rotatedBitmap, hints: nil)
101
+ if result != nil {
102
+ break
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ if let result {
109
+ completion(result)
110
+ }
111
+ }
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-camera",
3
- "version": "16.0.1",
3
+ "version": "16.0.3",
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": "2e4f18d41da033c5ced0a4a045d91cf5250016b7"
53
+ "gitHead": "fc8a1408cab82e003dd2424db2994d7dea958067"
54
54
  }