Ulepszanie odtwarzania multimediów: wprowadzenie wstępnego wczytywania w Media3 – część 1
8 min czytania
W dzisiejszych aplikacjach multimedialnych płynne, nieprzerwane odtwarzanie jest kluczem do zapewnienia użytkownikom jak najlepszych wrażeń. Użytkownicy oczekują, że filmy będą się odtwarzać natychmiast i bez przerw.
Głównym wyzwaniem jest opóźnienie. Tradycyjnie odtwarzacz wideo rozpoczyna pracę – łączenie się, pobieranie, analizowanie, buforowanie – dopiero wtedy, gdy użytkownik wybierze element do odtworzenia. W przypadku krótkich filmów takie podejście jest zbyt wolne. Rozwiązaniem jest działanie proaktywne. Musimy przewidzieć, co użytkownik będzie oglądać dalej, i przygotować treści z wyprzedzeniem. Na tym polega wstępne wczytywanie.
Najważniejsze korzyści wstępnego wczytywania:
- 🚀 Szybsze rozpoczynanie odtwarzania: filmy są już gotowe do odtworzenia, co przyspiesza przechodzenie między elementami i umożliwia natychmiastowe rozpoczęcie odtwarzania.
- 📉 Mniejsze buforowanie: dzięki proaktywnemu wczytywaniu danych odtwarzanie jest znacznie mniej podatne na przerwy, np. spowodowane problemami z siecią.
- ✨ Lepsze wrażenia użytkowników: połączenie szybszego rozpoczynania odtwarzania i mniejszego buforowania zapewnia użytkownikom płynniejszą i bardziej komfortową interakcję.
W tej 3-częściowej serii przedstawimy i omówimy szczegółowo zaawansowane narzędzia Media3 do (wstępnego) wczytywania komponentów.
- 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 posta powinniście umieć wstępnie wczytywać i odtwarzać elementy multimedialne z skonfigurowanym rankingiem i czasem trwania.
- W części 2 omówimy bardziej zaawansowane tematy związane z DefaultPreloadManager: używanie odbiorników do analizy, sprawdzanie sprawdzonych metod gotowych do wdrożenia, takich jak wzorzec okna przesuwnego, oraz niestandardowe komponenty współdzielone DefaultPreloadManager i ExoPlayer.
- W części 3 szczegółowo omówimy buforowanie na dysku za pomocą DefaultPreloadManager.
Wstępne wczytywanie na ratunek! 🦸♀️
Podstawowa idea wstępnego wczytywania jest prosta: wczytaj treści multimedialne, zanim będą Ci potrzebne. Gdy użytkownik przesunie palcem do następnego filmu, pierwsze segmenty filmu będą już pobrane i dostępne, gotowe do natychmiastowego odtworzenia.
Wyobraź sobie restaurację. W ruchliwej kuchni nie czeka się na zamówienie, aby zacząć kroić cebulę. 🧅 Przygotowania są robione 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 dotrze do następnego elementu. Przygotowywany jest pierwszy okres następnego okna, a próbki wideo, audio i tekstu są buforowane. Wstępnie wczytany okres jest później umieszczany w kolejce odtwarzacza z buforowanymi próbkami, które są natychmiast 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 odpowiedni do różnych przypadków użycia. Pierwszym krokiem jest wybranie odpowiedniego interfejsu API.
1. Wstępne wczytywanie elementów playlisty za pomocą PreloadConfiguration
Jest to proste podejście, przydatne w przypadku liniowych, sekwencyjnych multimediów, takich jak playlisty, w których kolejność odtwarzania jest przewidywalna (np. seria odcinków). Za pomocą interfejsów API playlisty ExoPlayer podajesz odtwarzaczowi pełną listę elementów multimedialnych i ustawiasz dla niego PreloadConfiguration. Odtwarzacz automatycznie wstępnie wczytuje kolejne elementy w sekwencji zgodnie z konfiguracją. Ten interfejs API próbuje zoptymalizować opóźnienie dołączenia, gdy użytkownik przejdzie 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 potrzebujesz wstępnego wczytywania, ten interfejs API jest świetną opcją, która nie wymaga dużego nakładu pracy.
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 playlisty można je ponownie wyłączyć, używając PreloadConfiguration.DEFAULT:
player.preloadConfiguration = PreloadConfiguration.DEFAULT
2. Wstępne wczytywanie list dynamicznych za pomocą PreloadManager
W przypadku dynamicznych interfejsów, takich jak kanały pionowe czy karuzele, w których „następny” element jest określany przez interakcję użytkownika, odpowiedni jest interfejs API PreloadManager. Jest to nowy, zaawansowany, samodzielny komponent w bibliotece Media3 ExoPlayer, zaprojektowany specjalnie do proaktywnego wstępnego wczytywania. Zarządza on zbiorem potencjalnych MediaSource, ustalając ich priorytety na podstawie odległości od bieżącej pozycji użytkownika, i zapewnia szczegółową kontrolę nad tym, co ma być wstępnie wczytane. Jest to odpowiednie rozwiązanie w przypadku złożonych scenariuszy, takich jak dynamiczne kanały krótkich filmów.
Konfigurowanie PreloadManager
DefaultPreloadManager to kanoniczna implementacja PreloadManager.
Konstruktor DefaultPreloadManager może tworzyć zarówno DefaultPreloadManager, jak i instancje ExoPlayer, które będą odtwarzać wstępnie wczytane treści. Aby utworzyć DefaultPreloadManager, musisz przekazać TargetPreloadStatusControl, który menedżer wstępnego wczytywania może wysyłać zapytania, aby dowiedzieć się, ile elementów ma wczytać. 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 ExoPlayer, jak i DefaultPreloadManager, co zapewnia prawidłowe udostępnianie komponentów pod nimi.
To wszystko. Masz teraz menedżera gotowego do odbierania instrukcji.
Konfigurowanie czasu trwania i rankingu za pomocą TargetPreloadStatusControl
Co zrobić, jeśli chcesz wstępnie wczytać np. 10 sekund filmu? Możesz podać pozycję elementów multimedialnych w karuzeli, a DefaultPreloadManager ustali priorytet wczytywania elementów na podstawie tego, jak blisko są one elementu, który jest aktualnie odtwarzany przez użytkownika.
Jeśli chcesz kontrolować, jak długo ma trwać wstępne wczytywanie elementu, możesz to określić za pomocą 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, wczytaj 3 sekundy filmu.
- Element „C” ma niższy priorytet, wczytaj tylko ścieżki.
- Element „D” ma jeszcze niższy priorytet, po prostu przygotuj.
- Wszystkie inne elementy są daleko, nie wczytuj niczego.
Ta szczegółowa kontrola może pomóc 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, natomiast PreloadConfiguration będzie tylko sprawdzać kolejne elementy.
Zarządzanie wstępnie wczytywanymi elementami
Po utworzeniu menedżera możesz zacząć mu mówić, co ma robić. Gdy użytkownik przewija kanał, identyfikujesz nadchodzące filmy i dodajesz je do menedżera. Interakcja z PreloadManager to rozmowa oparta na stanie między interfejsem a silnikiem wstępnego wczytywania.
1. Dodaj elementy multimedialne
Gdy wypełniasz kanał, musisz poinformować menedżera o multimediach, które ma śledzić. Na początku możesz dodać całą listę, którą chcesz wstępnie wczytać. Następnie możesz dodawać do listy pojedyncze elementy w razie potrzeby. 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 ponowne sprawdzenie nowej listy (sugerując, że coś się zmieniło, np. dodano lub usunięto element albo użytkownik przełączył się na odtwarzanie nowego elementu).
preloadManager.invalidate()
2. Pobierz i odtwórz element
Oto główna logika odtwarzania. Gdy użytkownik zdecyduje się odtworzyć ten film, nie musisz tworzyć nowego MediaSource. Zamiast tego poproś PreloadManager o ten, który już przygotował. Za pomocą MediaItem możesz pobrać MediaSource z PreloadManager.
Jeśli pobrany element z PreloadManager ma wartość null, oznacza to, że mediaItem nie jest jeszcze wstępnie wczytany ani dodany do PreloadMamager, więc możesz ustawić mediaItem 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 MediaSource pobrany z 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 rozpoczęcia jest krótszy.
3. Synchronizuj bieżący indeks z interfejsem
Ponieważ nasz kanał lub lista mogą być dynamiczne, ważne jest, aby informować PreloadManager o bieżącym indeksie odtwarzania, aby zawsze mógł on ustalać priorytet wstępnego wczytywania elementów znajdujących się najbliżej bieżącego indeksu.
preloadManager.setCurrentPlayingIndex(currentIndex) // Need to call invalidate() to update the priorities preloadManager.invalidate()
4. Usuń element
Aby menedżer działał wydajnie, należy usuwać elementy, których nie musi już śledzić, np. elementy znajdujące 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 wyczyścić wszystkie elementy naraz, możesz wywołać preloadManager.reset().
5. Zwolnij menedżera
Gdy nie potrzebujesz już PreloadManager (np. gdy interfejs jest niszczony), musisz go zwolnić, aby zwolnić jego zasoby. Dobrym miejscem na to jest miejsce, w którym zwalniasz już zasoby odtwarzacza. Zalecamy zwolnienie menedżera przed odtwarzaczem, ponieważ odtwarzacz może nadal odtwarzać, 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ższej prezentacji po prawej stronie widzimy wpływ PreloadManager , który ma krótszy czas wczytywania, a po lewej stronie – dotychczasowe działanie. Możesz też wyświetlić przykładowy kod prezentacji. (Dodatkowo: wyświetla też opóźnienie uruchamiania każdego filmu)
Co dalej?
To już koniec części 1. Masz teraz narzędzia do tworzenia dynamicznego systemu wstępnego wczytywania. Możesz użyć PreloadConfiguration, aby wstępnie wczytać następny element playlisty w ExoPlayer, lub skonfigurować DefaultPreloadManager, dodawać i usuwać elementy w locie, skonfigurować docelowy stan wstępnego wczytywania i prawidłowo pobrać wstępnie wczytane treści do odtworzenia.
W części 2 omówimy bardziej szczegółowo DefaultPreloadManager. Dowiesz się, jak nasłuchiwać zdarzeń wstępnego wczytywania, omówimy sprawdzone metody, takie jak używanie okna przesuwnego, aby uniknąć problemów z pamięcią, oraz zajrzymy pod maskę niestandardowych komponentów współdzielonych ExoPlayer i DefaultPreloadManager.
Masz jakieś uwagi do udostępnienia? Chętnie je poznamy.
Bądź na bieżąco i przyspiesz działanie swojej aplikacji. 🚀
-
Nowości dotyczące usługWitamy w drugiej części naszej 3-częściowej serii o wstępnym wczytywaniu multimediów w Media3. Ta seria ma na celu przeprowadzenie Cię przez proces tworzenia w aplikacjach na Androida bardzo responsywnych multimediów o niskim opóźnieniu.
Mayuri Khinvasara Khabya • 9 min czytania -
Nowości dotyczące usługW marcu przedstawiliśmy Android Bench – nasz ranking LLM do zadań związanych z tworzeniem aplikacji na Androida w rzeczywistych warunkach. Od tego czasu ulepszyliśmy test porównawczy na podstawie Waszych opinii, m.in. dodając ocenę modeli o otwartych wagach oraz wymiary kosztów i wydajności.
Zoe Lopez-Latorre • 3 min czytania -
Nowości dotyczące usługW Google Play dokładamy wszelkich starań, aby zapewnić użytkownikom jak najlepsze wrażenia, a deweloperom – narzędzia i elastyczność, które pozwolą im odnieść sukces.
Paul Feng • 3 min czytania
Otrzymuj co tydzień najnowsze informacje o tworzeniu aplikacji na Androida na swoją skrzynkę odbiorczą.