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()
        }
    }
}

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:


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) {
                    // Handle error - HiFi audio may not be enabled in dashboard settings
                    // or the call may already be joined
                    Log.e("LobbyScreen", "Failed to set audio bitrate profile: ${result.exceptionOrNull()?.message}")
                }
            }
        }
        // Handle other actions...
        else -> Unit
    }
}

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

  1. Before Joining: The audio bitrate profile must be set before joining the call. Once joined, changes will be ignored.

  2. Dashboard Settings: HiFi audio must be enabled in the dashboard settings for the call type. If not enabled, setAudioBitrateProfile will return a failure result.

  3. State Management: Always observe the call.microphone.audioBitrateProfile StateFlow to keep your UI in sync with the actual audio profile.

  4. Error Handling: Always check the result of setAudioBitrateProfile and handle failures appropriately, as the operation may fail if:

    • The call is already joined
    • HiFi audio is not enabled in dashboard settings
© Getstream.io, Inc. All Rights Reserved.