Ajouter la dépendance
La bibliothèque Media3 inclut un module d'UI basé sur Jetpack Compose. Pour l'utiliser, ajoutez la dépendance suivante :
Kotlin
implementation("androidx.media3:media3-ui-compose:1.7.1")
Groovy
implementation "androidx.media3:media3-ui-compose:1.7.1"
Nous vous encourageons vivement à développer votre application en privilégiant Compose ou à migrer depuis l'utilisation des vues.
Application de démonstration entièrement Compose
Bien que la bibliothèque media3-ui-compose
n'inclue pas de composables prêts à l'emploi (tels que des boutons, des indicateurs, des images ou des boîtes de dialogue), vous pouvez trouver une application de démonstration entièrement écrite en Compose qui évite toute solution d'interopérabilité comme l'encapsulation de PlayerView
dans AndroidView
. L'application de démonstration utilise les classes de détenteur d'état de l'UI du module media3-ui-compose
et la bibliothèque Compose Material3.
Conteneurs d'état de l'UI
Pour mieux comprendre comment utiliser la flexibilité des conteneurs d'état de l'UI par rapport aux composables, découvrez comment Compose gère l'état.
Conteneurs d'état des boutons
Pour certains états d'UI, nous partons du principe qu'ils seront très probablement consommés par des Composables de type bouton.
État | remember*State | Saisie |
---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2 boutons |
PreviousButtonState |
rememberPreviousButtonState |
Constante |
NextButtonState |
rememberNextButtonState |
Constante |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2 boutons |
PlaybackSpeedState |
rememberPlaybackSpeedState |
Menu ou N-Toggle |
Exemple d'utilisation de 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),
)
}
}
Notez que state
ne possède aucune information de thème, comme l'icône à utiliser pour la lecture ou la pause. Sa seule responsabilité est de transformer le Player
en état de l'UI.
Vous pouvez ensuite combiner les boutons dans la mise en page de votre choix :
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
Conteneurs d'état de sortie visuelle
PresentationState
contient des informations sur le moment où la sortie vidéo d'un PlayerSurface
peut être affichée ou doit être couverte par un élément d'espace réservé de l'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))
}
Ici, nous pouvons utiliser à la fois presentationState.videoSizeDp
pour mettre à l'échelle la surface selon le format souhaité (voir la documentation ContentScale pour plus de types) et presentationState.coverSurface
pour savoir quand le timing n'est pas le bon pour afficher la surface. Dans ce cas, vous pouvez placer un obturateur opaque sur la surface, qui disparaîtra lorsque la surface sera prête.
Où se trouvent les flows ?
De nombreux développeurs Android sont habitués à utiliser des objets Flow
Kotlin pour collecter des données d'UI en constante évolution. Par exemple, vous pouvez rechercher un flux Player.isPlaying
que vous pouvez collect
en tenant compte du cycle de vie. Ou quelque chose comme Player.eventsFlow
pour vous fournir un Flow<Player.Events>
que vous pouvez filter
comme vous le souhaitez.
Toutefois, l'utilisation de flows pour l'état de l'UI Player
présente certains inconvénients. L'une des principales préoccupations concerne la nature asynchrone du transfert de données. Nous souhaitons garantir la latence la plus faible possible entre un Player.Event
et sa consommation côté UI, en évitant d'afficher des éléments d'UI qui ne sont pas synchronisés avec le Player
.
Voici d'autres points à retenir :
- Un flux avec tous les
Player.Events
ne respecterait pas le principe de responsabilité unique, car chaque consommateur devrait filtrer les événements pertinents. - La création d'un flux pour chaque
Player.Event
vous obligera à les combiner (aveccombine
) pour chaque élément d'interface utilisateur. Il existe un mappage de type plusieurs à plusieurs entre un Player.Event et une modification d'élément d'UI. L'utilisation decombine
peut entraîner des états potentiellement illégaux de l'UI.
Créer des états d'UI personnalisés
Vous pouvez ajouter des états d'UI personnalisés si ceux existants ne répondent pas à vos besoins. Extrayez le code source de l'état existant pour copier le modèle. Une classe de conteneur d'état d'UI standard effectue les opérations suivantes :
- Accepte un
Player
. - S'abonne à
Player
à l'aide de coroutines. Pour en savoir plus, consultezPlayer.listen
. - Répond à un
Player.Events
spécifique en mettant à jour son état interne. - Acceptez les commandes de logique métier qui seront transformées en mise à jour
Player
appropriée. - Peut être créé à plusieurs endroits de l'arborescence de l'UI et conserve toujours une vue cohérente de l'état du lecteur.
- Expose les champs
State
de Compose qui peuvent être utilisés par un composable pour répondre dynamiquement aux modifications. - Fourni avec une fonction
remember*State
pour mémoriser l'instance entre les compositions.
Que se passe-t-il en arrière-plan ?
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)
}
}
}
Pour réagir à vos propres Player.Events
, vous pouvez les intercepter à l'aide de Player.listen
, qui est un suspend fun
vous permettant d'entrer dans le monde des coroutines et d'écouter indéfiniment les Player.Events
. L'implémentation Media3 de différents états d'UI permet au développeur final de ne pas avoir à se soucier de l'apprentissage de Player.Events
.