Library media3-ui-compose menyediakan komponen dasar untuk
membuat UI media di Jetpack Compose. API ini dirancang untuk developer yang memerlukan
lebih banyak penyesuaian daripada yang ditawarkan oleh library
media3-ui-compose-material3. Halaman ini menjelaskan cara menggunakan komponen inti dan holder status untuk
membuat UI pemutar media kustom.
Menggabungkan komponen Material3 dan Compose kustom
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 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:
| Komponen | Deskripsi |
|---|---|
PlayPauseButton |
Penampung status untuk tombol yang beralih antara putar dan jeda. |
SeekBackButton |
Penampung status untuk tombol yang memutar mundur dengan penambahan yang ditentukan. |
SeekForwardButton |
Penampung status untuk tombol yang memajukan video dengan penambahan 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 beralih di antara mode ulang. |
ShuffleButton |
Penampung status untuk tombol yang mengaktifkan/menonaktifkan mode acak. |
MuteButton |
Penampung status untuk tombol yang membisukan dan membunyikan audio pemutar. |
TimeText |
Penampung status untuk composable yang menampilkan progres pemain. |
ContentFrame |
Platform untuk menampilkan konten media yang menangani pengelolaan rasio aspek, pengubahan ukuran, dan shutter |
PlayerSurface |
Permukaan mentah yang menggabungkan SurfaceView dan TextureView di AndroidView. |
Menyesuaikan composable Player
Saat menggunakan library media3-ui-compose-material3, composable
Player memberikan pengalaman pemutaran media yang
lengkap. Anda dapat menyesuaikan tata letaknya dengan menyediakan composable Anda sendiri ke
slot kontennya, atau Anda dapat memanfaatkan objek PlayerDefaults untuk
menyesuaikan bagian tertentu dari UI, seperti TopControls, CenterControls,
BottomControls, dan ErrorOverlay.
Gambar 1 menunjukkan tempat composable Player menempatkan slotnya. Gambar 2 menunjukkan
urutan penggambaran lapisannya.
Player.
Kontrol tengah dan bawah menampilkan slot
PlayerDefaults.CenterControls dan
PlayerDefaults.BottomControls. Slot topControls,
above, dan below kosong secara
default.Player,
diberi nomor sesuai urutan penggambarannya.Misalnya, Anda dapat menggunakan PlayerDefaults.CenterControls untuk mengganti tombol putar/jeda tengah saja, sekaligus membiarkan kontrol tengah lainnya tetap utuh.
Anda juga dapat meneruskan composable yang sepenuhnya kustom untuk parameter seperti topControls,
dan sepenuhnya menghilangkan parameter lain seperti bottomControls untuk membiarkannya dalam setelan default.
@Composable fun CustomPlayerSlots(player: Player, showControls: Boolean, modifier: Modifier = Modifier) { Player( player = player, modifier = modifier, showControls = showControls, topControls = { // Fully custom top controls Text("My custom title") }, centerControls = { // Use default CenterControls but override the central button PlayerDefaults.CenterControls( player = player, central = { // A custom play/pause button Material3PlayPauseButton(player, modifier = Modifier.size(64.dp)) }, ) }, // bottomControls are left as default ) }
Holder status UI
Jika tidak ada komponen scaffolding yang memenuhi kebutuhan, 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 membuat asumsi bahwa status tersebut kemungkinan besar akan digunakan oleh composable seperti tombol.
| Negara Bagian | 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
CurrentMediaItemState memberikan informasi tentang MediaItem yang sedang diputar, sedangkan PlaylistState mengekspos informasi tentang MediaItems yang ditetapkan pada pemutar. Hal ini berguna untuk menampilkan informasi metadata di UI kustom Anda.
ErrorState memberikan informasi tentang status error pemutar saat ini,
yang dapat digunakan untuk menampilkan overlay error.
PresentationState menyimpan informasi kapan output video dalam
PlayerSurface dapat ditampilkan atau harus dicakup oleh elemen UI placeholder.
Composable ContentFrame menggabungkan penanganan rasio aspek dengan menampilkan shutter di atas platform 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.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() } }
Di sini, kita dapat menggunakan presentationState.videoAspectRatio untuk menskalakan Surface
ke rasio aspek yang dipilih (lihat Skala konten untuk jenis lainnya) dan
presentationState.coverSurface untuk mengetahui kapan waktu yang tidak tepat untuk
menampilkan Surface. Dalam hal ini, Anda dapat menempatkan shutter buram di atas
permukaan, yang akan hilang saat permukaan siap.
ContentFrame memungkinkan Anda menyesuaikan shutter sebagai lambda di akhir, tetapi secara default, shutter akan berupa @Composable Box hitam yang mengisi ukuran penampung induk.
Di mana Flow?
Banyak developer Android yang terbiasa menggunakan objek Flow Kotlin untuk mengumpulkan
data UI yang terus berubah. Misalnya, Anda mungkin mencari
flow 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 flow untuk status UI Player memiliki beberapa kelemahan. Salah satu masalah utamanya adalah sifat transfer data yang asinkron. Kita ingin mencapai latensi
sekecil mungkin antara Player.Event dan konsumsinya di
sisi UI, sehingga menghindari menampilkan elemen UI yang tidak sinkron dengan Player.
Poin lainnya meliputi:
- 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 polanya. Class holder status UI standar melakukan hal berikut:
- Mengambil objek
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 update
Playeryang sesuai. - Dapat dibuat di beberapa tempat di seluruh hierarki UI dan akan selalu mempertahankan tampilan status Pemutar 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(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) } } }
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 waktu. Implementasi Media3 dari berbagai status UI
membantu developer akhir agar tidak perlu mempelajari
Player.Events.