เพิ่มการพึ่งพา
ไลบรารี Media3 มีโมดูล UI ที่ใช้ Jetpack Compose หากต้องการใช้ ให้เพิ่มข้อกำหนดต่อไปนี้
Kotlin
implementation("androidx.media3:media3-ui-compose:1.6.0")
Groovy
implementation "androidx.media3:media3-ui-compose:1.6.0"
เราขอแนะนําอย่างยิ่งให้คุณพัฒนาแอปในลักษณะ Compose ก่อน หรือย้ายข้อมูลจากการใช้ Views
แอปเดโมที่คอมโพสิทอย่างเต็มรูปแบบ
แม้ว่าไลบรารี media3-ui-compose
จะไม่มีคอมโพสิเบิลที่พร้อมใช้งาน (เช่น ปุ่ม ตัวบ่งชี้ รูปภาพ หรือกล่องโต้ตอบ) แต่คุณจะเห็นแอปเดโมที่เขียนด้วย Compose ทั้งหมด ซึ่งหลีกเลี่ยงโซลูชันการทำงานร่วมกัน เช่น การรวม PlayerView
ใน AndroidView
แอปเดโมใช้คลาสตัวเก็บสถานะ UI จากโมดูล media3-ui-compose
และใช้ไลบรารี Compose Material3
ตัวเก็บสถานะ UI
หากต้องการทําความเข้าใจวิธีใช้ความยืดหยุ่นของตัวแปรสถานะ UI เทียบกับคอมโพสิเบิลได้ดีขึ้น โปรดอ่านวิธีจัดการสถานะของ Compose
ตัวเก็บสถานะปุ่ม
สำหรับสถานะ UI บางสถานะ เราถือว่า Composable ลักษณะปุ่มมีแนวโน้มที่จะใช้สถานะเหล่านั้นมากที่สุด
รัฐ | remember*State | ประเภท |
---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-สลับ |
PreviousButtonState |
rememberPreviousButtonState |
ค่าคงที่ |
NextButtonState |
rememberNextButtonState |
ค่าคงที่ |
RepeatButtonState |
rememberRepeatButtonState |
สวิตช์โยก 3 ทาง |
ShuffleButtonState |
rememberShuffleButtonState |
2-สลับ |
PlaybackSpeedState |
rememberPlaybackSpeedState |
เมนูหรือ N-Toggle |
ตัวอย่างการใช้ 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),
)
}
}
โปรดทราบว่า state
ไม่มีข้อมูลธีม เช่น ไอคอนสำหรับเล่นหรือหยุดชั่วคราว หน้าที่เดียวของ Player
คือเปลี่ยน Player
เป็นสถานะ UI
จากนั้นคุณสามารถผสมผสานปุ่มต่างๆ ในเลย์เอาต์ตามค่ากำหนดของคุณได้ ดังนี้
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
ตัวเก็บสถานะเอาต์พุตภาพ
PresentationState
เก็บข้อมูลสำหรับกรณีที่เอาต์พุตวิดีโอใน PlayerSurface
จะแสดงได้หรือควรปกปิดด้วยองค์ประกอบ UI ตัวยึดตำแหน่ง
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))
}
ในส่วนนี้ เราสามารถใช้ทั้ง presentationState.videoSizeDp
เพื่อปรับขนาด Surface ให้มีขนาดสัดส่วนตามที่ต้องการ (ดูประเภทอื่นๆ ในเอกสาร ContentScale) และ presentationState.coverSurface
เพื่อดูว่าเวลาใดไม่เหมาะที่จะแสดง Surface ในกรณีนี้ คุณสามารถวางชัตเตอร์ทึบไว้ด้านบนของพื้นผิว ซึ่งจะหายไปเมื่อพื้นผิวพร้อมใช้งาน
ฟีดอยู่ที่ไหน
นักพัฒนาแอป Android หลายคนคุ้นเคยกับการใช้ออบเจ็กต์ Flow
ของ Kotlin เพื่อรวบรวมข้อมูล UI ที่เปลี่ยนแปลงอยู่ตลอดเวลา เช่น คุณอาจมองหาPlayer.isPlaying
โฟลว์ที่คุณสามารถcollect
ในลักษณะที่คำนึงถึงวงจร หรือใช้ข้อความอย่าง Player.eventsFlow
เพื่อให้คุณได้รับ Flow<Player.Events>
ซึ่งคุณสามารถ filter
ในแบบที่คุณต้องการ
อย่างไรก็ตาม การใช้โฟลว์สำหรับPlayer
สถานะ UI มีข้อเสียอยู่บ้าง ข้อกังวลหลักประการหนึ่งคือการโอนข้อมูลแบบไม่พร้อมกัน เราต้องการลดเวลาในการตอบสนองระหว่าง Player.Event
กับการใช้งานในฝั่ง UI ให้น้อยที่สุด เพื่อหลีกเลี่ยงการแสดงองค์ประกอบ UI ที่ไม่ซิงค์กับ Player
ประเด็นอื่นๆ ได้แก่
- โฟลว์ที่มี
Player.Events
ทั้งหมดจะไม่เป็นไปตามหลักการความรับผิดชอบแบบรวมศูนย์ ผู้บริโภคแต่ละรายจะต้องกรองเหตุการณ์ที่เกี่ยวข้องออก - การสร้างโฟลว์สำหรับ
Player.Event
แต่ละรายการจะทำให้คุณต้องรวมPlayer.Event
เหล่านั้น (ด้วยcombine
) สำหรับองค์ประกอบ UI แต่ละรายการ มีการแมปแบบหลายต่อหลายรายการระหว่าง Player.Event กับการเปลี่ยนแปลงองค์ประกอบ UI การใช้combine
อาจทําให้ UI อยู่ในสถานะที่อาจผิดกฎหมาย
สร้างสถานะ UI ที่กําหนดเอง
คุณสามารถเพิ่มสถานะ UI ที่กําหนดเองได้หากสถานะที่มีอยู่ไม่ตรงกับความต้องการของคุณ ตรวจสอบซอร์สโค้ดของสถานะที่มีอยู่เพื่อคัดลอกรูปแบบ คลาสตัวยึดสถานะ UI ทั่วไปจะทําสิ่งต่อไปนี้
- รับ
Player
- สมัครรับ
Player
โดยใช้ coroutine ดูรายละเอียดเพิ่มเติมได้ที่Player.listen
- ตอบสนองต่อ
Player.Events
ที่เฉพาะเจาะจงโดยการอัปเดตสถานะภายใน - ยอมรับคําสั่งตรรกะทางธุรกิจที่จะเปลี่ยนเป็น
Player
การอัปเดตที่เหมาะสม - สร้างได้ในหลายตำแหน่งในต้นไม้ UI และจะรักษามุมมองสถานะของผู้เล่นให้สอดคล้องกันเสมอ
- แสดงฟิลด์ Compose
State
ที่ Composable สามารถใช้เพื่อตอบสนองต่อการเปลี่ยนแปลงแบบไดนามิก - มีฟังก์ชัน
remember*State
สำหรับการจดจำอินสแตนซ์ระหว่างการคอมโพสิชัน
สิ่งที่เกิดขึ้นเบื้องหลัง
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)
}
}
}
หากต้องการโต้ตอบกับ Player.Events
ของคุณเอง ให้จับ Player.Events
โดยใช้ Player.listen
ซึ่งเป็น suspend fun
ที่ช่วยให้คุณเข้าสู่โลกของ coroutine และฟัง Player.Events
ได้โดยไม่มีกำหนด การใช้งาน UI ต่างๆ ของ Media3 ช่วยให้นักพัฒนาแอปไม่ต้องกังวลเรื่องการเรียนรู้เกี่ยวกับPlayer.Events