Библиотека media3-ui-compose предоставляет базовые компоненты для создания пользовательского интерфейса медиаплеера в Jetpack Compose. Она предназначена для разработчиков, которым требуется больше возможностей для настройки, чем предлагает библиотека media3-ui-compose-material3 . На этой странице объясняется, как использовать основные компоненты и держатели состояний для создания пользовательского интерфейса медиаплеера.
Смешивание Material3 и пользовательских компонентов Compose.
Библиотека media3-ui-compose-material3 разработана с учетом гибкости. Вы можете использовать готовые компоненты для большинства элементов пользовательского интерфейса, но заменить один компонент на собственную реализацию, когда вам потребуется больший контроль. Именно здесь вступает в игру библиотека media3-ui-compose .
Например, представьте, что вы хотите использовать стандартные PreviousButton и NextButton из библиотеки Material3, но вам нужна совершенно собственная PlayPauseButton . Этого можно добиться, используя PlayPauseButton из основной библиотеки media3-ui-compose и разместив его рядом с готовыми компонентами.
Row { // Use prebuilt component from the Media3 UI Compose Material3 library PreviousButton(player) // Use the scaffold component from Media3 UI Compose library PlayPauseButton(player) { // `this` is PlayPauseButtonState FilledTonalButton( onClick = { Log.d("PlayPauseButton", "Clicking on play-pause button") this.onClick() }, enabled = this.isEnabled, ) { Icon( imageVector = if (showPlay) Icons.Default.PlayArrow else Icons.Default.Pause, contentDescription = if (showPlay) "Play" else "Pause", ) } } // Use prebuilt component from the Media3 UI Compose Material3 library NextButton(player) }
Доступные компоненты
Библиотека media3-ui-compose предоставляет набор готовых компонентов для распространенных элементов управления проигрывателем. Вот некоторые из компонентов, которые вы можете использовать непосредственно в своем приложении:
| Компонент | Описание |
|---|---|
PlayPauseButton | Контейнер состояния для кнопки, переключающей режимы воспроизведения и паузы. |
SeekBackButton | Контейнер состояний для кнопки, которая перемещается назад на заданный шаг. |
SeekForwardButton | Контейнер состояний для кнопки, которая перемещается вперед на заданный шаг. |
NextButton | Контейнер состояния для кнопки, осуществляющей переход к следующему медиафайлу. |
PreviousButton | Контейнер состояния для кнопки, которая перенаправляет на предыдущий медиафайл. |
RepeatButton | Контейнер состояния для кнопки, который переключает режимы повтора. |
ShuffleButton | Контейнер состояния для кнопки, переключающей режим перемешивания. |
MuteButton | Контейнер состояния для кнопки, которая включает и выключает звук у игрока. |
TimeText | Контейнер состояния для составного объекта, отображающий прогресс игрока. |
ContentFrame | Поверхность для отображения медиаконтента, которая управляет соотношением сторон, изменением размера и имеет затвор. |
PlayerSurface | Исходная поверхность, которая оборачивает SurfaceView и TextureView в AndroidView . |
Настройте составной Player
При использовании библиотеки media3-ui-compose-material3 компонент Player обеспечивает полноценный опыт воспроизведения мультимедиа. Вы можете настроить его макет, предоставив собственные компоненты для его слотов контента, или использовать объект PlayerDefaults для настройки отдельных частей пользовательского интерфейса, таких как TopControls , CenterControls , BottomControls и ErrorOverlay .
Например, вы можете использовать PlayerDefaults.CenterControls , чтобы переопределить только центральную кнопку воспроизведения/паузы, оставив остальные центральные элементы управления без изменений. Вы также можете передать полностью настраиваемый составной объект в качестве параметра, например, topControls , и полностью опустить другие параметры, такие как bottomControls чтобы оставить их по умолчанию.
@Composable fun CustomPlayerSlots(player: Player, modifier: Modifier = Modifier) { Player( player = player, modifier = modifier, topControls = { p, visible -> // Fully custom top controls AnimatedVisibility(visible) { Text("My custom title") } }, centerControls = { p, visible -> // Use default CenterControls but override the central button PlayerDefaults.CenterControls( player = p, visible = visible, central = { // A custom play/pause button Material3PlayPauseButton(it, modifier = Modifier.size(64.dp)) }, ) }, // bottomControls are left as default ) }
Владельцы статуса UI
Если ни один из вспомогательных компонентов не соответствует вашим потребностям, вы также можете использовать объекты состояния напрямую. Как правило, рекомендуется использовать соответствующие методы remember , чтобы сохранить внешний вид пользовательского интерфейса между перекомпозициями.
Чтобы лучше понять, как можно использовать гибкость держателей состояния пользовательского интерфейса по сравнению с компонуемыми элементами, ознакомьтесь с тем, как Compose управляет состоянием .
Держатели состояния кнопок
Для некоторых состояний пользовательского интерфейса библиотека исходит из предположения, что они, скорее всего, будут использоваться составными элементами, похожими на кнопки.
| Состояние | помните*Штат | Тип |
|---|---|---|
PlayPauseButtonState | rememberPlayPauseButtonState | 2-Переключатель |
PreviousButtonState | rememberPreviousButtonState | Постоянный |
NextButtonState | rememberNextButtonState | Постоянный |
RepeatButtonState | rememberRepeatButtonState | 3-Переключатель |
ShuffleButtonState | rememberShuffleButtonState | 2-Переключатель |
PlaybackSpeedState | rememberPlaybackSpeedState | Меню или N-переключатель |
Пример использования PlayPauseButtonState :
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), ) }
Визуальные держатели состояний вывода
CurrentMediaItemState предоставляет информацию о текущем воспроизводимом MediaItem , а PlaylistState — информацию о MediaItems установленных в плеере. Это полезно для отображения метаданных в пользовательском интерфейсе.
ErrorState предоставляет информацию о текущем состоянии ошибки проигрывателя, которую можно использовать для отображения всплывающего окна с сообщением об ошибке.
PresentationState содержит информацию о том, когда видеовыход в PlayerSurface может быть показан или должен быть закрыт элементом пользовательского интерфейса-заполнителем. ContentFrame composable объединяет обработку соотношения сторон с обеспечением отображения затвора над поверхностью, которая еще не готова.
@Composable fun ContentFrame( player: Player?, modifier: Modifier = Modifier, surfaceType: @SurfaceType Int = SURFACE_TYPE_SURFACE_VIEW, contentScale: ContentScale = ContentScale.Fit, keepContentOnReset: Boolean = false, shutter: @Composable () -> Unit = { Box(Modifier.fillMaxSize().background(Color.Black)) }, ) { val presentationState = rememberPresentationState(player, keepContentOnReset) val scaledModifier = modifier.resizeWithContentScale(contentScale, presentationState.videoAspectRatio) // Always leave PlayerSurface to be part of the Compose tree because it will be initialized 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, scaledModifier, surfaceType) if (presentationState.coverSurface) { // Cover the surface that is being prepared with a shutter shutter() } }
Здесь мы можем использовать как presentationState.videoAspectRatio для масштабирования поверхности до выбранного соотношения сторон (см. Content scale для получения информации о других типах), так и presentationState.coverSurface чтобы определить, когда неподходящее время для отображения поверхности. В этом случае вы можете разместить непрозрачную завесу поверх поверхности, которая исчезнет, когда поверхность будет готова. ContentFrame позволяет настроить завесу как лямбда-функцию в конце, но по умолчанию это будет черный @Composable Box заполняющий размер родительского контейнера.
Где находятся потоки?
Многие разработчики 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. - Может быть создан в нескольких местах дерева пользовательского интерфейса и всегда будет поддерживать согласованное представление состояния игрока.
- Предоставляет доступ к полям
Stateкомпозиции, которые могут быть использованы компонуемым элементом для динамического реагирования на изменения. - Включает функцию
remember*Stateдля запоминания экземпляра между композициями.
Что происходит за кулисами:
class SomeButtonState(private val player: Player) { var isEnabled by mutableStateOf(player.isCommandAvailable(COMMAND_ACTION_A)) private set var someFieldValue by mutableStateOf(someFieldDefault) private set fun onClick() { player.actionA() } suspend fun observe(): Nothing = player.listen { events -> if (events.containsAny(EVENT_B_CHANGED, EVENT_C_CHANGED, EVENT_AVAILABLE_COMMANDS_CHANGED)) { someFieldValue = this.someField isEnabled = this.isCommandAvailable(COMMAND_ACTION_A) } } }
Чтобы реагировать на собственные Player.Events , вы можете перехватывать их с помощью Player.listen , который представляет собой suspend fun , позволяющую войти в мир сопрограмм и бесконечно прослушивать события Player.Events . Реализация различных состояний пользовательского интерфейса в Media3 помогает конечному разработчику не беспокоиться об отслеживании Player.Events .