Stosy sieciowe

ExoPlayer zwykle służy do strumieniowego przesyłania multimediów przez internet. Obsługuje wiele stosów sieciowych na potrzeby tworzenia bazowych żądań sieciowych. Wybór stosu sieciowego może mieć duży wpływ na wydajność strumieniowania.

Na tej stronie opisujemy, jak skonfigurować ExoPlayer do korzystania z Twojego stosu sieciowego, zawiera listę dostępnych opcji, wskazówki dotyczące wyboru stosu sieciowego dla aplikacji i włączanie buforowania w przypadku strumieniowanych multimediów.

Konfigurowanie ExoPlayer do korzystania z określonego stosu sieciowego

ExoPlayer wczytuje dane przez komponenty DataSource, które uzyskuje z DataSource.Factory instancji wstrzykniętych z kodu aplikacji.

Jeśli Twoja aplikacja ma odtwarzać tylko zawartość HTTP, wybór stosu sieciowego jest równie łatwy jak zaktualizowanie wstrzykiwanych przez nią instancji DataSource.Factory w taki sposób, aby były instancjami HttpDataSource.Factory odpowiadającym stosowi sieci, którego chcesz używać. Jeśli aplikacja musi też odtwarzać treści inne niż HTTP, na przykład pliki lokalne, użyj DefaultDataSource.Factory:

Kotlin

DefaultDataSource.Factory(
  ...
  /* baseDataSourceFactory= */ PreferredHttpDataSource.Factory(...))

Java

new DefaultDataSource.Factory(
    ...
    /* baseDataSourceFactory= */ new PreferredHttpDataSource.Factory(...));

W tym przykładzie PreferredHttpDataSource.Factory to fabryka odpowiadająca Twojemu preferowanemu stosie sieci. Warstwa DefaultDataSource.Factory umożliwia obsługę źródeł innych niż HTTP, na przykład plików lokalnych.

Poniższy przykład pokazuje, jak utworzyć obiekt ExoPlayer, który będzie korzystać ze stosu sieci Cronet oraz obsługiwać odtwarzanie treści innych niż HTTP.

Kotlin

// Given a CronetEngine and Executor, build a CronetDataSource.Factory.
val cronetDataSourceFactory = CronetDataSource.Factory(cronetEngine, executor)

// Wrap the CronetDataSource.Factory in a DefaultDataSource.Factory, which adds
// in support for requesting data from other sources (such as files, resources,
// etc).
val dataSourceFactory =
  DefaultDataSource.Factory(context, /* baseDataSourceFactory= */ cronetDataSourceFactory)

// Inject the DefaultDataSource.Factory when creating the player.
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
    )
    .build()

Java

// Given a CronetEngine and Executor, build a CronetDataSource.Factory.
CronetDataSource.Factory cronetDataSourceFactory =
    new CronetDataSource.Factory(cronetEngine, executor);

// Wrap the CronetDataSource.Factory in a DefaultDataSource.Factory, which adds
// in support for requesting data from other sources (such as files, resources,
// etc).
DefaultDataSource.Factory dataSourceFactory =
    new DefaultDataSource.Factory(
        context, /* baseDataSourceFactory= */ cronetDataSourceFactory);

// Inject the DefaultDataSource.Factory when creating the player.
ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory))
        .build();

Obsługiwane stosy sieciowe

ExoPlayer bezpośrednio obsługuje wbudowane stosy sieciowe Cronet, OkHttp i Android. Usługę ExoPlayer można też rozszerzyć, aby obsługiwała dowolny inny stos sieciowy, który działa na Androidzie.

Cronet

Cronet to stos sieciowy Chromium udostępniony jako biblioteka aplikacjom na Androida. Cronet korzysta z wielu technologii, które zmniejszają opóźnienia i zwiększają przepustowość żądań sieciowych potrzebnych aplikacji do działania, w tym tych realizowanych przez ExoPlayer. Natywnie obsługuje on protokoły HTTP, HTTP/2 i HTTP/3 przez protokoły QUIC. Aplikacja Cronet jest używana w największych aplikacjach do odtwarzania strumieniowego, w tym YouTube.

