Abhängigkeit hinzufügen
Die Media3-Bibliothek enthält ein Jetpack Compose-basiertes UI-Modul. Wenn Sie sie verwenden möchten, fügen Sie die folgende Abhängigkeit hinzu:
Kotlin
implementation("androidx.media3:media3-ui-compose:1.6.0")
Groovy
implementation "androidx.media3:media3-ui-compose:1.6.0"
Wir empfehlen Ihnen dringend, Ihre App mit Compose als Hauptelement zu entwickeln oder von der Verwendung von Ansichten zu migrieren.
Demo-App für Fully Compose
Die media3-ui-compose
-Bibliothek enthält zwar keine sofort einsatzbereiten Composables (z. B. Schaltflächen, Indikatoren, Bilder oder Dialoge), aber es gibt eine Demo-App, die vollständig in Compose geschrieben wurde, bei der keine Interoperabilitätslösungen wie das Einbetten von PlayerView
in AndroidView
erforderlich sind. Die Demo-App verwendet die UI-Statushalterklassen aus dem media3-ui-compose
-Modul und die Compose Material3-Bibliothek.
UI-Statushalter
Wenn Sie mehr darüber erfahren möchten, wie Sie die Flexibilität von UI-Statushaltern im Vergleich zu Composeables nutzen können, lesen Sie den Artikel Status in Compose verwalten.
State Holder für Schaltflächen
Bei einigen UI-Status gehen wir davon aus, dass sie höchstwahrscheinlich von Schaltflächen-ähnlichen Composables verwendet werden.
Bundesland | remember*State | Eingeben |
---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
2-Toggle |
PreviousButtonState |
rememberPreviousButtonState |
Konstante |
NextButtonState |
rememberNextButtonState |
Konstante |
RepeatButtonState |
rememberRepeatButtonState |
3-Toggle |
ShuffleButtonState |
rememberShuffleButtonState |
2-Toggle |
PlaybackSpeedState |
rememberPlaybackSpeedState |
Menü oder N-Toggle |
Beispiel für die Verwendung von 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),
)
}
}
Beachten Sie, dass state
keine Informationen zur Themengestaltung enthält, z. B. ein Symbol für die Wiedergabe oder Pausierung. Seine einzige Aufgabe besteht darin, die Player
in den UI-Status umzuwandeln.
Sie können die Schaltflächen dann nach Belieben im Layout platzieren:
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
State Holder für visuelle Ausgabe
PresentationState
enthält Informationen dazu, wann die Videoausgabe in einem PlayerSurface
angezeigt werden kann oder von einem Platzhalter-UI-Element abgedeckt werden soll.
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))
}
Hier können wir sowohl presentationState.videoSizeDp
verwenden, um das Surface auf das gewünschte Seitenverhältnis zu skalieren (weitere Typen finden Sie in der ContentScale-Dokumentation), als auch presentationState.coverSurface
, um zu erfahren, wann das Timing nicht richtig ist, um das Surface anzuzeigen. In diesem Fall können Sie einen undurchsichtigen Schiebevorhang auf der Oberfläche platzieren, der verschwindet, wenn die Oberfläche bereit ist.
Wo finde ich Abläufe?
Viele Android-Entwickler sind mit der Verwendung von Kotlin-Flow
-Objekten zum Erfassen sich ständig ändernder UI-Daten vertraut. Angenommen, Sie suchen nach einem Player.isPlaying
-Vorgang, den Sie collect
schleifenbasiert ausführen können. Oder etwas wie Player.eventsFlow
, um Ihnen eine Flow<Player.Events>
zu geben, die Sie nach Belieben filter
können.
Die Verwendung von Aufrufabfolgen für den UI-Status von Player
hat jedoch einige Nachteile. Eines der Hauptprobleme ist die asynchrone Natur der Datenübertragung. Wir möchten für möglichst wenig Latenz zwischen einem Player.Event
und seiner Verwendung auf der UI-Seite sorgen, um die Anzeige von UI-Elementen zu vermeiden, die nicht mit dem Player
synchronisiert sind.
Weitere Punkte:
- Ein Fluss mit allen
Player.Events
würde nicht dem Prinzip der einzelnen Verantwortung entsprechen, da jeder Nutzer die relevanten Ereignisse herausfiltern müsste. - Wenn Sie einen Ablauf für jede
Player.Event
erstellen möchten, müssen Sie sie für jedes UI-Element kombinieren (mitcombine
). Es gibt eine Mehrfachzuordnung zwischen einem Player.Event und einer Änderung des UI-Elements. Wenncombine
verwendet werden muss, kann dies zu potenziell illegalen Zuständen der Benutzeroberfläche führen.
Benutzerdefinierte UI-Status erstellen
Sie können benutzerdefinierte UI-Zustände hinzufügen, wenn die vorhandenen nicht Ihren Anforderungen entsprechen. Sehen Sie sich den Quellcode des vorhandenen Status an, um das Muster zu kopieren. Eine typische UI-Statushalterklasse führt Folgendes aus:
- Nimmt eine
Player
entgegen. - Abonniert
Player
mithilfe von Koroutinen. Weitere Informationen finden Sie unterPlayer.listen
. - Reagiert auf bestimmte
Player.Events
, indem der interne Status aktualisiert wird. - Akzeptieren Sie Geschäftslogikbefehle, die in eine entsprechende
Player
-Aktualisierung umgewandelt werden. - Kann an mehreren Stellen im UI-Baum erstellt werden und bietet immer eine konsistente Ansicht des Spielerstatus.
- Stellt Compose
State
-Felder bereit, die von einem Composable verwendet werden können, um dynamisch auf Änderungen zu reagieren. - Bietet eine
remember*State
-Funktion, um die Instanz zwischen Kompositionen zu speichern.
Was passiert hinter den Kulissen:
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)
}
}
}
Wenn Sie auf Ihre eigenen Player.Events
reagieren möchten, können Sie sie mit Player.listen
abfangen. Das ist eine suspend fun
, mit der Sie die Welt der coroutines betreten und unbegrenzt auf Player.Events
hören können. Die Implementierung verschiedener UI-Zustände in Media3 hilft dem Endentwickler, sich nicht mit Player.Events
befassen zu müssen.