종속 항목 추가
Media3 라이브러리에는 Jetpack Compose 기반 UI 모듈이 포함되어 있습니다. 이를 사용하려면 다음 종속 항목을 추가하세요.
Kotlin
implementation("androidx.media3:media3-ui-compose:1.6.0")
Groovy
implementation "androidx.media3:media3-ui-compose:1.6.0"
Compose 우선 방식으로 앱을 개발하거나 뷰 사용에서 이전하는 것이 좋습니다.
전체 Compose 데모 앱
media3-ui-compose
라이브러리에는 즉시 사용 가능한 컴포저블 (예: 버튼, 표시기, 이미지, 대화상자)이 포함되어 있지 않지만 AndroidView
에서 PlayerView
를 래핑하는 것과 같은 상호 운용성 솔루션을 피하는 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 상태로 변환하는 것만이 Compose 컴포저블의 유일한 책임입니다.
그런 다음 원하는 레이아웃에서 버튼을 조합할 수 있습니다.
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
를 사용하여 노출 영역을 원하는 가로세로 비율로 조정하고 (자세한 유형은 ContentScale 문서 참고) presentationState.coverSurface
를 사용하여 노출 영역을 표시하기에 적절하지 않은 시기를 파악할 수 있습니다. 이 경우 노출 영역 위에 불투명한 셔터를 배치할 수 있으며, 노출 영역이 준비되면 셔터가 사라집니다.
흐름은 어디에 있나요?
많은 Android 개발자는 Kotlin Flow
객체를 사용하여 끊임없이 변화하는 UI 데이터를 수집하는 방법을 잘 알고 있습니다. 예를 들어 수명 주기에 맞게 collect
할 수 있는 Player.isPlaying
흐름을 찾고 있을 수 있습니다. 또는 Player.eventsFlow
와 같이 원하는 방식으로 filter
할 수 있는 Flow<Player.Events>
를 제공합니다.
그러나 Player
UI 상태에 흐름을 사용하면 몇 가지 단점이 있습니다. 주요 우려사항 중 하나는 데이터 전송의 비동기 특성입니다. Player.Event
와 UI 측의 소비 간에 최대한 지연 시간을 줄여 Player
와 동기화되지 않은 UI 요소가 표시되지 않도록 해야 합니다.
기타 사항은 다음과 같습니다.
- 모든
Player.Events
가 있는 흐름은 단일 책임 원칙을 준수하지 않으므로 각 소비자가 관련 이벤트를 필터링해야 합니다. - 각
Player.Event
의 흐름을 만들려면 각 UI 요소에 대해Player.Event
를 결합해야 합니다(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
를 사용하여 Player.Events
를 포착하면 됩니다. 다양한 UI 상태의 Media3 구현은 최종 개발자가 Player.Events
에 관해 알아야 하는 부담을 덜어줍니다.