Compose Camera LogoCompose Camera
Guides

Custom Extensions

Extending camera functionality with custom control logic.

Custom Extensions

While Plugins are designed for frame analysis and data processing (like QR scanning or OCR), Extensions (CameraControlExtension) are intended for controlling camera state, hardware features, or encapsulating complex logic that interacts with the CameraController.

CameraControlExtension Interface

To create a custom extension, implement the CameraControlExtension interface:

interface CameraControlExtension {
    val id: String
    fun onAttach(controller: CameraController)
    fun onDetach()

    // Optional Lifecycle methods
    fun onCameraReady() {}
    fun onCameraReleased() {}
}

Example: Manual Focus Extension

A powerful use case for extensions is accessing low-level platform APIs. For instance, on Android, you might want to use Camera2 Interop to control focus distance manually—something the standard CameraX API doesn't expose directly.

Here is a simplified version of a ManualFocusExtension (from the Sample App):

@OptIn(ExperimentalCamera2Interop::class)
class ManualFocusExtension : CameraControlExtension {
    override val id: String = "manual-focus"

    private var controller: AndroidCameraController? = null
    private var camera2Control: Camera2CameraControl? = null

    // State exposed to UI
    private val _focusDistance = MutableStateFlow(0f)
    val focusDistance: StateFlow<Float> = _focusDistance.asStateFlow()

    override fun onAttach(controller: CameraController) {
        if (controller is AndroidCameraController) {
            this.controller = controller
        }
    }

    override fun onCameraReady() {
        val ctrl = controller ?: return

        // Access internal Camera object via reflection
        // This is a workaround as the Camera object is not exposed in the public API
        try {
            val cameraField = ctrl::class.java.getDeclaredField("camera")
            cameraField.isAccessible = true
            val camera = cameraField.get(ctrl) as? androidx.camera.core.Camera ?: return

            camera2Control = Camera2CameraControl.from(camera.cameraControl)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    fun setFocusDistance(distance: Float) {
        val control = camera2Control ?: return

        val options = CaptureRequestOptions.Builder()
            .setCaptureRequestOption(
                CaptureRequest.CONTROL_AF_MODE,
                CaptureRequest.CONTROL_AF_MODE_OFF
            )
            .setCaptureRequestOption(
                CaptureRequest.LENS_FOCUS_DISTANCE,
                distance
            )
            .build()

        control.captureRequestOptions = options
        _focusDistance.value = distance
    }

    override fun onDetach() {
        controller = null
        camera2Control = null
    }
}

Registering Extensions

Using DSL

You can register extensions easily using the extensions block in rememberCameraController.

val manualFocus = remember { ManualFocusExtension() }

val controller = rememberCameraController {
    extensions {
        +manualFocus
    }
}

Accessing Extensions

If you register the extension internally or need to access it later, you can retrieve it by ID:

val extension = controller.getExtension<ManualFocusExtension>("manual-focus")
extension?.setFocusDistance(0.5f)

Advanced Use Case: Vendor Extensions

This API is particularly useful for platform-specific features. For example, if you are building an Android-specific extension to handle a unique vendor API, you can cast the CameraController to AndroidCameraController inside onAttach and check for specific device capabilities.

On this page