Menambahkan dependensi
Library Media3 menyertakan modul UI berbasis Jetpack Compose. Untuk menggunakannya, tambahkan dependensi berikut:
Kotlin
implementation("androidx.media3:media3-ui-compose:1.8.0")
Groovy
implementation "androidx.media3:media3-ui-compose:1.8.0"
Sebaiknya Anda mengembangkan aplikasi dengan pendekatan Compose-first atau bermigrasi dari penggunaan View.
Aplikasi demo Compose sepenuhnya
Meskipun library media3-ui-compose tidak menyertakan Composable siap pakai (seperti tombol, indikator, gambar, atau dialog), Anda dapat menemukan
aplikasi demo yang ditulis sepenuhnya di Compose yang menghindari solusi
interoperabilitas seperti membungkus PlayerView di AndroidView. Aplikasi demo
menggunakan class pemegang status UI dari modul media3-ui-compose dan memanfaatkan
library Compose Material3.
Holder status UI
Untuk lebih memahami cara menggunakan fleksibilitas holder status UI versus composable, baca cara Compose mengelola Status.
Holder status tombol
Untuk beberapa status UI, kami berasumsi bahwa status tersebut kemungkinan besar akan digunakan oleh Composable seperti tombol.
| Status | remember*State | Jenis |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-Toggle |
PreviousButtonState |
rememberPreviousButtonState |
Konstanta |
NextButtonState |
rememberNextButtonState |
Konstanta |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2-Toggle |
PlaybackSpeedState |
rememberPlaybackSpeedState |
Menu atau N-Toggle |
Contoh penggunaan 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),
)
}
}
Perhatikan bagaimana state tidak memiliki informasi tema, seperti ikon yang digunakan untuk memutar
atau menjeda. Satu-satunya tanggung jawabnya adalah mengubah Player menjadi status UI.
Kemudian, Anda dapat menggabungkan tombol dalam tata letak pilihan Anda:
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
Holder status output visual
PresentationState menyimpan informasi tentang kapan output video di
PlayerSurface dapat ditampilkan atau harus dicakup oleh elemen UI placeholder.
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))
}
Di sini, kita dapat menggunakan kedua presentationState.videoSizeDp untuk menskalakan Platform ke
rasio aspek yang diinginkan (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.
Di mana Flow berada?
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. Kami ingin memastikan latensi sekecil mungkin antara Player.Event dan konsumsinya di sisi UI, sehingga tidak 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. Periksa kode sumber status yang ada untuk menyalin pola. Class holder status UI standar melakukan hal berikut:
- Menerima
Player. - Berlangganan ke
Playermenggunakan coroutine. LihatPlayer.listenuntuk 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 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(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 untuk berbagai status UI membantu developer akhir tidak perlu mempelajari Player.Events.