依存関係を追加する
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 を優先してアプリを開発するか、View の使用から移行することを強くおすすめします。
完全な Compose デモアプリ
media3-ui-compose
ライブラリには、すぐに使用できる Composable(ボタン、インジケーター、画像、ダイアログなど)は含まれていませんが、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
フローを探している場合などです。または、Player.eventsFlow
のようなものを使用して、必要な方法で filter
できる Flow<Player.Events>
を提供します。
ただし、Player
UI 状態にフローを使用することには、いくつかのデメリットがあります。主な懸念事項の 1 つは、データ転送が非同期であることです。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.listen
を使用してキャッチします。これは、コルーチンの世界に入り、Player.Events
を無期限にリッスンできる suspend fun
です。Media3 のさまざまな UI 状態の実装により、エンド デベロッパーは Player.Events
の学習を気にすることなく済みます。