Добавить зависимость
Библиотека Media3 включает модуль пользовательского интерфейса на базе Jetpack Compose. Чтобы использовать его, добавьте следующую зависимость:
Котлин
implementation("androidx.media3:media3-ui-compose:1.8.0")
Круто
implementation "androidx.media3:media3-ui-compose:1.8.0"
Мы настоятельно рекомендуем вам разрабатывать свое приложение в стиле Compose-first или отказаться от использования Views .
Полностью демо-приложение Compose
 Хотя библиотека media3-ui-compose не включает готовые элементы Composable (такие как кнопки, индикаторы, изображения или диалоговые окна), вы можете найти демонстрационное приложение, полностью написанное на Compose , которое не использует решения для обеспечения взаимодействия, такие как обёртывание PlayerView в AndroidView . Демонстрационное приложение использует классы-держатели состояний пользовательского интерфейса из модуля media3-ui-compose и библиотеку Compose Material3 .
держатели состояния UI
Чтобы лучше понять, как можно использовать гибкость держателей состояний пользовательского интерфейса по сравнению с компонуемыми элементами, ознакомьтесь с тем, как Compose управляет State .
Держатели состояния кнопки
Для некоторых состояний пользовательского интерфейса мы предполагаем, что они, скорее всего, будут использоваться компонуемыми элементами в виде кнопок.
| Состояние | помните*государство | Тип | 
|---|---|---|
| PlayPauseButtonState | rememberPlayPauseButtonState | 2-Переключить | 
| PreviousButtonState | rememberPreviousButtonState | Постоянный | 
| NextButtonState | rememberNextButtonState | Постоянный | 
| RepeatButtonState | rememberRepeatButtonState | 3-Переключатель | 
| ShuffleButtonState | rememberShuffleButtonState | 2-Переключить | 
| PlaybackSpeedState | rememberPlaybackSpeedState | Меню или N-Toggle | 
 Пример использования 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),
    )
  }
}
 Обратите внимание, что state не содержит информации о теме, например, значка для воспроизведения или паузы. Его единственная задача — преобразовать Player в состояние пользовательского интерфейса.
Затем вы можете комбинировать кнопки в макете по своему усмотрению:
Row(
  modifier = modifier.fillMaxWidth(),
  horizontalArrangement = Arrangement.SpaceEvenly,
  verticalAlignment = Alignment.CenterVertically,
) {
  PreviousButton(player)
  PlayPauseButton(player)
  NextButton(player)
}
Визуальные держатели выходных состояний
 PresentationState хранит информацию о том, когда видеовыход в PlayerSurface может быть отображен или должен быть перекрыт элементом-заполнителем пользовательского интерфейса.
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))
  }
 Здесь мы можем использовать как presentationState.videoSizeDp для масштабирования Surface до нужного соотношения сторон (см. документацию ContentScale для получения дополнительных типов), так и presentationState.coverSurface для определения момента, когда отображение Surface нежелательно. В этом случае можно разместить поверх Surface непрозрачную шторку, которая исчезнет, когда поверхность будет готова.
Где находятся потоки?
 Многие разработчики Android знакомы с использованием объектов Kotlin Flow для сбора постоянно меняющихся данных пользовательского интерфейса. Например, вас может заинтересовать поток Player.isPlaying , который можно collect с учётом жизненного цикла. Или что-то вроде Player.eventsFlow , предоставляющее Flow<Player.Events> , который можно filter нужным вам способом.
 Однако использование потоков для состояния пользовательского интерфейса Player имеет ряд недостатков. Одна из основных проблем — асинхронность передачи данных. Мы хотим обеспечить минимально возможную задержку между Player.Event и его использованием в пользовательском интерфейсе, избегая отображения элементов пользовательского интерфейса, которые не синхронизированы с Player .
Другие пункты включают в себя:
-  Поток со всеми Player.Eventsне будет придерживаться единого принципа ответственности, каждому потребителю придется отфильтровывать соответствующие события.
-  Создание потока для каждого Player.Eventпотребует их объединения (с помощьюcombine) для каждого элемента пользовательского интерфейса. Между событием Player.Event и изменением элемента пользовательского интерфейса существует соответствие «многие ко многим». Использованиеcombineможет привести к потенциально недопустимым состояниям пользовательского интерфейса.
Создавайте пользовательские состояния пользовательского интерфейса
Вы можете добавить собственные состояния пользовательского интерфейса, если существующие не отвечают вашим требованиям. Ознакомьтесь с исходным кодом существующего состояния, чтобы скопировать шаблон. Типичный класс-держатель состояний пользовательского интерфейса выполняет следующие действия:
-  Принимает Player.
-  Подписывается на Playerс помощью сопрограмм. Подробнее см. вPlayer.listen.
-  Реагирует на определенные события Player.Eventsобновляя свое внутреннее состояние.
-  Принимайте команды бизнес-логики, которые будут преобразованы в соответствующее обновление Player.
- Может быть создан в нескольких местах дерева пользовательского интерфейса и всегда будет поддерживать единообразное представление состояния игрока.
-  Предоставляет поля StateCompose, которые могут использоваться Composable для динамического реагирования на изменения.
-  Поставляется с функцией remember*Stateдля запоминания экземпляра между композициями.
Что происходит за кулисами:
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)
      }
    }
}
 Чтобы реагировать на собственные Player.Events , вы можете перехватывать их с помощью Player.listen — это suspend fun , позволяющее войти в мир сопрограмм и бесконечно прослушивать события Player.Events . Реализация различных состояний пользовательского интерфейса в Media3 помогает конечному разработчику не беспокоиться о необходимости изучения Player.Events .
