0

Trying to migrate barcode scanner to Jetpack compose and updating camera and ML Kit dependencies to latest version.

The current shows the camera view correctly, but it is not scanning the barcodes.
The ImageAnalysis analyzer runs only once.

Code

@Composable
fun CameraPreview(
    data: CameraPreviewData,
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    AndroidView(
        modifier = Modifier
            .fillMaxSize(),
        factory = { AndroidViewContext ->
            PreviewView(AndroidViewContext).apply {
                this.scaleType = PreviewView.ScaleType.FILL_CENTER
                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
                // Preview is incorrectly scaled in Compose on some devices without this
                implementationMode = PreviewView.ImplementationMode.COMPATIBLE
            }
        },
        update = { previewView ->
            val cameraSelector: CameraSelector = CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build()
            val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
            val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =
                ProcessCameraProvider.getInstance(context)

            cameraProviderFuture.addListener({
                val preview: Preview = Preview.Builder()
                    .build()
                    .also {
                        // Attach the viewfinder's surface provider to preview use case
                        it.setSurfaceProvider(previewView.surfaceProvider)
                    }
                val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
                val barcodeAnalyser = BarcodeAnalyser { barcodes ->
                    barcodes.forEach { barcode ->
                        barcode.rawValue?.let { barcodeValue ->
                            logError("Barcode value detected: ${barcodeValue}.")
                            // Other handling code
                        }
                    }
                }
                val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder()
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .build()
                    .also {
                        it.setAnalyzer(cameraExecutor, barcodeAnalyser)
                    }

                try {
                    cameraProvider.unbindAll()
                    cameraProvider.bindToLifecycle(
                        lifecycleOwner,
                        cameraSelector,
                        preview,
                        imageAnalysis
                    )
                } catch (exception: Exception) {
                    logError("Use case binding failed with exception : $exception")
                }
            }, ContextCompat.getMainExecutor(context))
        },
    )
}

BarcodeAnalyser

class BarcodeAnalyser(
    private val onBarcodesDetected: (barcodes: List<Barcode>) -> Unit,
) : ImageAnalysis.Analyzer {
    private var lastAnalyzedTimestamp = 0L

    override fun analyze(
        imageProxy: ImageProxy,
    ) {
        logError("Inside analyze")
        val currentTimestamp = System.currentTimeMillis()
        if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.SECONDS.toMillis(1)) {

            imageProxy.image?.let { imageToAnalyze ->
                val options = BarcodeScannerOptions.Builder()
                    .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
                    .build()
                val barcodeScanner = BarcodeScanning.getClient(options)
                val imageToProcess =
                    InputImage.fromMediaImage(imageToAnalyze, imageProxy.imageInfo.rotationDegrees)

                barcodeScanner.process(imageToProcess)
                    .addOnSuccessListener { barcodes ->
                        if (barcodes.isNotEmpty()) {
                            logError("Scanned: $barcodes")
                            onBarcodesDetected(barcodes)
                            imageProxy.close()
                        } else {
                            logError("No barcode scanned")
                        }
                    }
                    .addOnFailureListener { exception ->
                        logError("BarcodeAnalyser: Something went wrong with exception: $exception")
                        imageProxy.close()
                    }
            }
            lastAnalyzedTimestamp = currentTimestamp
        }
    }
}

References

Abhimanyu
  • 11,351
  • 7
  • 51
  • 121
  • 1
    You are only calling `imageProxy.close()` if there was an error or a code was found. However the `addOnSuccessListener` callback is also called by the barcode scanner if everything works fine and there just wasn't any code in the frame. The analyzer will not provide new frames until you close the proxy – Adrian K Oct 18 '21 at 15:21

1 Answers1

1

Thanks to Adrian's comment.

It worked after the following changes.

In BarcodeAnalyser

  1. Removed imageProxy.close() from addOnSuccessListener and addOnFailureListener. Added it to addOnCompleteListener.
  2. Added imageProxy.close() in else condition as well.
class BarcodeAnalyser(
    private val onBarcodesDetected: (barcodes: List<Barcode>) -> Unit,
) : ImageAnalysis.Analyzer {
    private var lastAnalyzedTimestamp = 0L

    override fun analyze(
        imageProxy: ImageProxy,
    ) {
        logError("Inside analyze")
        val currentTimestamp = System.currentTimeMillis()
        if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.SECONDS.toMillis(1)) {

            imageProxy.image?.let { imageToAnalyze ->
                // ...Same code

                barcodeScanner.process(imageToProcess)
                    .addOnSuccessListener { barcodes ->
                        if (barcodes.isNotEmpty()) {
                            logError("Scanned: $barcodes")
                            onBarcodesDetected(barcodes)
                            // imageProxy.close()
                        } else {
                            logError("No barcode scanned")
                        }
                    }
                    .addOnFailureListener { exception ->
                        logError("BarcodeAnalyser: Something went wrong with exception: $exception")
                        // imageProxy.close()
                    }
                    .addOnCompleteListener {
                        imageProxy.close()
                    }
            }
            lastAnalyzedTimestamp = currentTimestamp
        } else {
            imageProxy.close()
        }
    }
}
Abhimanyu
  • 11,351
  • 7
  • 51
  • 121