Menambahkan dependensi
Library Media3 menyertakan modul UI berbasis Jetpack Compose. Untuk menggunakannya, tambahkan dependensi berikut:
Kotlin
implementation("androidx.media3:media3-ui-compose:1.6.0")
Groovy
implementation "androidx.media3:media3-ui-compose:1.6.0"
Sebaiknya Anda mengembangkan aplikasi dengan cara Compose-first atau bermigrasi dari penggunaan View.
Aplikasi demo Compose sepenuhnya
Meskipun library media3-ui-compose
tidak menyertakan Composable
yang siap pakai (seperti tombol, indikator, gambar, atau dialog), Anda dapat menemukan
aplikasi demo yang ditulis sepenuhnya di Compose yang menghindari solusi
interoperabilitas seperti menggabungkan PlayerView
di AndroidView
. Aplikasi demo
menggunakan class holder status UI dari modul media3-ui-compose
dan menggunakan
library Compose Material3.
Holder status UI
Untuk lebih memahami cara menggunakan fleksibilitas holder status UI dibandingkan composable, baca cara Compose mengelola Status.
Holder status tombol
Untuk beberapa status UI, kami membuat asumsi 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 bahwa state
tidak memiliki informasi tema, seperti ikon yang akan digunakan untuk memutar
atau menjeda. Satu-satunya tanggung jawabnya adalah mengubah Player
menjadi status UI.
Kemudian, Anda dapat menggabungkan tombol dalam tata letak preferensi 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 ditutupi oleh elemen UI placeholder.
val presentationState = rememberPresentationState(player)
val scaledModifier = Modifier.resize(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 presentationState.videoSizeDp
untuk menskalakan Platform ke
rasio aspek yang diinginkan (lihat dokumen ContentScale untuk jenis lainnya) dan
presentationState.coverSurface
untuk mengetahui kapan waktunya tidak tepat untuk
menampilkan Platform. Dalam hal ini, Anda dapat memosisikan shutter 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
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.
Namun, penggunaan alur untuk status UI Player
memiliki beberapa kekurangan. Salah satu masalah utama
adalah sifat transfer data yang asinkron. Kita ingin memastikan
latensi sekecil mungkin antara Player.Event
dan penggunaannya di
sisi UI, sehingga menghindari tampilan elemen UI yang tidak sinkron dengan Player
.
Poin lainnya mencakup:
- Alur dengan semua
Player.Events
tidak akan mematuhi satu prinsip tanggung jawab, setiap konsumen harus memfilter peristiwa yang relevan. - Membuat alur untuk setiap
Player.Event
akan mengharuskan Anda menggabungkannya (dengancombine
) untuk setiap elemen UI. Ada pemetaan many-to-many antara Player.Event dan perubahan elemen UI. Harus menggunakancombine
dapat menyebabkan UI ke 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
Player
menggunakan coroutine. LihatPlayer.listen
untuk mengetahui detail selengkapnya. - Merespons
Player.Events
tertentu dengan memperbarui status internalnya. - Menerima perintah logika bisnis yang akan diubah menjadi update
Player
yang sesuai. - Dapat dibuat di beberapa tempat di hierarki UI dan akan selalu mempertahankan tampilan status Pemain yang konsisten.
- Mengekspos kolom
State
Compose yang dapat digunakan oleh Composable untuk merespons perubahan secara dinamis. - Dilengkapi dengan fungsi
remember*State
untuk 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 waktu. Penerapan Media3 dari berbagai status
UI membantu developer akhir untuk tidak perlu mempelajari
Player.Events
.