Library media3-ui-compose menyediakan komponen dasar untuk
membangun UI media di Jetpack Compose. Dirancang untuk developer yang memerlukan
penyesuaian lebih lanjut daripada yang ditawarkan oleh library media3-ui-compose-material3. Halaman ini menjelaskan cara menggunakan komponen inti dan pemegang status untuk
membuat UI pemutar media kustom.
Menggabungkan komponen Compose kustom dan Material 3
Library media3-ui-compose-material3 dirancang agar fleksibel. Anda dapat
menggunakan komponen bawaan untuk sebagian besar UI, tetapi menukar satu komponen
dengan implementasi kustom saat Anda memerlukan kontrol yang lebih besar. Di sinilah library media3-ui-compose berperan.
Misalnya, bayangkan Anda ingin menggunakan
PreviousButton dan NextButton standar dari
library Material3, tetapi Anda memerlukan PlayPauseButton yang sepenuhnya kustom. Anda dapat
melakukannya dengan menggunakan PlayPauseButton dari library media3-ui-compose
inti dan menempatkannya bersama komponen bawaan.
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) }
Komponen yang tersedia
Library media3-ui-compose menyediakan serangkaian composable bawaan untuk kontrol pemutar umum. Berikut beberapa komponen yang dapat Anda gunakan langsung di
aplikasi Anda:
| Komponen | Deskripsi |
|---|---|
PlayPauseButton |
Penampung status untuk tombol yang beralih antara putar dan jeda. |
SeekBackButton |
Penampung status untuk tombol yang mencari mundur dengan inkrement yang ditentukan. |
SeekForwardButton |
Penampung status untuk tombol yang mencari ke depan dengan inkrement yang ditentukan. |
NextButton |
Penampung status untuk tombol yang mencari item media berikutnya. |
PreviousButton |
Penampung status untuk tombol yang mencari item media sebelumnya. |
RepeatButton |
Penampung status untuk tombol yang bergantian melalui mode pengulangan. |
ShuffleButton |
Penampung status untuk tombol yang mengalihkan mode acak. |
MuteButton |
Penampung status untuk tombol yang membisukan dan mengaktifkan suara pemutar. |
TimeText |
Container status untuk composable yang menampilkan progres pemain. |
ContentFrame |
Platform untuk menampilkan konten media yang menangani pengelolaan rasio aspek, pengubahan ukuran, dan rana |
PlayerSurface |
Permukaan mentah yang membungkus SurfaceView dan TextureView di AndroidView. |
Holder status UI
Jika tidak ada komponen scaffolding yang memenuhi kebutuhan Anda, Anda juga dapat menggunakan
objek status secara langsung. Sebaiknya gunakan metode remember yang sesuai untuk mempertahankan tampilan UI Anda di antara rekomposisi.
Untuk lebih memahami cara menggunakan fleksibilitas holder status UI versus Composable, baca cara Compose mengelola Status.
Holder status tombol
Untuk beberapa status UI, library mengasumsikan bahwa status tersebut kemungkinan besar akan digunakan oleh Composable seperti tombol.
| Status | remember*State | Jenis |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-Tombol |
PreviousButtonState |
rememberPreviousButtonState |
Konstanta |
NextButtonState |
rememberNextButtonState |
Konstanta |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2-Tombol |
PlaybackSpeedState |
rememberPlaybackSpeedState |
Menu atau N-Toggle |
Contoh penggunaan 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), ) }
Holder status output visual
PresentationState menyimpan informasi tentang kapan output video di
PlayerSurface dapat ditampilkan atau harus dicakup oleh elemen UI placeholder.
Composable ContentFrame menggabungkan penanganan rasio aspek dengan menangani
penampilan tombol rana di atas permukaan yang belum siap.
@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.videoSizeDp) // 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, scaledModifier, surfaceType) if (presentationState.coverSurface) { // Cover the surface that is being prepared with a shutter shutter() } }
Di sini, kita dapat menggunakan kedua presentationState.videoSizeDp untuk menskalakan Platform ke
rasio aspek yang dipilih (lihat dokumen ContentScale untuk jenis lainnya) dan
presentationState.coverSurface untuk mengetahui kapan waktu yang tepat untuk
menampilkan Platform. Dalam hal ini, Anda dapat memosisikan penutup buram di atas permukaan, yang akan menghilang saat permukaan siap. ContentFrame
memungkinkan Anda menyesuaikan rana sebagai lambda di akhir, tetapi secara default, rana akan berupa
@Composable Box hitam yang mengisi ukuran penampung induk.
Di mana letak Flow?
Banyak developer Android yang terbiasa menggunakan objek Flow Kotlin untuk mengumpulkan
data UI yang terus berubah. Misalnya, Anda mungkin mencari alur
Player.isPlaying yang dapat Anda collect dengan cara yang mendukung siklus proses. Atau
sesuatu seperti Player.eventsFlow untuk memberi Anda Flow<Player.Events>
yang dapat Anda filter sesuai keinginan Anda.
Namun, menggunakan alur untuk status UI Player memiliki beberapa kekurangan. Salah satu masalah utama adalah sifat transfer data yang asinkron. Kita ingin mencapai latensi sekecil mungkin antara Player.Event dan konsumsinya di sisi UI, dengan menghindari menampilkan elemen UI yang tidak sinkron dengan Player.
Poin lainnya mencakup:
- Alur dengan semua
Player.Eventstidak akan mematuhi prinsip tanggung jawab tunggal, setiap konsumen harus memfilter peristiwa yang relevan. - Membuat flow untuk setiap
Player.Eventakan mengharuskan Anda menggabungkannya (dengancombine) untuk setiap elemen UI. Ada pemetaan many-to-many antara Player.Event dan perubahan elemen UI. Penggunaancombinedapat menyebabkan UI berada dalam status yang berpotensi ilegal.
Membuat status UI kustom
Anda dapat menambahkan status UI kustom jika status yang ada tidak memenuhi kebutuhan Anda. Lihat kode sumber status yang ada untuk menyalin pola. Class holder status UI standar melakukan hal berikut:
- Menerima
Player. - Berlangganan ke
Playermenggunakan coroutine. LihatPlayer.listenuntuk mengetahui detail selengkapnya. - Merespons
Player.Eventstertentu dengan memperbarui status internalnya. - Menerima perintah logika bisnis yang akan diubah menjadi pembaruan
Playeryang sesuai. - Dapat dibuat di beberapa tempat di seluruh hierarki UI dan akan selalu mempertahankan tampilan status Pemain yang konsisten.
- Mengekspos kolom
StateCompose yang dapat digunakan oleh Composable untuk merespons perubahan secara dinamis. - Dilengkapi dengan fungsi
remember*Stateuntuk mengingat instance di antara komposisi.
Yang terjadi di balik layar:
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)
}
}
}
Untuk bereaksi terhadap Player.Events Anda sendiri, Anda dapat menangkapnya menggunakan Player.listen
yang merupakan suspend fun yang memungkinkan Anda memasuki dunia coroutine dan
memproses Player.Events tanpa batas. Penerapan Media3 dari berbagai status UI membantu developer akhir tidak perlu mempelajari Player.Events.