Voice input indicator in Jetpack Compose Glimmer

Applicable XR devices
This guidance helps you build experiences for these types of XR devices.
Display Glasses

The VoiceInputIndicator is a Jetpack Compose Glimmer component that displays sound activity to the user. You can use this component to accept voice input or other audio and provide visual feedback to show that your app is capturing audio.

This component is strictly a visual element. It doesn't record audio or process microphone data on its own. Instead, it responds to an audio level provided by your app, which shows a visual representation of the audio intensity.

Figure 1. An example of the VoiceInputIndicator.

Request projected permissions

To capture audio data to drive the indicator, your app needs the Manifest.permission.RECORD_AUDIO permission.

When you build augmented experiences for display glasses, ensure that you request permissions correctly across both the device and the glasses. Use ProjectedPermissionsResultContract and ProjectedPermissionsRequestParams to prompt the user.

For more information about requesting permissions in projected environments, see Request hardware permissions.

Capture and normalize audio

The VoiceInputIndicator requires a Float between 0.0f and 1.0f to visually reflect audio intensity. Because it operates purely as a visual component and doesn't capture audio, your app must actively monitor the microphone and stream the volume to the indicator's level parameter.

While there are multiple ways to capture audio data, one approach is to use the SpeechRecognizer API. When using SpeechRecognizer, you can capture real-time volume updates using the RecognitionListener.onRmsChanged callback, which provides the audio level in decibels, rmsdB.

Because this raw value typically falls between 0 and 10, you must normalize the output to the required 0.0f to 1.0f range. You can hold this normalized level in a state variable to continuously update the indicator.

Here's an example using a RecognitionListener to update a state variable:

// Example state variables to hold the normalized level
val audioLevel = MutableStateFlow(0f)
val speechRecognizer = SpeechRecognizer.createSpeechRecognizer(context)

val listener = object : RecognitionListener {
    override fun onRmsChanged(rmsdB: Float) {
        // Normalize raw dB (~0–10) to 0.0-1.0 for the indicator
        audioLevel.value = ((rmsdB - 1f) / 9f).coerceIn(0f, 1f)
    }

    // ... Implement other required RecognitionListener methods ...
    override fun onReadyForSpeech(params: Bundle?) {}
    override fun onBeginningOfSpeech() {}
    override fun onEndOfSpeech() {}
    override fun onError(error: Int) {}
    override fun onResults(results: Bundle?) {}
    override fun onPartialResults(partialResults: Bundle?) {}
    override fun onEvent(eventType: Int, params: Bundle?) {}
    override fun onBufferReceived(buffer: ByteArray?) {}
}

// Attach the listener and start recognizing speech
speechRecognizer.setRecognitionListener(listener)

val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
    putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
}
speechRecognizer.startListening(intent)

Integrate the voice input indicator

Once your audio stream is properly normalized and exposed as state, you can pass it directly into the VoiceInputIndicator.

@Composable
fun VoiceInputExample() {
    // Collect the flow as Compose State so the UI reacts to changes
    val currentLevel by audioLevel.collectAsState()

    VoiceInputIndicator(
        // The component responds to the level provided, showing a visual representation of the audio intensity
        level = { currentLevel },
        modifier = Modifier.size(64.dp)
    )
}

Customization parameters

The VoiceInputIndicator provides a few parameters to customize its behavior and appearance:

  • level: A function returning a Float that represents the level of the voice input, ranging from 0.0 for silence to 1.0 for the loudest sound.
  • indicatorColor: The color of the indicator bars. By default, this uses the primary color from the Glimmer theme; see GlimmerTheme.colors.primary.
  • modifier: The standard Compose modifier to be applied to the indicator.

If your UI requires the indicator to sit within a visible background container, use the ContainedVoiceInputIndicator. This alternative ensures that the indicator bars are transparent, allowing the background container to show through.