Skip to main content

Audio Volume Indicator

The audio indicator gives you a visual feedback when a user is speaking. To understand who's speaking we provide call.state.dominantSpeaker and call.state.activeSpeakers. On the participant you can observe participant.speaking, participant.dominantSpeaker, participant.audioLevel and participant.audioLevels.

This example shows how to render a visual UI indicator that changes based on the audio level.

First, let's create some fake audio data for the preview and create the preview

fun fakeAudioState(): StateFlow<List<Float>> {
val audioFlow = flow {
val audioLevels = mutableListOf(0f, 0f, 0f, 0f, 0f)
while (true) {
val newValue = Random.nextFloat()
audioLevels.removeAt(0)
audioLevels.add(newValue)
emit(audioLevels.toList())
delay(300)
}
}
return audioFlow.stateIn(
scope = CoroutineScope(Dispatchers.Default),
started = SharingStarted.Eagerly,
initialValue = listOf(0f, 0f, 0f, 0f, 0f)
)
}

@Preview(showBackground = true)
@Composable
fun AudioPreview() {
val audioLevels by fakeAudioState().collectAsState()
MyApplicationTheme {
AudioVolumeIndicator(audioLevels)
}
}

Next here's a little custom audio visualization:

@Composable
fun AudioVolumeIndicator(audioState: List<Float>) {
// based on this fun blogpost: https://proandroiddev.com/jetpack-compose-tutorial-replicating-dribbble-audio-app-part-1-513ac91c02e3
val infiniteAnimation = rememberInfiniteTransition()
val animations = mutableListOf<State<Float>>()

repeat(5) {
val durationMillis = Random.nextInt(500, 1000)
animations += infiniteAnimation.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis),
repeatMode = RepeatMode.Reverse,
)
)
}

Canvas(modifier = Modifier.width(45.dp).padding(horizontal = 12.dp)) {
val canvasCenterY = 0
var startOffset = 0f
val barWidthFloat = 10f
val barMinHeight = 0f
val barMaxHeight = 150f
val gapWidthFloat = 1f

repeat(5) { index ->
val currentSize = animations[index % animations.size].value
var barHeightPercent = audioState[index] + currentSize
if (barHeightPercent > 1.0f) {
val diff = barHeightPercent - 1.0f
barHeightPercent = 1.0f - diff
}
val barHeight = barMinHeight + (barMaxHeight - barMinHeight) * barHeightPercent
drawLine(
color = Color(0xFF9CCC65),
start = Offset(startOffset, canvasCenterY - barHeight / 2),
end = Offset(startOffset, canvasCenterY + barHeight / 2),
strokeWidth = barWidthFloat,
cap = StrokeCap.Round,
)
startOffset += barWidthFloat + gapWidthFloat
}
}
}

And here's how you can use your custom volume indicator for a single video renderer by implementing labelContent composable parameter on ParticipantVideo component like the example below:

CallContent(
modifier = Modifier.background(color = VideoTheme.colors.appBackground),
videoRenderer = { modifier, call, participant, style ->
ParticipantVideo(
modifier = modifier,
call = call,
participant = participant,
style = style,
labelContent = {
val fakeAudio by fakeAudioState().collectAsState()
ParticipantLabel(
participant = participant,
soundIndicatorContent = {
AudioVolumeIndicator(fakeAudio)
}
)
}
)
},
..
)

The end result looks like this:

Audio Volume Indicator

Did you find this page helpful?