## Add the dependency

The Media3 library includes a Jetpack Compose-based UI module. To use it, add the following dependency:  

### Kotlin

```kotlin
implementation("androidx.media3:media3-ui-compose:1.8.0")
```

### Groovy

```groovy
implementation "androidx.media3:media3-ui-compose:1.8.0"
```

We highly encourage you to develop your app in a Compose-first fashion or[migrate from using Views](https://developer.android.com/develop/ui/compose/migrate).

## Fully Compose demo app

While the`media3-ui-compose`library does not include out-of-the-box Composables (such as buttons, indicators, images or dialogs), you can find a[demo app written fully in Compose](https://github.com/androidx/media/tree/release/demos/compose)that avoids any interoperability solutions like wrapping[`PlayerView`](https://developer.android.com/reference/androidx/media3/ui/PlayerView)in[`AndroidView`](https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#AndroidView(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1)). The demo app utilises the UI state holder classes from`media3-ui-compose`module and makes use of the[Compose Material3](https://developer.android.com/develop/ui/compose/designsystems/material3)library.

## UI state holders

To better understand how you can use the flexibility of UI state holders versus composables, read up on how Compose[manages State](https://developer.android.com/develop/ui/compose/state).

### Button state holders

For some UI states, we make the assumption that they will most likely be consumed by button-like Composables.

|                                                             State                                                              |                                                                                                 remember\*State                                                                                                 |       Type       |
|--------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|
| [`PlayPauseButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/PlayPauseButtonState) | [`rememberPlayPauseButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/package-summary#rememberPlayPauseButtonState(androidx.media3.common.Player))                   | 2-Toggle         |
| [`PreviousButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/PreviousButtonState)   | [`rememberPreviousButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/package-summary#rememberPreviousButtonState(androidx.media3.common.Player))                     | Constant         |
| [`NextButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/NextButtonState)           | [`rememberNextButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/package-summary#rememberNextButtonState(androidx.media3.common.Player))                             | Constant         |
| [`RepeatButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/RepeatButtonState)       | [`rememberRepeatButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/package-summary#rememberRepeatButtonState(androidx.media3.common.Player,kotlin.collections.List)) | 3-Toggle         |
| [`ShuffleButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/ShuffleButtonState)     | [`rememberShuffleButtonState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/package-summary#rememberShuffleButtonState(androidx.media3.common.Player))                       | 2-Toggle         |
| [`PlaybackSpeedState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/PlaybackSpeedState)     | [`rememberPlaybackSpeedState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/package-summary#rememberPlaybackSpeedState(androidx.media3.common.Player))                       | Menu or N-Toggle |

Example usage of`PlayPauseButtonState`:  

    @Composable
    fun PlayPauseButton(player: Player, modifier: Modifier = Modifier) {
      val state = rememberPlayPauseButtonState(player)

      IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
        Icon(
          imageVector = if (state.showPlay) Icons.Default.PlayArrow else Icons.Default.Pause,
          contentDescription =
            if (state.showPlay) stringResource(R.string.playpause_button_play)
            else stringResource(R.string.playpause_button_pause),
        )
      }
    }

<br />

Note how`state`possesses no theming information, like icon to use for playing or pausing. Its only responsibility is to transform the`Player`into UI state.

You can then mix and match the buttons in the layout of your preference:  

    Row(
      modifier = modifier.fillMaxWidth(),
      horizontalArrangement = Arrangement.SpaceEvenly,
      verticalAlignment = Alignment.CenterVertically,
    ) {
      PreviousButton(player)
      PlayPauseButton(player)
      NextButton(player)
    }

<br />

### Visual output state holders

[`PresentationState`](https://developer.android.com/reference/kotlin/androidx/media3/ui/compose/state/PresentationState)holds to information for when the video output in a`PlayerSurface`can be shown or should be covered by a placeholder UI element.  

    val presentationState = rememberPresentationState(player)
    val scaledModifier = Modifier.resizeWithContentScale(ContentScale.Fit, presentationState.videoSizeDp)

    Box(modifier) {
      // Always leave PlayerSurface to be part of the Compose tree because it will be initialised in
      // the process. If this composable is guarded by some condition, it might never become visible
      // because the Player won't emit the relevant event, e.g. the first frame being ready.
      PlayerSurface(
        player = player,
        surfaceType = SURFACE_TYPE_SURFACE_VIEW,
        modifier = scaledModifier,
      )

      if (presentationState.coverSurface) {
        // Cover the surface that is being prepared with a shutter
        Box(Modifier.background(Color.Black))
      }

<br />

Here, we can use both`presentationState.videoSizeDp`to scale the Surface to the desired aspect ratio (see[ContentScale docs](https://developer.android.com/develop/ui/compose/graphics/images/customize#content-scale)for more types) and`presentationState.coverSurface`to know when the timing is not right to be showing the Surface. In this case, you can position an opaque shutter on top of the surface, which will disappear when the surface becomes ready.

## Where are Flows?

Many Android developers are familiar with using Kotlin`Flow`objects to collect ever-changing UI data. For example, you might be on the lookout for`Player.isPlaying`flow that you can`collect`in a lifecycle-aware manner. Or something like`Player.eventsFlow`to provide you with a`Flow<Player.Events>`that you can`filter`the way you want.

However, using flows for`Player`UI state has some drawbacks. One of the main concerns is the asynchronous nature of data transfer. We want to ensure as little latency as possible between a`Player.Event`and its consumption on the UI side, avoiding showing UI elements that are out-of-sync with the`Player`.

Other points include:

- A flow with all the`Player.Events`wouldn't adhere to a single responsibility principle, each consumer would have to filter out the relevant events.
- Creating a flow for each`Player.Event`will require you to combine them (with[`combine`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/combine.html)) for each UI element. There is a many-to-many mapping between a Player.Event and a UI element change. Having to use`combine`could lead the UI to potentially illegal states.

## Create custom UI states

You can add custom UI states if the existing ones don't fulfil your needs. Check out the source code of the existing state to copy the pattern. A typical UI state holder class does the following:

1. Takes in a[`Player`](https://developer.android.com/reference/androidx/media3/common/Player).
2. Subscribes to the`Player`using coroutines. See[`Player.listen`](https://developer.android.com/reference/kotlin/androidx/media3/common/Player#(androidx.media3.common.Player).listen(kotlin.Function2))for more details.
3. Responds to particular[`Player.Events`](https://developer.android.com/reference/androidx/media3/common/Player.Events)by updating its internal state.
4. Accept business-logic commands that will be transformed into an appropriate`Player`update.
5. Can be created in multiple places across the UI tree and will always maintain a consistent view of Player's state.
6. Exposes Compose`State`fields that can be consumed by a Composable to dynamically respond to changes.
7. Comes with a`remember*State`function for remembering the instance between compositions.

What happens behind the scenes:  

    class SomeButtonState(private val player: Player) {
      var isEnabled by mutableStateOf(player.isCommandAvailable(Player.COMMAND_ACTION_A))
        private set

      var someField by mutableStateOf(someFieldDefault)
        private set

      fun onClick() {
        player.actionA()
      }

      suspend fun observe() =
        player.listen { events ->
          if (
            events.containsAny(
              Player.EVENT_B_CHANGED,
              Player.EVENT_C_CHANGED,
              Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
            )
          ) {
            someField = this.someField
            isEnabled = this.isCommandAvailable(Player.COMMAND_ACTION_A)
          }
        }
    }

To react to your own`Player.Events`, you can catch them using[`Player.listen`](https://developer.android.com/reference/kotlin/androidx/media3/common/Player#(androidx.media3.common.Player).listen(kotlin.Function2))which is a`suspend fun`that lets you enter the coroutine world and indefinitely listen to`Player.Events`. Media3 implementation of various UI states helps the end developer not to concern themselves with learning about`Player.Events`.
| **Note:** `Player`acts as a single source of truth. We shouldn't change`someField`externally because it will cause a divergence between the UI view of the world and that of the`Player`. Acting upon the button (for example, with a click) is an event that comes in. We react to it by sending the appropriate action to the`Player`without updating any fields locally. We receive the update from the`Player`in the form of a`Player.Event`and that updates the UI state. The UI element, like a button, that created the UI state locally, is able to extract the relevant information to display the correct UI element (icon, shape, content description, animation, etc.). Read more about[Compose State and unidirectional data flow](https://developer.android.com/develop/ui/compose/state).