Hi-Fi Audio
This cookbook demonstrates how to use ToggleHifiAudioAction in your Compose UI to allow users to toggle between standard and high-fidelity audio modes.
Overview
ToggleHifiAudioAction is a pre-built Compose component that provides a toggle button for switching between standard voice audio (AUDIO_BITRATE_PROFILE_VOICE_STANDARD_UNSPECIFIED) and high-fidelity music audio (AUDIO_BITRATE_PROFILE_MUSIC_HIGH_QUALITY).
Basic Usage
The simplest way to use ToggleHifiAudioAction is to provide the current audio bitrate profile state and handle the action:
@Composable
fun MyLobbyScreen(call: Call) {
val audioBitrateProfile by call.microphone.audioBitrateProfile.collectAsStateWithLifecycle()
val isMusicHighQuality = audioBitrateProfile == AudioBitrateProfile.AUDIO_BITRATE_PROFILE_MUSIC_HIGH_QUALITY
ToggleHifiAudioAction(
isMusicHighQuality = isMusicHighQuality,
onCallAction = { action: ToggleHifiAudio ->
// Handle the toggle action
handleHifiAudioToggle(call, action.isHifiAudioEnabled)
}
)
}Using in Lobby Screen with buildDefaultLobbyControlActions
The easiest way to add the HiFi audio toggle to your lobby screen is using the buildDefaultLobbyControlActions function:
@Composable
fun LobbyControls(
call: Call,
showHifiAudioToggle: Boolean = false,
) {
val isCameraEnabled by call.camera.isEnabled.collectAsStateWithLifecycle()
val isMicrophoneEnabled by call.microphone.isEnabled.collectAsStateWithLifecycle()
val actions = buildDefaultLobbyControlActions(
call = call,
onCallAction = { action ->
handleCallAction(call, action)
},
isCameraEnabled = isCameraEnabled,
isMicrophoneEnabled = isMicrophoneEnabled,
showHifiAudioToggle = showHifiAudioToggle,
)
// Render the actions
Row {
actions.forEach { action ->
action()
}
}
}Using in an in-call control bar
The same ToggleHifiAudioAction works after join. buildDefaultCallControlActions does not add it, so append it to that list. DefaultOnCallActionHandler already handles ToggleHifiAudio.
@Composable
fun InCallControls(
call: Call,
showHifiAudioToggle: Boolean,
) {
val audioBitrateProfile by call.microphone.audioBitrateProfile.collectAsStateWithLifecycle()
val isMusicHighQuality =
audioBitrateProfile == AudioBitrateProfile.AUDIO_BITRATE_PROFILE_MUSIC_HIGH_QUALITY
val actions = buildDefaultCallControlActions(
call = call,
onCallAction = { action ->
DefaultOnCallActionHandler.onCallAction(call, action)
},
)
Row {
actions.forEach { action ->
action()
}
if (showHifiAudioToggle) {
ToggleHifiAudioAction(
isMusicHighQuality = isMusicHighQuality,
onCallAction = { action ->
DefaultOnCallActionHandler.onCallAction(call, action)
},
)
}
}
}Checking if HiFi audio is enabled
Before showing the toggle, check if HiFi audio is enabled in the call settings:
@Composable
fun LobbyScreen(call: Call) {
val hifiAudioEnabled by call.state.settings
.map { it?.audio?.hifiAudioEnabled ?: false }
.collectAsStateWithLifecycle(initialValue = false)
// Settings is loaded by the SDK asynchronously
val settingsLoaded by call.state.settings
.map { it != null }
.collectAsStateWithLifecycle(initialValue = false)
val showHifiAudioToggle = settingsLoaded && hifiAudioEnabled
// Use showHifiAudioToggle in your UI
LobbyControls(
call = call,
showHifiAudioToggle = showHifiAudioToggle,
)
}Handling the Toggle Action
When the user taps the toggle, you need to handle the ToggleHifiAudio action. Here's how to implement the handler:
Using DefaultOnCallActionHandler
The SDK provides a default handler that you can use in the lobby or after join:
val actions = buildDefaultLobbyControlActions(
call = call,
onCallAction = { action ->
DefaultOnCallActionHandler.onCallAction(call, action)
},
showHifiAudioToggle = showHifiAudioToggle,
)Custom Handler Implementation
For more control, implement your own handler:
fun handleCallAction(call: Call, action: CallAction) {
when (action) {
is ToggleHifiAudio -> {
val newProfile = if (action.isHifiAudioEnabled) {
AudioBitrateProfile.AUDIO_BITRATE_PROFILE_MUSIC_HIGH_QUALITY
} else {
AudioBitrateProfile.AUDIO_BITRATE_PROFILE_VOICE_STANDARD_UNSPECIFIED
}
// Launch in a coroutine scope since setAudioBitrateProfile is suspend
CoroutineScope(SupervisorJob() + Dispatchers.Main).launch {
val result = call.microphone.setAudioBitrateProfile(newProfile)
if (result.isFailure) {
// Dashboard does not allow HiFi, or call settings could not be fetched
Log.e("LobbyScreen", "Failed to set audio bitrate profile: ${result.exceptionOrNull()?.message}")
}
}
}
// Handle other actions...
else -> Unit
}
}Using ViewModel (Recommended)
For better separation of concerns, handle the action in your ViewModel:
class CallLobbyViewModel(private val call: Call) : ViewModel() {
fun setAudioBitrateProfile(isHifiAudioEnabled: Boolean) {
viewModelScope.launch {
val newProfile = if (isHifiAudioEnabled) {
AudioBitrateProfile.AUDIO_BITRATE_PROFILE_MUSIC_HIGH_QUALITY
} else {
AudioBitrateProfile.AUDIO_BITRATE_PROFILE_VOICE_STANDARD_UNSPECIFIED
}
val result = call.microphone.setAudioBitrateProfile(newProfile)
if (result.isFailure) {
Log.e(
"CallLobbyViewModel",
"Failed to set audio bitrate profile: ${result.exceptionOrNull()?.message}"
)
}
}
}
}
// In your Composable
@Composable
fun LobbyScreen(viewModel: CallLobbyViewModel) {
val actions = buildDefaultLobbyControlActions(
call = viewModel.call,
onCallAction = { action ->
when (action) {
is ToggleHifiAudio -> {
viewModel.setAudioBitrateProfile(action.isHifiAudioEnabled)
}
// Handle other actions...
else -> Unit
}
},
showHifiAudioToggle = showHifiAudioToggle,
)
}Complete Example
Here's a complete example showing how to integrate ToggleHifiAudioAction in a lobby screen:
@Composable
fun CallLobbyScreen(
call: Call,
onBack: () -> Unit,
) {
// Check if HiFi audio is enabled
val hifiAudioEnabled by call.state.settings
.map { it?.audio?.hifiAudioEnabled ?: false }
.collectAsStateWithLifecycle(initialValue = false)
val settingsLoaded by call.state.settings
.map { it != null }
.collectAsStateWithLifecycle(initialValue = false)
val showHifiAudioToggle = settingsLoaded && hifiAudioEnabled
// Get device states
val isCameraEnabled by call.camera.isEnabled.collectAsStateWithLifecycle()
val isMicrophoneEnabled by call.microphone.isEnabled.collectAsStateWithLifecycle()
// Build control actions
val actions = buildDefaultLobbyControlActions(
call = call,
onCallAction = { action ->
handleCallAction(call, action)
},
isCameraEnabled = isCameraEnabled,
isMicrophoneEnabled = isMicrophoneEnabled,
showHifiAudioToggle = showHifiAudioToggle,
)
// Render UI
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
// Your lobby content here...
// Control actions row
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
actions.forEach { action ->
action()
}
}
}
}
private fun handleCallAction(call: Call, action: CallAction) {
when (action) {
is ToggleHifiAudio -> {
val newProfile = if (action.isHifiAudioEnabled) {
AudioBitrateProfile.AUDIO_BITRATE_PROFILE_MUSIC_HIGH_QUALITY
} else {
AudioBitrateProfile.AUDIO_BITRATE_PROFILE_VOICE_STANDARD_UNSPECIFIED
}
CoroutineScope(SupervisorJob() + Dispatchers.Main).launch {
call.microphone.setAudioBitrateProfile(newProfile)
}
}
// Handle other actions (ToggleMicrophone, ToggleCamera, etc.)
else -> Unit
}
}Customization
You can customize the appearance of ToggleHifiAudioAction using its styling parameters:
ToggleHifiAudioAction(
modifier = Modifier,
isMusicHighQuality = isMusicHighQuality,
enabled = true,
shape = RoundedCornerShape(8.dp),
enabledColor = Color.Blue,
disabledColor = Color.Gray,
enabledIconTint = Color.White,
disabledIconTint = Color.Black,
onStyle = VideoTheme.styles.buttonStyles.primaryIconButtonStyle(),
offStyle = VideoTheme.styles.buttonStyles.tertiaryIconButtonStyle(),
onCallAction = { action ->
handleHifiAudioToggle(call, action.isHifiAudioEnabled)
}
)Important notes
-
Before or during a call:
setAudioBitrateProfileworks in the lobby and after join. Bind the button tocall.microphone.audioBitrateProfile. That flow only moves on success, so a refused mid-call stage snaps the toggle back. -
Dashboard settings: HiFi audio must be enabled for the call type. If it is not,
setAudioBitrateProfilereturns a failure. -
Result<Unit>: Success means the profile is in force. Failure means it is not. The exception message names any live stage that refused. See High fidelity audio for what the SDK changes under the hood. -
Error handling: The
Resultfails when HiFi is not allowed on the call type, when call settings could not be fetched, or when a live stage refused. It does not fail merely because the call is already joined.