Media3 bietet eine Standard-PlayerView
mit einigen Anpassungsoptionen.
Drawables überschreiben
PlayerView
verwendet PlayerControlView
, um die Wiedergabesteuerung und die Fortschrittsanzeige anzuzeigen. Die von PlayerControlView
verwendeten Drawables können durch Drawables mit denselben Namen überschrieben werden, die in Ihrer Anwendung definiert sind. Eine Liste der Steuerelement-Zeichnobjekte, die überschrieben werden können, finden Sie in der Dokumentation zu PlayerControlView
.
Für weitere Anpassungen müssen App-Entwickler ihre eigenen UI-Komponenten implementieren. Hier sind einige Best Practices, die Ihnen den Einstieg erleichtern können.
Best Practices
Wenn Sie eine Medien-UI implementieren, die eine Verbindung zu einer Media3-Player
herstellt (z. B. ExoPlayer
, MediaController
oder eine benutzerdefinierte Player
-Implementierung), sollten Sie für eine optimale Nutzeroberfläche diese Best Practices beachten.
Schaltfläche für Wiedergabe/Pause
Die Wiedergabe- und Pause-Schaltflächen entsprechen nicht direkt einem einzelnen Playerstatus. Ein Nutzer sollte beispielsweise die Wiedergabe nach dem Ende oder einem Fehler fortsetzen können, auch wenn der Player nicht pausiert ist.
Zur Vereinfachung der Implementierung bietet Media3 Dienstmethoden, mit denen entschieden werden kann, welche Schaltfläche angezeigt werden soll (Util.shouldShowPlayButton
) und wie Tastendrücke verarbeitet werden (Util.handlePlayPauseButtonAction
):
val shouldShowPlayButton: Boolean = Util.shouldShowPlayButton(player) playPauseButton.setImageDrawable(if (shouldShowPlayButton) playDrawable else pauseDrawable) playPauseButton.setOnClickListener { Util.handlePlayPauseButtonAction(player) }
boolean shouldShowPlayButton = Util.shouldShowPlayButton(player); playPauseButton.setImageDrawable(shouldShowPlayButton ? playDrawable : pauseDrawable); playPauseButton.setOnClickListener(view -> Util.handlePlayPauseButtonAction(player));
Statusaktualisierungen anhören
Der UI-Komponente muss ein Player.Listener
hinzugefügt werden, um über Statusänderungen informiert zu werden, die eine entsprechende UI-Aktualisierung erfordern. Weitere Informationen finden Sie unter Wiedergabeereignisse abhören.
Das Aktualisieren der Benutzeroberfläche kann teuer sein und oft werden mehrere Spielerereignisse gleichzeitig gesendet. Um zu vermeiden, dass die Benutzeroberfläche in kurzer Zeit zu oft aktualisiert wird, ist es im Allgemeinen besser, nur onEvents
zu hören und von dort aus Updates der Benutzeroberfläche auszulösen:
player.addListener(object : Player.Listener{ override fun onEvents(player: Player, events: Player.Events){ if (events.containsAny( Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) { updatePlayPauseButton() } if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) { updateRepeatModeButton() } } })
player.addListener(new Player.Listener() { @Override public void onEvents(Player player, Player.Events events) { if (events.containsAny( Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) { updatePlayPauseButton(); } if (events.containsAny(Player.EVENT_REPEAT_MODE_CHANGED)) { updateRepeatModeButton(); } } });
Verfügbare Befehle verarbeiten
Eine allgemeine UI-Komponente, die mit verschiedenen Player
-Implementierungen funktionieren muss, sollte die verfügbaren Playerbefehle prüfen, um Schaltflächen ein- oder auszublenden und nicht unterstützte Methoden aufzurufen:
nextButton.isEnabled = player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT)
nextButton.setEnabled(player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT));
Verschluss des ersten Frames und Bildanzeige
Wenn in einer UI-Komponente Videos oder Bilder angezeigt werden, wird in der Regel ein Platzhalter verwendet, bis der tatsächliche erste Frame oder das erste Bild verfügbar ist. Bei der gemischten Video- und Bildwiedergabe muss die Bildansicht außerdem zum richtigen Zeitpunkt ausgeblendet und eingeblendet werden.
Ein gängiges Muster zum Umgang mit diesen Aktualisierungen besteht darin, Player.Listener.onEvents()
auf Änderungen in ausgewählten Tracks (EVENT_TRACKS_CHANGED
) und auf das Rendern des ersten Videoframes (EVENT_RENDERED_FIRST_FRAME
) sowie auf ImageOutput.onImageAvailable()
auf ein neues Bild zu hören:
override fun onEvents(player: Player, events: Player.Events) { if (events.contains(Player.EVENT_TRACKS_CHANGED)) { // If no video or image track: show shutter, hide image view. // Otherwise: do nothing to wait for first frame or image. } if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) { // Hide shutter, hide image view. } } override fun onImageAvailable(presentationTimeUs: Long, bitmap: Bitmap) { // Show shutter, set image and show image view. }
@Override public void onEvents(Player player, Events events) { if (events.contains(Player.EVENT_TRACKS_CHANGED)) { // If no video or image track: show shutter, hide image view. // Otherwise: do nothing to wait for first frame or image. } if (events.contains(Player.EVENT_RENDERED_FIRST_FRAME)) { // Hide shutter, hide image view. } } @Override public void onImageAvailable(long presentationTimeUs, Bitmap bitmap) { // Show shutter, set image and show image view. }