Only bind camera use cases that the device supports.

The new camera implementation always bound all four CameraX use cases
(preview, image capture, video capture, and image analysis) regardless
of device capabilities. On devices with LEGACY camera hardware level,
this causes image capture to fail with "Capture request failed with
reason ERROR" because the hardware cannot handle that many simultaneous
use cases.

This change makes video capture and QR scanning use case binding
conditional based on CameraXModePolicy, which already determines device
capabilities. Video capture is only bound when the device supports mixed
mode (image + video simultaneously). QR scanning analysis is only bound
when explicitly requested.
This commit is contained in:
Greyson Parrelli
2026-02-25 15:29:32 +00:00
committed by Cody Henthorne
parent a2057e20d2
commit c37bb96aab
4 changed files with 46 additions and 26 deletions

View File

@@ -76,6 +76,8 @@ fun CameraScreen(
modifier: Modifier = Modifier,
roundCorners: Boolean = true,
contentAlignment: Alignment = Alignment.Center,
enableVideoCapture: Boolean = true,
enableQrScanning: Boolean = false,
content: @Composable BoxScope.() -> Unit = {}
) {
val context = LocalContext.current
@@ -103,7 +105,9 @@ fun CameraScreen(
lifecycleOwner = lifecycleOwner,
cameraProvider = cameraProvider,
surfaceProvider = surfaceProvider,
context = context
context = context,
enableVideoCapture = enableVideoCapture,
enableQrScanning = enableQrScanning
)
)
}

View File

@@ -8,12 +8,14 @@ import androidx.lifecycle.LifecycleOwner
sealed interface CameraScreenEvents {
/** Binds a camera to a sruface provider. */
/** Binds a camera to a surface provider. */
data class BindCamera(
val lifecycleOwner: LifecycleOwner,
val cameraProvider: ProcessCameraProvider,
val surfaceProvider: Preview.SurfaceProvider,
val context: Context
val context: Context,
val enableVideoCapture: Boolean = true,
val enableQrScanning: Boolean = false
) : CameraScreenEvents
/** Focuses the camera on a point. */

View File

@@ -344,27 +344,35 @@ class CameraScreenViewModel : ViewModel() {
.setResolutionSelector(resolutionSelector)
.build()
// Video capture (16:9 is default for video)
val recorder = Recorder.Builder()
.setAspectRatio(AspectRatio.RATIO_16_9)
.setQualitySelector(
androidx.camera.video.QualitySelector.from(
androidx.camera.video.Quality.HIGHEST,
androidx.camera.video.FallbackStrategy.higherQualityOrLowerThan(androidx.camera.video.Quality.HD)
)
)
.build()
val videoCaptureUseCase = VideoCapture.withOutput(recorder)
// Build the list of use cases based on device capabilities
val useCases = mutableListOf<androidx.camera.core.UseCase>(preview, imageCaptureUseCase)
// Image analysis for QR code detection
val imageAnalysisUseCase = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(imageAnalysisExecutor) { imageProxy ->
processImageForQrCode(imageProxy)
var videoCaptureUseCase: VideoCapture<Recorder>? = null
if (event.enableVideoCapture) {
val recorder = Recorder.Builder()
.setAspectRatio(AspectRatio.RATIO_16_9)
.setQualitySelector(
androidx.camera.video.QualitySelector.from(
androidx.camera.video.Quality.HIGHEST,
androidx.camera.video.FallbackStrategy.higherQualityOrLowerThan(androidx.camera.video.Quality.HD)
)
)
.build()
videoCaptureUseCase = VideoCapture.withOutput(recorder)
useCases += videoCaptureUseCase
}
if (event.enableQrScanning) {
val imageAnalysisUseCase = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(imageAnalysisExecutor) { imageProxy ->
processImageForQrCode(imageProxy)
}
}
}
useCases += imageAnalysisUseCase
}
// Select camera based on lensFacing
val cameraSelector = CameraSelector.Builder()
@@ -379,10 +387,7 @@ class CameraScreenViewModel : ViewModel() {
camera = event.cameraProvider.bindToLifecycle(
event.lifecycleOwner,
cameraSelector,
preview,
imageCaptureUseCase,
videoCaptureUseCase,
imageAnalysisUseCase
*useCases.toTypedArray()
)
lifecycleOwner = event.lifecycleOwner