Wiadomości o usługach
Ulepszanie odtwarzania multimediów: wprowadzenie wstępnego wczytywania w Media3 – część 1
Czas czytania: 8 minut
W dzisiejszych aplikacjach multimedialnych zapewnienie płynnego, nieprzerwanego odtwarzania jest kluczowe dla pozytywnych wrażeń użytkowników. Użytkownicy oczekują, że filmy będą odtwarzane natychmiast i płynnie, bez przerw.
Głównym wyzwaniem jest opóźnienie. Zazwyczaj odtwarzacz wideo rozpoczyna działanie – łączenie się, pobieranie, analizowanie i buforowanie – dopiero po wybraniu przez użytkownika elementu do odtworzenia. W kontekście krótkich filmów takie reaktywne podejście jest zbyt powolne. Rozwiązaniem jest proaktywne działanie. Musimy przewidzieć, co użytkownik będzie oglądać dalej, i przygotować te treści z wyprzedzeniem. Na tym polega wstępne wczytywanie.
Najważniejsze zalety wstępnego wczytywania:
- 🚀 Szybsze rozpoczęcie odtwarzania: filmy są już gotowe do odtworzenia, co zapewnia szybsze przejścia między elementami i natychmiastowe rozpoczęcie odtwarzania.
- 📉 Mniejsze buforowanie: dzięki proaktywnemu wczytywaniu danych odtwarzanie jest znacznie mniej podatne na przerwy, np. z powodu problemów z siecią.
- ✨ Płynniejsze działanie: połączenie szybszego uruchamiania i mniejszego buforowania zapewnia użytkownikom płynniejszą i wygodniejszą interakcję.
W tej 3-częściowej serii przedstawimy zaawansowane narzędzia Media3 do (wstępnego) wczytywania komponentów i omówimy je szczegółowo.
- W części 1 omówimy podstawy: różne strategie wstępnego wczytywania dostępne w Media3, włączanie PreloadConfiguration i konfigurowanie DefaultPreloadManager oraz włączanie wstępnego wczytywania elementów w aplikacji. Po przeczytaniu tego bloga będziesz w stanie wstępnie wczytywać i odtwarzać elementy multimedialne z określonym rankingiem i czasem trwania.
- W części 2 omówimy bardziej zaawansowane tematy związane z klasą DefaultPreloadManager: używanie odbiorników do analizy, poznawanie sprawdzonych metod gotowych do wdrożenia w środowisku produkcyjnym, takich jak wzorzec okna przesuwnego i niestandardowe komponenty udostępnione klasy DefaultPreloadManager i ExoPlayer.
- W części 3 szczegółowo omówimy buforowanie dysku za pomocą klasy DefaultPreloadManager.
Wstępne wczytywanie na ratunek 🦸♀️
Podstawowa idea wstępnego wczytywania jest prosta: wczytaj treści multimedialne, zanim będą potrzebne. Zanim użytkownik przesunie palcem do następnego filmu, pierwsze segmenty filmu są już pobrane i dostępne, gotowe do natychmiastowego odtworzenia.
Pomyśl o tym jak o restauracji. W ruchliwej kuchni nie czeka się na zamówienie, aby zacząć kroić cebulę. 🧅 Przygotowują się z wyprzedzeniem. Wstępne wczytywanie to przygotowanie odtwarzacza wideo.
Gdy wstępne wczytywanie jest włączone, może pomóc zminimalizować opóźnienie dołączenia, gdy użytkownik przejdzie do następnego elementu, zanim bufor odtwarzania osiągnie ten element. Przygotowywany jest pierwszy okres następnego okna, a próbki wideo, audio i tekstu są buforowane. Wstępnie załadowany okres jest później umieszczany w kolejce odtwarzacza z buforowanymi próbkami, które są od razu dostępne i gotowe do przekazania do kodeka w celu renderowania.
W Media3 są 2 główne interfejsy API do wstępnego wczytywania, z których każdy jest przeznaczony do innych przypadków użycia. Pierwszym krokiem jest wybór odpowiedniego interfejsu API.
1. Wstępne wczytywanie elementów playlisty za pomocą PreloadConfiguration
Jest to proste podejście przydatne w przypadku liniowych, sekwencyjnych treści multimedialnych, takich jak playlisty, w których kolejność odtwarzania jest przewidywalna (np. seria odcinków). Przekazujesz odtwarzaczowi pełną listę elementów multimedialnych za pomocą interfejsów API listy odtwarzania ExoPlayera i ustawiasz PreloadConfiguration dla odtwarzacza, a następnie automatycznie wstępnie wczytuje on kolejne elementy w sekwencji zgodnie z konfiguracją. Ten interfejs API próbuje zoptymalizować opóźnienie dołączenia, gdy użytkownik przechodzi do następnego elementu, zanim bufor odtwarzania zacznie się nakładać na następny element.
Wstępne wczytywanie rozpoczyna się tylko wtedy, gdy nie są wczytywane żadne multimedia do bieżącego odtwarzania, co zapobiega konkurowaniu o przepustowość z głównym odtwarzaniem.
Jeśli nadal nie masz pewności, czy wstępne wczytywanie jest Ci potrzebne, ten interfejs API to świetna opcja, aby to sprawdzić.
player.preloadConfiguration =
PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)
W przypadku powyższej konfiguracji PreloadConfiguration odtwarzacz próbuje wstępnie wczytać 5 sekund multimediów dla następnego elementu na playliście.
Po włączeniu wstępnego wczytywania playlist można je ponownie wyłączyć, klikając PreloadConfiguration.DEFAULT:
player.preloadConfiguration = PreloadConfiguration.DEFAULT
2. Wstępne ładowanie list dynamicznych za pomocą klasy PreloadManager
W przypadku dynamicznych interfejsów, takich jak pionowe pliki danych czy karuzele, w których „następny” element jest określany przez interakcję użytkownika, odpowiedni jest interfejs PreloadManager API. Jest to nowy, zaawansowany, samodzielny komponent w bibliotece Media3 ExoPlayer, który został zaprojektowany specjalnie do proaktywnego wstępnego wczytywania. Zarządza kolekcją potencjalnych obiektów MediaSource, nadając im priorytet na podstawie odległości od bieżącej pozycji użytkownika, i zapewnia szczegółową kontrolę nad tym, co ma być wstępnie wczytywane. Jest to przydatne w złożonych scenariuszach, takich jak dynamiczne kanały krótkich filmów.
Konfigurowanie menedżera wstępnego wczytywania
DefaultPreloadManager to kanoniczna implementacja klasy PreloadManager.
Obiekt DefaultPreloadManager może tworzyć zarówno instancje DefaultPreloadManager, jak i dowolne instancje ExoPlayer, które będą odtwarzać wstępnie załadowane treści. Aby utworzyć obiekt DefaultPreloadManager, musisz przekazać obiekt TargetPreloadStatusControl, o który menedżer wstępnego wczytywania może wysyłać zapytania, aby dowiedzieć się, ile danych wczytać w przypadku danego elementu. W sekcji poniżej wyjaśnimy i zdefiniujemy przykład TargetPreloadStatusControl.
val preloadManagerBuilder = DefaultPreloadManager.Builder(context, targetPreloadStatusControl) val preloadManager = val preloadManagerBuilder.build() // Build ExoPlayer with DefaultPreloadManager.Builder val player = preloadManagerBuilder.buildExoPlayer()
Konieczne jest użycie tego samego konstruktora zarówno w przypadku ExoPlayera, jak i DefaultPreloadManager, co zapewnia prawidłowe udostępnianie komponentów wewnętrznych.
To wszystko. Masz teraz menedżera gotowego do otrzymywania instrukcji.
Konfigurowanie czasu trwania i rankingu za pomocą parametru TargetPreloadStatusControl
Co zrobić, jeśli chcesz wstępnie wczytać na przykład 10 sekund filmu? Możesz podać pozycję elementów multimedialnych w karuzeli, a klasa DefaultPreloadManager ustawi priorytet wczytywania elementów na podstawie tego, jak blisko są one elementu, który użytkownik aktualnie odtwarza.
Jeśli chcesz określić, jak długo ma trwać wstępne wczytywanie elementu, możesz to zrobić, zwracając wartość DefaultPreloadManager.PreloadStatus.
Na przykład
- Element „A” ma najwyższy priorytet, wczytaj 5 sekund filmu.
- Element „B” ma średni priorytet, ale gdy do niego dotrzesz, załaduj 3 sekundy filmu.
- Element „C” ma niższy priorytet, więc śledzone są tylko wczytania.
- Element „D” ma jeszcze niższy priorytet, więc wystarczy się przygotować.
- Pozostałe elementy są daleko. Nie wczytuj niczego z wyprzedzeniem.
Ta szczegółowa kontrola może pomóc Ci zoptymalizować wykorzystanie zasobów, co jest zalecane w celu zapewnienia płynnego odtwarzania.
import androidx.media3.exoplayer.DefaultPreloadManager.PreloadStatus
class MyTargetPreloadStatusControl(
currentPlayingIndex: Int = C.INDEX_UNSET
) : TargetPreloadStatusControl<Int,PreloadStatus> {
// The app is responsible for updating this based on UI state
override fun getTargetPreloadStatus(index: Int): PreloadStatus? {
val distance = index - currentPlayingIndex
// Adjacent items (Next): preload 5 seconds
if (distance == 1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED and suggest loading // 5000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(5000L)
}
// Adjacent items (Previous): preload 3 seconds
else if (distance == -1) {
// Return a PreloadStatus that is labelled by STAGE_SPECIFIED_RANGE_LOADED //and suggest loading 3000ms from the default start position
return PreloadStatus.specifiedRangeLoaded(3000L)
}
// Items two positions away: just select tracks
else if (distance) == 2) {
// Return a PreloadStatus that is labelled by STAGE_TRACKS_SELECTED
return PreloadStatus.TRACKS_SELECTED
}
// Items four positions away: just select prepare
else if (abs(distance) <= 4) {
// Return a PreloadStatus that is labelled by STAGE_SOURCE_PREPARED
return PreloadStatus.SOURCE_PREPARED
}
// All other items are too far away
return null
}
}
Wskazówka: PreloadManager może wstępnie wczytywać zarówno poprzednie, jak i następne elementy, podczas gdy PreloadConfiguration będzie tylko wyprzedzać następne elementy.
Zarządzanie elementami wstępnego wczytywania
Po utworzeniu menedżera możesz zacząć mu mówić, nad czym ma pracować. Gdy użytkownik przewija źródło, identyfikujesz nadchodzące filmy i dodajesz je do menedżera. Interakcja z klasą PreloadManager to oparta na stanie rozmowa między interfejsem a silnikiem wstępnego wczytywania.
1. Dodawanie elementów multimedialnych
Podczas wypełniania pliku danych musisz poinformować menedżera o mediach, które ma śledzić. Jeśli dopiero zaczynasz, możesz dodać całą listę, którą chcesz wstępnie wczytać. Następnie możesz w razie potrzeby dodawać do listy pojedyncze produkty. Masz pełną kontrolę nad tym, jakie elementy znajdują się na liście wstępnego wczytywania, co oznacza, że musisz też zarządzać tym, co jest dodawane i usuwane z menedżera.
val initialMediaItems = pullMediaItemsFromService(/* count= */ 20)
for (index in 0 until initialMediaItems.size) {
preloadManager.add(
initialMediaItems.get(index),index)
)
}
Menedżer zacznie teraz pobierać dane dla tego MediaItem w tle.
Po dodaniu elementu poproś menedżera o ponowną ocenę nowej listy (sugerując, że coś się zmieniło, np. dodano lub usunięto element albo użytkownik zaczął odtwarzać nowy element).
preloadManager.invalidate()
2. Odtwarzanie pobranego elementu
Teraz przejdziemy do głównej logiki odtwarzania. Gdy użytkownik zdecyduje się odtworzyć ten film, nie musisz tworzyć nowego obiektu MediaSource. Zamiast tego poproś PreloadManager o wersję, którą już przygotował. Obiekt MediaSource możesz pobrać z Menedżera wstępnego wczytywania za pomocą obiektu MediaItem.
Jeśli pobrany element z klasy PreloadManager ma wartość null, oznacza to, że element mediaItem nie został jeszcze wstępnie wczytany ani dodany do klasy PreloadManager, więc możesz ustawić go bezpośrednio.
// When a media item is about to display on the screen
val mediaSource = preloadManager.getMediaSource(mediaItem)
if (mediaSource!= null) {
player.setMediaSource(mediaSource)
} else {
// If mediaSource is null, that mediaItem hasn't been added yet.
// So, send it directly to the player.
player.setMediaItem(mediaItem)
}
player.prepare()
// When the media item is displaying at the center of the screen
player.play()
Przygotowując element MediaSource pobrany z klasy PreloadManager, możesz płynnie przejść od wstępnego wczytywania do odtwarzania, korzystając z danych, które są już w pamięci. Dzięki temu czas uruchamiania jest krótszy.
3. Synchronizowanie bieżącego indeksu z interfejsem
Ponieważ nasza lista może być dynamiczna, ważne jest, aby powiadomić PreloadManager o bieżącym indeksie odtwarzania, aby zawsze mógł on traktować priorytetowo elementy najbliższe bieżącemu indeksowi w celu wstępnego wczytywania.
preloadManager.setCurrentPlayingIndex(currentIndex) // Need to call invalidate() to update the priorities preloadManager.invalidate()
4. Usuwanie elementu
Aby menedżer działał wydajnie, usuń z niego elementy, których nie musi już śledzić, np. te, które znajdują się daleko od bieżącej pozycji użytkownika.
// When an item is too far from the current playing index preloadManager.remove(mediaItem)
Jeśli chcesz usunąć wszystkie elementy naraz, możesz wywołać metodę preloadManager.reset().
5. Zwolnij menedżera
Gdy nie potrzebujesz już menedżera wstępnego wczytywania (np. po zniszczeniu interfejsu), musisz go zwolnić, aby zwolnić jego zasoby. Najlepiej zrobić to w miejscu, w którym udostępniasz już zasoby odtwarzacza. Zalecamy zwolnienie menedżera przed odtwarzaczem, ponieważ odtwarzacz może nadal odtwarzać treści, jeśli nie potrzebujesz już wstępnego wczytywania.
// In your Activity's onDestroy() or Composable's onDispose preloadManager.release()
Czas na prezentację
Sprawdź, jak to działa w praktyce 👍
Na poniższym filmie po prawej stronie widać wpływ PreloadManager na szybkość wczytywania, a po lewej stronie – obecne działanie. Możesz też wyświetlić przykładowy kod wersji demonstracyjnej. (Dodatkowo wyświetla czas oczekiwania na uruchomienie każdego filmu).
Co dalej?
To już wszystko w części 1. Masz teraz narzędzia do tworzenia dynamicznego systemu wstępnego wczytywania. Możesz użyć PreloadConfiguration, aby wstępnie wczytać kolejny element z listy odtwarzania w ExoPlayerze, lub skonfigurować DefaultPreloadManager, dodawać i usuwać elementy na bieżąco, skonfigurować docelowy stan wstępnego wczytywania i prawidłowo pobierać wstępnie wczytane treści do odtwarzania.
W części 2 omówimy dokładniej DefaultPreloadManager. Omówimy, jak nasłuchiwać zdarzeń wstępnego wczytywania, przedstawimy sprawdzone metody, takie jak używanie okna przesuwnego, aby uniknąć problemów z pamięcią, oraz przyjrzymy się niestandardowym komponentom współdzielonym ExoPlayera i klasy DefaultPreloadManager.
Czy masz jakieś uwagi, którymi chcesz się podzielić? Czekamy na Twoją opinię.
Śledź nasze aktualności i przyspiesz działanie swojej aplikacji. 🚀
Czytaj dalej
-
Wiadomości o usługach
Witamy w drugiej części naszej 3-częściowej serii o wstępnym wczytywaniu multimediów za pomocą Media3. Ta seria artykułów ma Cię poprowadzić przez proces tworzenia w aplikacjach na Androida wysoce responsywnych multimediów o niskim opóźnieniu.
Mayuri Khinvasara Khabya • Czas czytania: 9 minut
-
Wiadomości o usługach
Android Studio Panda 4 jest już stabilny i możesz go używać w środowisku produkcyjnym. Wprowadziliśmy m.in. tryb planowania i przewidywanie kolejnych zmian, dzięki czemu tworzenie wysokiej jakości aplikacji na Androida jest jeszcze łatwiejsze.
Matt Dyor • Czas czytania: 5 minut
-
Wiadomości o usługach
Jeśli jesteś deweloperem aplikacji na Androida i chcesz wdrożyć w niej innowacyjne funkcje oparte na AI, niedawno udostępniliśmy nowe, zaawansowane aktualizacje.
Thomas Ezan • Czas czytania: 3 minuty
Bądź na bieżąco
Otrzymuj co tydzień najnowsze informacje o tworzeniu aplikacji na Androida na swoją skrzynkę odbiorczą.