Wiadomości o usługach

Ulepszanie odtwarzania multimediów: wprowadzenie wstępnego wczytywania w Media3 – część 1

Czas czytania: 8 minut
Mayuri Khinvasara Khabya
Inżynier ds. relacji z deweloperami

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 displ​​ay 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).

Demo-PreloadManager_2.webp

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. 🚀

Autor:

Czytaj dalej