Reputation: 4307
I'm having an issue with my MlKit barcode reader, I'm reading one barcode per time, like once a barcode is read, I open a dialog and stop the camera.
The issue is that the reader is too fast and reads the barcode value twice and i would prevent it.
I've yet tried to set a variable like "isReading" and to check for it in the onBitmapPrepared()
but that had no results.
The only thing that works is to call close()
after a barcode read, but that will prevent the next barcode from being read.
Here is the Analyzer:
class BarcodeAnalyzer(overlay: ViewFinderOverlay, private val prefix: String)
: BaseAnalyzer<String>(overlay) {
private var scanner: BarcodeScanner = run {
val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_CODE_128,
Barcode.FORMAT_CODE_39,
Barcode.FORMAT_CODE_93,
Barcode.FORMAT_EAN_13,
Barcode.FORMAT_EAN_8,
Barcode.FORMAT_UPC_A,
Barcode.FORMAT_UPC_E,
Barcode.FORMAT_QR_CODE
)
.build()
BarcodeScanning.getClient(options)
}
override fun onBitmapPrepared(bitmap: Bitmap) {
val inputImage: InputImage = InputImage.fromBitmap(bitmap, 0)
val result = Tasks.await(scanner.process(inputImage))
result.firstOrNull()?.let { barcode ->
barcode.rawValue?.let { rawValue ->
if (barcode.format == Barcode.FORMAT_CODE_39) {
val code32 = Utils.code39ToCode32(rawValue, prefix)
postResult(code32)
} else {
postResult(rawValue)
}
}
}
}
override fun close() {
scanner.close()
}
}
And the usage in the fragment:
@AndroidEntryPoint
class BarcodeReaderFragment : Fragment() {
...
private val scanner: BarcodeReaderViewModel by activityViewModels()
private var camera: Camera? = null
private var cameraProvider: ProcessCameraProvider? = null
/** Blocking camera operations are performed using this executor */
private lateinit var cameraExecutor: ExecutorService
private var flashEnabled = false
private val analyzer: BarcodeAnalyzer by lazy {
BarcodeAnalyzer(binding.overlay, sharedPreferences.getString("code32Prefix", "A") ?: "A")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
// Initialize our background executor
cameraExecutor = Executors.newSingleThreadExecutor()
lifecycleScope.launch { setupUIAndObservers() }
setUp()
}
private fun setUp() {
analyzer.liveData().observe(viewLifecycleOwner) { barcode ->
beepManager.playBeepSoundAndVibrate()
Log.d("DEBUG", barcode.toString())
if (!args.singleDecode) {
scanner.setBarcode(barcode)
}
if (args.singleDecode) {
navigation.previousBackStackEntry?.savedStateHandle?.set("barcode", barcode)
navigation.popBackStack()
}
}
analyzer.errorLiveData().observe(viewLifecycleOwner) { e ->
Log.e("Analysing failed", e.message.toString())
Toast.makeText(requireContext(), "Scanner failed, reason: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
private fun bindPreview() {
lifecycle.addObserver(analyzer)
val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.")
val preview: Preview = Preview.Builder()
.setTargetResolution(Size(TARGET_PREVIEW_WIDTH, TARGET_PREVIEW_HEIGHT))
.build()
val cameraSelector: CameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
val imageAnalyzer = ImageAnalysis.Builder()
.setTargetResolution(Size(TARGET_PREVIEW_WIDTH, TARGET_PREVIEW_HEIGHT))
.setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(cameraExecutor, analyzer)
}
try {
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
camera =
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalyzer)
preview.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
if (camera?.cameraInfo?.hasFlashUnit() == true) {
lifecycleScope.launch {
binding.ivFlashControl.visibility = View.VISIBLE
}
binding.ivFlashControl.setOnClickListener {
camera?.cameraControl?.enableTorch(!flashEnabled)
}
camera?.cameraInfo?.torchState?.observe(viewLifecycleOwner) {
it?.let { torchState ->
if (torchState == TorchState.ON) {
flashEnabled = true
binding.ivFlashControl.isSelected = true
} else {
flashEnabled = false
binding.ivFlashControl.isSelected = false
}
}
}
}
}catch (e: Exception) {
e.printStackTrace()
}
}
private fun observeScannerStatus() {
scanner.isCodeReaderEnabled.observe(viewLifecycleOwner) { isEnabled ->
if (isEnabled) {
lifecycleScope.launch {
bindPreview()
}
} else {
lifecycleScope.launch {
cameraProvider?.unbindAll()
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
// Nullify camera when view is destroyed
camera = null
// Shut down our background executor
cameraExecutor.shutdown()
cameraProvider?.unbindAll()
}
...
}
The scanner state is managed by the observeScannerStatus()
, while in setUp() i get the value from the read barcode and here if the barcode is scanned, the value is got twice.
EDIT:
One of the solutions I've found is to create a public variable in BarcodeAnalyzer
called isEnabled
and to set it to false once the barcode is read, then in observeScannerStatus
I'm setting it to true.
But it seems a bit messy as solution.
Upvotes: 0
Views: 562
Reputation: 406
I don't know how to code it in Kotlin, but here is the solution I implemented in Java. Keep in mind that the variable barcodes is a list of Barcode!
public void analyze(@NonNull ImageProxy imageProxy) {
Image mediaImage = imageProxy.getImage();
int rotation = imageProxy.getImageInfo().getRotationDegrees();
if (mediaImage != null) {
InputImage image =
InputImage.fromMediaImage(mediaImage, rotation);
scanner.process(image)
.addOnSuccessListener(barcodes -> {
if (!barcodes.isEmpty()) {
Barcode barcode = barcodes.get(0);
processBarcodeResult(barcode, imageProxy, barcodes.size());
}
})
.addOnCompleteListener(results -> imageProxy.close());
}
}
Upvotes: 2