ExoPlayer obsługuje Cronet za pomocą biblioteki Cronet. Szczegółowe informacje o tym, jak z niej korzystać, znajdziesz na stronie README.md biblioteki. Pamiętaj, że biblioteka Cronet może korzystać z 3 podstawowych implementacji Cronet:

  1. Usługi Google Play: zalecamy skorzystanie z tej implementacji w większości przypadków oraz powrót do wbudowanego stosu sieciowego Androida (DefaultHttpDataSource), jeśli Usługi Google Play są niedostępne.
  2. Cronet Embedded: może być dobrym rozwiązaniem, jeśli duży odsetek użytkowników znajduje się na rynkach, na których Usługi Google Play nie są powszechnie dostępne, lub jeśli chcesz kontrolować dokładną wersję używanej implementacji Cronet. Główną wadą Cronet Embedded jest to, że aplikacje zajmują około 8 MB.
  3. Cronet Fallback: w przypadku implementacji zastępczej Cronet implementuje interfejs API aplikacji Cronet jako otokę wbudowanego stosu sieciowego Androida. Nie należy go używać z ExoPlayer, ponieważ bezpośrednie użycie wbudowanego stosu sieciowego Androida (DefaultHttpDataSource) jest bardziej efektywne.

OkHttp

OkHttp to kolejny nowoczesny stos sieci, który jest powszechnie używany przez wiele popularnych aplikacji na Androida. Obsługuje HTTP i HTTP/2, ale jeszcze nie obsługuje HTTP/3 przez QUIC.

ExoPlayer obsługuje OkHttp za pomocą biblioteki OkHttp. Szczegółowe informacje o tym, jak z niej korzystać, znajdziesz na stronie README.md biblioteki. Gdy używasz biblioteki OkHttp, stos sieci jest osadzony w aplikacji. Podobnie jest do Cronet Embedded, jednak OkHttp jest znacznie mniejszy i powiększa się o mniej niż 1 MB.

Wbudowany stos sieciowy w Androidzie

ExoPlayer korzysta z wbudowanego stosu sieciowego Androida z funkcjami DefaultHttpDataSource i DefaultHttpDataSource.Factory, które wchodzą w skład głównej biblioteki ExoPlayer.

Dokładna implementacja stosu sieciowego zależy od oprogramowania działającego na odpowiednim urządzeniu. Na większości urządzeń (od 2021 r.) obsługiwany jest tylko protokół HTTP (tzn. protokoły HTTP/2 i HTTP/3 przez QUIC nie są obsługiwane).

Inne stosy sieciowe

Aplikacje mogą też integrować z ExoPlayer inne stosy sieciowe. Aby to zrobić, zaimplementuj HttpDataSource opakowujący stos sieciowy razem z odpowiadającym mu HttpDataSource.Factory. Dobrymi przykładami są biblioteki Cronet i OkHttp firmy ExoPlayer.

W przypadku integracji ze stosem sieciowym w języku Java warto wdrożyć DataSourceContractTest, aby sprawdzić, czy implementacja HttpDataSource działa prawidłowo. OkHttpDataSourceContractTest w bibliotece OkHttp to dobry przykład tego, jak to zrobić.

Wybieranie stosu sieciowego

W tabeli poniżej zebraliśmy wady i zalety stosów sieciowych obsługiwanych przez ExoPlayer.

Stos sieciowy Protokoły Wpływ na rozmiar pliku APK Uwagi
Cronet (Usługi Google Play) HTTP
HTTP/2
HTTP/3 przez QUIC
Mały
(<100 KB)
Wymaga Usług Google Play. Wersja Cronet jest aktualizowana automatycznie
Cronet (wbudowany) HTTP
HTTP/2
HTTP/3 przez QUIC
Duży
(ok. 8 MB)
Wersja Cronet kontrolowana przez dewelopera aplikacji
Cronet (kreacja zastępcza) HTTP
(różni się w zależności od urządzenia)
Mały
(<100 KB)
Niezalecane w przypadku odtwarzacza ExoPlayer
OkHttp HTTP
HTTP/2
Mały
(<1 MB)
Wymaga środowiska wykonawczego Kotlin
Wbudowany stos sieciowy HTTP
(różni się w zależności od urządzenia)
Brak Implementacja różni się w zależności od urządzenia

