Media3 udostępnia domyślny PlayerView
, który zawiera kilka opcji dostosowywania.
Zastępowanie rysunków
PlayerView
używa PlayerControlView
do wyświetlania elementów sterujących odtwarzaniem i paska postępu. Obiekty graficzne używane przez PlayerControlView
mogą zostać zastąpione przez obiekty graficzne o tych samych nazwach zdefiniowanych w aplikacji. Aby poznać listę obiektów rysowanych, które można zastąpić, zapoznaj się z dokumentacją PlayerControlView
.
W przypadku dalszych dostosowań deweloperzy aplikacji powinni zaimplementować własne komponenty interfejsu. Oto kilka sprawdzonych metod, które pomogą Ci zacząć.
Sprawdzone metody
Podczas implementowania interfejsu użytkownika multimediów, który łączy się z Media3 Player
(np. ExoPlayer
, MediaController
lub niestandardowa implementacja Player
), zalecamy stosowanie tych sprawdzonych metod, aby zapewnić jak najlepsze wrażenia z interfejsu.
Przycisk Odtwórz/Wstrzymaj
Przyciski odtwarzania i pauzy nie odpowiadają bezpośrednio stanowi pojedynczego odtwarzacza. Użytkownik powinien mieć możliwość ponownego uruchomienia odtwarzania po jego zakończeniu lub niepowodzeniu, nawet jeśli odtwarzacz nie jest wstrzymany.
Aby uprościć implementację, Media3 udostępnia przydatne metody, które pozwalają określić, który przycisk ma być wyświetlany (Util.shouldShowPlayButton
), oraz obsługiwać naciśnięcia przycisków (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));
Słuchanie informacji o stanie
Komponent interfejsu musi dodać Player.Listener
, aby otrzymywać informacje o zmianach stanu, które wymagają odpowiedniej aktualizacji interfejsu. Więcej informacji znajdziesz w sekcji Odtwarzanie zdarzeń.
Odświeżanie interfejsu może być kosztowne, a wiele zdarzeń dotyczących gracza często dociera do serwera w tym samym czasie. Aby uniknąć zbyt częstego odświeżania interfejsu w krótkim czasie, lepiej jest słuchać tylko onEvents
i w ten sposób wywoływać aktualizacje interfejsu:
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(); } } });
Obsługa dostępnych poleceń
Ogólny komponent interfejsu użytkownika, który może być używany w różnych implementacjach Player
, powinien sprawdzać dostępne polecenia odtwarzacza, aby wyświetlać lub ukrywać przyciski i unikać wywoływania nieobsługiwanych metod:
nextButton.isEnabled = player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT)
nextButton.setEnabled(player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT));
Zasłona pierwszego klatki i wyświetlanie obrazu
Gdy komponent interfejsu wyświetla film lub obrazy, zwykle używa widoku zastępczego, dopóki nie będzie dostępne pierwsze prawidłowe zdjęcie lub pierwsza prawidłowa klatka. Ponadto odtwarzanie mieszanki filmu i obrazu wymaga ukrywania i wyświetlania obrazu w odpowiednich momentach.
Typowym sposobem obsługi tych aktualizacji jest słuchanie zdarzenia Player.Listener.onEvents()
w przypadku każdej zmiany w wybranych ścieżkach (EVENT_TRACKS_CHANGED
) i w momencie wyrenderowania pierwszego kadru filmu (EVENT_RENDERED_FIRST_FRAME
), a także zdarzenia ImageOutput.onImageAvailable()
w przypadku dostępności nowego obrazu:
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. }