종속 항목 추가
Media3 라이브러리에는 Jetpack Compose 기반 UI 모듈이 포함되어 있습니다. 이를 사용하려면 다음 종속 항목을 추가하세요.
Kotlin
implementation("androidx.media3:media3-ui-compose:1.7.1")
Groovy
implementation "androidx.media3:media3-ui-compose:1.7.1"
Compose 우선 방식으로 앱을 개발하거나 뷰 사용에서 이전하는 것이 좋습니다.
완전 Compose 데모 앱
media3-ui-compose
라이브러리에는 기본적으로 컴포저블 (예: 버튼, 표시기, 이미지 또는 대화상자)이 포함되어 있지 않지만 PlayerView
을 AndroidView
로 래핑하는 것과 같은 상호 운용성 솔루션을 사용하지 않는 Compose로 완전히 작성된 데모 앱을 찾을 수 있습니다. 데모 앱은 media3-ui-compose
모듈의 UI 상태 홀더 클래스를 활용하고 Compose Material3 라이브러리를 사용합니다.
UI 상태 홀더
컴포저블과 비교하여 UI 상태 홀더의 유연성을 사용하는 방법을 자세히 알아보려면 Compose에서 상태를 관리하는 방법을 읽어보세요.
버튼 상태 홀더
일부 UI 상태의 경우 버튼과 유사한 컴포저블에서 소비될 가능성이 가장 높다고 가정합니다.
상태 | remember*State | 유형 |
---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-Toggle |
PreviousButtonState |
rememberPreviousButtonState |
상수 |
NextButtonState |
rememberNextButtonState |
상수 |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2-Toggle |
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
를 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 개발자가 Kotlin Flow
객체를 사용하여 끊임없이 변화하는 UI 데이터를 수집하는 데 익숙합니다. 예를 들어 수명 주기 인식 방식으로 collect
할 수 있는 Player.isPlaying
흐름을 찾고 있을 수 있습니다. 또는 원하는 방식으로 filter
할 수 있는 Flow<Player.Events>
을 제공하는 Player.eventsFlow
와 같은 항목을 사용할 수 있습니다.
하지만 Player
UI 상태에 흐름을 사용하면 몇 가지 단점이 있습니다. 주요 우려사항 중 하나는 데이터 전송의 비동기적 특성입니다. Player.Event
와 UI 측에서의 소비 간 지연 시간을 최대한 줄여 Player
와 동기화되지 않은 UI 요소가 표시되지 않도록 해야 합니다.
기타 사항은 다음과 같습니다.
- 모든
Player.Events
가 포함된 흐름은 단일 책임 원칙을 준수하지 않으며 각 소비자는 관련 이벤트를 필터링해야 합니다. - 각
Player.Event
의 흐름을 만들려면 각 UI 요소에 대해combine
로 결합해야 합니다. Player.Event와 UI 요소 변경 간에는 다대다 매핑이 있습니다.combine
을 사용해야 하면 UI가 불법 상태가 될 수 있습니다.
맞춤 UI 상태 만들기
기존 UI 상태가 요구사항을 충족하지 않는 경우 맞춤 UI 상태를 추가할 수 있습니다. 기존 상태의 소스 코드를 확인하여 패턴을 복사합니다. 일반적인 UI 상태 홀더 클래스는 다음을 실행합니다.
Player
을 사용합니다.- 코루틴을 사용하여
Player
를 구독합니다. 자세한 내용은Player.listen
를 참고하세요. - 내부 상태를 업데이트하여 특정
Player.Events
에 응답합니다. - 적절한
Player
업데이트로 변환될 비즈니스 로직 명령어를 수락합니다. - UI 트리 전체에서 여러 위치에 만들 수 있으며 항상 플레이어 상태의 일관된 뷰를 유지합니다.
- 컴포저블이 변경사항에 동적으로 응답하기 위해 사용할 수 있는 Compose
State
필드를 노출합니다. - 컴포지션 간에 인스턴스를 기억하기 위한
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
를 무기한 수신할 수 있는 suspend fun
인 Player.listen
를 사용하여 이를 포착하면 됩니다. 다양한 UI 상태의 Media3 구현은 최종 개발자가 Player.Events
에 대해 학습하지 않아도 되도록 지원합니다.