react-native-rectangle-doc-scanner 3.191.0 → 3.193.0

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.
@@ -11,6 +11,8 @@ import androidx.camera.view.PreviewView
11
11
  import androidx.core.content.ContextCompat
12
12
  import androidx.lifecycle.Lifecycle
13
13
  import androidx.lifecycle.LifecycleOwner
14
+ import androidx.lifecycle.LiveData
15
+ import androidx.lifecycle.Observer
14
16
  import java.io.File
15
17
  import java.util.concurrent.ExecutorService
16
18
  import java.util.concurrent.Executors
@@ -28,6 +30,11 @@ class CameraController(
28
30
 
29
31
  private var useFrontCamera = false
30
32
  private var torchEnabled = false
33
+ private var detectionEnabled = true
34
+ private var isCaptureSession = false
35
+ private var hasFallbackAttempted = false
36
+ private var cameraStateLiveData: LiveData<CameraState>? = null
37
+ private var cameraStateObserver: Observer<CameraState>? = null
31
38
 
32
39
  var onFrameAnalyzed: ((Rectangle?, Int, Int) -> Unit)? = null
33
40
 
@@ -51,6 +58,7 @@ class CameraController(
51
58
  Log.d(TAG, "========================================")
52
59
 
53
60
  this.useFrontCamera = useFrontCam
61
+ this.detectionEnabled = enableDetection
54
62
 
55
63
  Log.d(TAG, "[CAMERA_CONTROLLER] Getting ProcessCameraProvider instance...")
56
64
  val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
@@ -61,7 +69,9 @@ class CameraController(
61
69
  cameraProvider = cameraProviderFuture.get()
62
70
  Log.d(TAG, "[CAMERA_CONTROLLER] Got cameraProvider: $cameraProvider")
63
71
  Log.d(TAG, "[CAMERA_CONTROLLER] Calling bindCameraUseCases...")
64
- bindCameraUseCases(enableDetection)
72
+ // Bind preview + analysis only. ImageCapture is bound lazily during capture
73
+ // to avoid stream configuration timeouts on some devices.
74
+ bindCameraUseCases(enableDetection, useImageCapture = false)
65
75
  } catch (e: Exception) {
66
76
  Log.e(TAG, "[CAMERA_CONTROLLER] Failed to start camera", e)
67
77
  e.printStackTrace()
@@ -80,9 +90,10 @@ class CameraController(
80
90
  /**
81
91
  * Bind camera use cases (preview, capture, analysis)
82
92
  */
83
- private fun bindCameraUseCases(enableDetection: Boolean) {
93
+ private fun bindCameraUseCases(enableDetection: Boolean, useImageCapture: Boolean) {
84
94
  Log.d(TAG, "[BIND] bindCameraUseCases called")
85
95
  Log.d(TAG, "[BIND] enableDetection: $enableDetection")
96
+ Log.d(TAG, "[BIND] useImageCapture: $useImageCapture")
86
97
 
87
98
  val cameraProvider = cameraProvider
88
99
  if (cameraProvider == null) {
@@ -117,16 +128,20 @@ class CameraController(
117
128
  .build()
118
129
  Log.d(TAG, "[BIND] Preview created: $preview")
119
130
 
120
- // Image capture use case (high resolution for document scanning)
121
- Log.d(TAG, "[BIND] Creating ImageCapture use case...")
122
- imageCapture = ImageCapture.Builder()
123
- .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
124
- // Cap resolution to avoid camera session timeouts on lower-end devices.
125
- .setTargetResolution(Size(960, 720))
126
- .setTargetRotation(targetRotation)
127
- .setFlashMode(ImageCapture.FLASH_MODE_AUTO)
128
- .build()
129
- Log.d(TAG, "[BIND] ImageCapture created: $imageCapture")
131
+ // Image capture use case (bound only when capture is requested)
132
+ if (useImageCapture) {
133
+ Log.d(TAG, "[BIND] Creating ImageCapture use case...")
134
+ imageCapture = ImageCapture.Builder()
135
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
136
+ // Cap resolution to avoid camera session timeouts on lower-end devices.
137
+ .setTargetResolution(Size(960, 720))
138
+ .setTargetRotation(targetRotation)
139
+ .setFlashMode(ImageCapture.FLASH_MODE_AUTO)
140
+ .build()
141
+ Log.d(TAG, "[BIND] ImageCapture created: $imageCapture")
142
+ } else {
143
+ imageCapture = null
144
+ }
130
145
 
131
146
  // Image analysis use case for rectangle detection
132
147
  imageAnalysis = if (enableDetection) {
@@ -166,7 +181,10 @@ class CameraController(
166
181
  cameraProvider.unbindAll()
167
182
 
168
183
  // Bind use cases to camera
169
- val useCases = mutableListOf<UseCase>(preview, imageCapture!!)
184
+ val useCases = mutableListOf<UseCase>(preview)
185
+ if (imageCapture != null) {
186
+ useCases.add(imageCapture!!)
187
+ }
170
188
  if (imageAnalysis != null) {
171
189
  useCases.add(imageAnalysis!!)
172
190
  }
@@ -179,6 +197,7 @@ class CameraController(
179
197
  *useCases.toTypedArray()
180
198
  )
181
199
  Log.d(TAG, "[BIND] Bound to lifecycle successfully, camera: $camera")
200
+ registerCameraStateObserver(camera)
182
201
 
183
202
  // Restore torch state if it was enabled
184
203
  if (torchEnabled) {
@@ -190,12 +209,38 @@ class CameraController(
190
209
  Log.d(TAG, "[BIND] Camera started successfully!")
191
210
  Log.d(TAG, "[BIND] hasFlashUnit: ${camera?.cameraInfo?.hasFlashUnit()}")
192
211
  Log.d(TAG, "[BIND] ========================================")
212
+ isCaptureSession = useImageCapture
193
213
  } catch (e: Exception) {
194
214
  Log.e(TAG, "[BIND] Failed to bind camera use cases", e)
195
215
  e.printStackTrace()
196
216
  }
197
217
  }
198
218
 
219
+ private fun registerCameraStateObserver(camera: Camera?) {
220
+ val cam = camera ?: return
221
+ cameraStateLiveData?.let { liveData ->
222
+ cameraStateObserver?.let { liveData.removeObserver(it) }
223
+ }
224
+
225
+ val observer = Observer<CameraState> { state ->
226
+ val error = state.error
227
+ if (error != null && !hasFallbackAttempted && !isCaptureSession) {
228
+ hasFallbackAttempted = true
229
+ Log.e(TAG, "[STATE] Camera error detected (${error.code}), falling back to preview-only")
230
+ try {
231
+ cameraProvider?.unbindAll()
232
+ bindCameraUseCases(enableDetection = false, useImageCapture = false)
233
+ } catch (e: Exception) {
234
+ Log.e(TAG, "[STATE] Fallback bind failed", e)
235
+ }
236
+ }
237
+ }
238
+
239
+ cameraStateObserver = observer
240
+ cameraStateLiveData = cam.cameraInfo.cameraState
241
+ cam.cameraInfo.cameraState.observe(lifecycleOwner, observer)
242
+ }
243
+
199
244
  /**
200
245
  * Analyze frame for rectangle detection
201
246
  */
@@ -301,6 +346,24 @@ class CameraController(
301
346
  onImageCaptured: (File) -> Unit,
302
347
  onError: (Exception) -> Unit
303
348
  ) {
349
+ if (!isCaptureSession) {
350
+ val provider = cameraProvider ?: run {
351
+ onError(Exception("Camera provider not initialized"))
352
+ return
353
+ }
354
+ ContextCompat.getMainExecutor(context).execute {
355
+ try {
356
+ // Rebind with ImageCapture only for the capture to avoid stream timeouts.
357
+ provider.unbindAll()
358
+ bindCameraUseCases(enableDetection = false, useImageCapture = true)
359
+ capturePhoto(outputDirectory, onImageCaptured, onError)
360
+ } catch (e: Exception) {
361
+ onError(e)
362
+ }
363
+ }
364
+ return
365
+ }
366
+
304
367
  val imageCapture = imageCapture ?: run {
305
368
  onError(Exception("Image capture not initialized"))
306
369
  return
@@ -320,6 +383,11 @@ class CameraController(
320
383
  override fun onImageSaved(output: ImageCapture.OutputFileResults) {
321
384
  Log.d(TAG, "Photo capture succeeded: ${photoFile.absolutePath}")
322
385
  onImageCaptured(photoFile)
386
+ if (detectionEnabled) {
387
+ ContextCompat.getMainExecutor(context).execute {
388
+ bindCameraUseCases(enableDetection = true, useImageCapture = false)
389
+ }
390
+ }
323
391
  }
324
392
 
325
393
  override fun onError(exception: ImageCaptureException) {
@@ -327,7 +395,12 @@ class CameraController(
327
395
  if (exception.imageCaptureError == ImageCapture.ERROR_CAMERA_CLOSED) {
328
396
  Log.w(TAG, "Camera was closed during capture, attempting restart")
329
397
  stopCamera()
330
- startCamera(useFrontCamera, true)
398
+ startCamera(useFrontCamera, detectionEnabled)
399
+ }
400
+ if (detectionEnabled) {
401
+ ContextCompat.getMainExecutor(context).execute {
402
+ bindCameraUseCases(enableDetection = true, useImageCapture = false)
403
+ }
331
404
  }
332
405
  onError(exception)
333
406
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.191.0",
3
+ "version": "3.193.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",