Nowości dotyczące usług
Ulepszanie odtwarzania multimediów: wprowadzenie wstępnego wczytywania w Media3 – część 1
8 minut czytania
W dzisiejszych aplikacjach multimedialnych kluczem do zadowolenia użytkowników jest płynne, nieprzerwane odtwarzanie. Użytkownicy oczekują, że filmy będą się odtwarzać natychmiast i płynnie, bez przerw.
Głównym problemem jest opóźnienie. Tradycyjnie odtwarzacz wideo rozpoczyna pracę – łączenie się, pobieranie, analizowanie, buforowanie – dopiero po wybraniu przez użytkownika elementu do odtworzenia. W przypadku dzisiejszych krótkich filmów takie reaktywne podejście jest zbyt wolne. Rozwiązaniem jest proaktywność. 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 natychmiastowe rozpoczynanie odtwarzania.
- 📉 Mniejsze buforowanie: dzięki proaktywnemu wczytywaniu danych odtwarzanie jest znacznie mniej podatne na przerwy, np. spowodowane problemami z siecią.
- ✨ Płynniejsze wrażenia użytkowników: połączenie szybszego rozpoczynania odtwarzania i mniejszego buforowania zapewnia użytkownikom płynniejszą i bardziej spójną interakcję.
W tej 3-częściowej serii przedstawimy i szczegółowo omówimy 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śmy móc wstępnie wczytywać i odtwarzać elementy multimedialne z skonfigurowanym rankingiem i czasem trwania.
- W części 2 zajmiemy się bardziej zaawansowanymi tematami związanymi z DefaultPreloadManager: używaniem odbiorników do analizy, sprawdzonymi metodami gotowymi do wdrożenia, takimi jak wzorzec okna przesuwnego, oraz niestandardowymi komponentami współdzielonymi 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: wczytuj 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.
Pomyśl o tym jak o restauracji. 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 dodawany do kolejki 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ą, aby je wypróbować!
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 kolekcją 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 przydatne w złożonych scenariuszach, 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()
Używanie tego samego konstruktora zarówno dla ExoPlayer, jak i DefaultPreloadManager jest konieczne, aby zapewnić 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 priorytety 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ć następne 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. Dodawanie elementów multimedialnych
Gdy wypełniasz kanał, musisz poinformować menedżera o multimediach, które ma śledzić. Na początek 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 ponowną ocenę 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. Pobieranie i odtwarzanie elementu
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ł. Możesz pobrać MediaSource z PreloadManager za pomocą MediaItem.
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, używając danych, które są już w pamięci. Dzięki temu czas rozpoczęcia jest krótszy.
3. Synchronizowanie bieżącego indeksu 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ć priorytety 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. Usuwanie elementu
Aby menedżer był wydajny, 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 usunąć wszystkie elementy naraz, możesz wywołać preloadManager.reset().
5. Zwalnianie menedżera
Gdy nie potrzebujesz już PreloadManager (np. gdy interfejs zostanie zniszczony), 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 👍
Na poniższej prezentacji po prawej stronie widać 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 zajmiemy się bardziej szczegółowo DefaultPreloadManager. Omówimy, 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! 🚀
Czytaj dalej
-
Nowości dotyczące usług
Witamy w drugiej części 3-częściowej serii o wstępnym wczytywaniu multimediów za pomocą 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 minut czytania
-
Nowości dotyczące usług
Podczas tegorocznej konferencji Google I/O mówiliśmy o naszym zmieniającym się modelu biznesowym, który oferuje większy wybór i nowe sposoby na odkrywanie Twoich aplikacji i treści w Sklepie Play i poza nim. Przedstawiliśmy też zaawansowane narzędzia i statystyki, które pomogą Ci rozwijać firmę przy mniejszej złożoności.
Paul Feng • 6 minut czytania
-
Nowości dotyczące usług
Z przyjemnością informujemy, że w Androidzie XR pojawiło się oficjalne wsparcie dla Unreal Engine i Godot. Uruchamiamy też nowe narzędzia, które zwiększą Twoją produktywność i umożliwią korzystanie z nowych funkcji XR: Android XR Engine Hub i Android XR Interaction Framework.
Luke Hopkins • 4 minuty czytania
Bądź na bieżąco
Otrzymuj co tydzień najnowsze informacje o tworzeniu aplikacji na Androida na swoją skrzynkę odbiorczą.