HTTP/2 i HTTP/3 za pośrednictwem protokołów QUIC mogą znacznie zwiększyć wydajność strumieniowego przesyłania multimediów. W szczególności w przypadku strumieniowania adaptacyjnych multimediów rozpowszechnianych za pomocą sieci dystrybucji treści (CDN) są sytuacje, w których zastosowanie tych protokołów może umożliwić temu sieciom znacznie wydajniejsze działanie. Z tego powodu obsługa HTTP/2 i HTTP/3 w Cronet przez QUIC (a OkHttp przez OkHttp – HTTP/2) ma dużą zaletę w porównaniu z wbudowanym stosem sieciowym Androida, o ile serwery, na których hostowane są treści, również obsługują te protokoły.

Jeśli rozważasz strumieniowe przesyłanie multimediów osobno, zalecamy korzystanie z usługi Cronet w ramach Usług Google Play. Jeśli Usługi Google Play są niedostępne, użyj DefaultHttpDataSource. To zalecenie pozwala zachować równowagę między włączeniem HTTP/2 i HTTP/3 przez QUIC na większości urządzeń do uniknięcia znacznego zwiększenia rozmiaru pliku APK. Od tej rekomendacji są wyjątki. W przypadkach, w których Usługi Google Play mogą być niedostępne na znacznej części urządzeń, na których będzie działać Twoja aplikacja, bardziej odpowiednie może być użycie Cronet Embedded lub OkHttp. Korzystanie z wbudowanego stosu sieciowego może być dopuszczalne, jeśli rozmiar pliku APK ma kluczowe znaczenie lub gdy strumieniowanie multimediów jest tylko ułamkiem funkcji aplikacji.

Oprócz multimediów dobrze jest wybrać jeden stos sieciowy dla wszystkich sieci używanych przez aplikację. Dzięki temu zasoby (np. gniazda) mogą być efektywnie łączone i współdzielone między ExoPlayerem a innymi komponentami aplikacji.

Aplikacja najprawdopodobniej będzie musiała obsługiwać sieci niezwiązane z odtwarzaniem multimediów, dlatego wybór stosu sieci powinien być zgodny z naszymi powyższymi zaleceniami dotyczącymi strumieniowania multimediów, a także wymaganiami innych komponentów obsługujących sieci oraz ich względnym znaczeniem dla aplikacji.

Buforuję multimedia

ExoPlayer obsługuje buforowanie załadowanych bajtów na dysku, aby zapobiegać wielokrotnemu ładowaniu tych samych bajtów z sieci. Jest to przydatne podczas przewijania dotychczasowych multimediów lub powtarzania tego samego elementu.

Buforowanie wymaga instancji SimpleCache wskazującej dedykowany katalog pamięci podręcznej oraz CacheDataSource.Factory:

Kotlin

// Note: This should be a singleton in your app.
val databaseProvider = StandaloneDatabaseProvider(context)

// An on-the-fly cache should evict media when reaching a maximum disk space limit.
val cache =
    SimpleCache(
        downloadDirectory, LeastRecentlyUsedCacheEvictor(maxBytes), databaseProvider)

// Configure the DataSource.Factory with the cache and factory for the desired HTTP stack.
val cacheDataSourceFactory =
    CacheDataSource.Factory()
        .setCache(cache)
        .setUpstreamDataSourceFactory(httpDataSourceFactory)

// Inject the DefaultDataSource.Factory when creating the player.
val player =
    ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory))
        .build()

Java

// Note: This should be a singleton in your app.
DatabaseProvider databaseProvider = new StandaloneDatabaseProvider(context);

// An on-the-fly cache should evict media when reaching a maximum disk space limit.
Cache cache =
    new SimpleCache(
        downloadDirectory, new LeastRecentlyUsedCacheEvictor(maxBytes), databaseProvider);

// Configure the DataSource.Factory with the cache and factory for the desired HTTP stack.
DataSource.Factory cacheDataSourceFactory =
    new CacheDataSource.Factory()
        .setCache(cache)
        .setUpstreamDataSourceFactory(httpDataSourceFactory);

// Inject the DefaultDataSource.Factory when creating the player.
ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory))
        .build();