Stosy sieciowe

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

Na tej stronie dowiesz się, jak skonfigurować ExoPlayera, aby używał wybranego stosu sieciowego. Znajdziesz tu też listę dostępnych opcji, wskazówki dotyczące wyboru stosu sieciowego dla aplikacji oraz instrukcje włączania buforowania multimediów strumieniowych.

Konfigurowanie ExoPlayera pod kątem używania określonego stosu sieciowego

ExoPlayer wczytuje dane za pomocą komponentów DataSource, które uzyskuje z instancji DataSource.Factory wstrzykiwanych z kodu aplikacji.

Jeśli aplikacja musi odtwarzać tylko treści http(s), wybór stosu sieciowego jest tak prosty jak zaktualizowanie wszystkich instancji DataSource.Factory, które aplikacja wstrzykuje, aby były instancjami HttpDataSource.Factory odpowiadającymi stosowi sieciowemu, którego chcesz używać. Jeśli aplikacja musi też odtwarzać treści inne niż http(s), np. pliki lokalne, użyj DefaultDataSource.Factory:

Kotlin

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

Java

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

W tym przykładzie PreferredHttpDataSource.Factory to fabryka odpowiadająca preferowanemu stosowi sieciowemu. Warstwa DefaultDataSource.Factory dodaje obsługę źródeł innych niż http(s), takich jak pliki lokalne.

Poniższy przykład pokazuje, jak utworzyć ExoPlayer, który będzie używać stosu sieciowego Cronet i obsługiwać odtwarzanie treści innych niż http(s).

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 zapewnia bezpośrednią obsługę HttpEngine, Cronet, OkHttp i wbudowanego domyślnego stosu sieciowego Androida. ExoPlayer można też rozszerzyć, aby obsługiwał dowolny inny stos sieciowy działający na Androidzie.

HttpEngine

HttpEngine to zalecany domyślny stos sieciowy na Androidzie od interfejsu API 34 (lub rozszerzeń S 7). W większości przypadków używa wewnętrznie stosu sieciowego Cronet, obsługując protokoły HTTP, HTTP/2 i HTTP/3 przez QUIC.

ExoPlayer obsługuje HttpEngine za pomocą HttpEngineDataSource.Factory. Możesz wstrzyknąć tę fabrykę źródeł danych w sposób opisany w sekcji Konfigurowanie ExoPlayera pod kątem używania określonego stosu sieciowego.

Cronet

Cronet to stos sieciowy Chromium udostępniany aplikacjom na Androida jako biblioteka. Cronet wykorzystuje wiele technologii, które zmniejszają opóźnienia i zwiększają przepustowość żądań sieciowych, których aplikacja potrzebuje do działania, w tym żądań wysyłanych przez ExoPlayer. Natywnie obsługuje protokoły HTTP, HTTP/2 i HTTP/3 przez QUIC. Z Cronet korzystają niektóre z największych aplikacji do odtwarzania strumieniowego na świecie, w tym YouTube.

ExoPlayer obsługuje Cronet za pomocą biblioteki Cronet. Szczegółowe instrukcje dotyczące korzystania z biblioteki znajdziesz w README.md. Pamiętaj, że biblioteka Cronet może korzystać z 3 podstawowych implementacji Cronet:

  1. Usługi Google Play: w większości przypadków zalecamy używanie tej implementacji, a jeśli usługi Google Play są niedostępne, użycie wbudowanego stosu sieciowego Androida (DefaultHttpDataSource).
  2. Cronet Embedded: może być dobrym wyborem, 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ą wbudowanego Croneta jest to, że zwiększa rozmiar aplikacji o około 8 MB.
  3. Cronet Fallback: implementacja zastępcza Cronet implementuje interfejs API Cronet jako kod wokół wbudowanego stosu sieciowego Androida. Nie należy go używać z ExoPlayerem, ponieważ bezpośrednie korzystanie z wbudowanego stosu sieciowego Androida (za pomocą DefaultHttpDataSource) jest bardziej wydajne.

OkHttp

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

ExoPlayer obsługuje OkHttp za pomocą biblioteki OkHttp. Szczegółowe instrukcje dotyczące korzystania z biblioteki znajdziesz w README.md. W przypadku korzystania z biblioteki OkHttp stos sieciowy jest umieszczony w aplikacji. Jest to podobne do Cronet Embedded, ale OkHttp jest znacznie mniejszy i zwiększa rozmiar aplikacji o mniej niż 1 MB.

Wbudowany stos sieciowy Androida

ExoPlayer obsługuje wbudowany stos sieciowy Androida z DefaultHttpDataSource i DefaultHttpDataSource.Factory, które są częścią podstawowej biblioteki ExoPlayer.

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

Inne stosy sieciowe

Aplikacje mogą też integrować z ExoPlayerem inne stosy sieciowe. Aby to zrobić, zaimplementuj interfejs HttpDataSource, który opakowuje stos sieciowy, wraz z odpowiednim interfejsem HttpDataSource.Factory. Dobrym przykładem są biblioteki Cronet i OkHttp ExoPlayera.

Podczas integracji ze stosem sieciowym opartym wyłącznie na języku Java warto zaimplementować DataSourceContractTest, aby sprawdzić, czy implementacja HttpDataSource działa prawidłowo. Dobrym przykładem jest OkHttpDataSourceContractTest w bibliotece OkHttp.

Wybór stosu sieciowego

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

Stos sieciowy Protokoły Wpływ na rozmiar pliku APK Uwagi
HttpEngine HTTP
HTTP/2
HTTP/3 przez QUIC
Brak Dostępne tylko w interfejsie API 34 lub S Extensions 7
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
(~8 MB)
Wersja Cronet kontrolowana przez dewelopera aplikacji
Cronet (funkcja zastępcza) HTTP
(zależy od urządzenia)
Mały
(<100 KB)
Niezalecane w przypadku ExoPlayera
OkHttp HTTP
HTTP/2
Mały
(<1 MB)
Wbudowany stos sieciowy HTTP
(zależy od urządzenia)
Brak Implementacja różni się w zależności od urządzenia

Protokoły HTTP/2 i HTTP/3 przez QUIC mogą znacznie poprawić wydajność strumieniowego przesyłania multimediów. W szczególności w przypadku strumieniowego przesyłania multimediów adaptacyjnych, które są rozpowszechniane za pomocą sieci dystrybucji treści (CDN), istnieją przypadki, w których użycie tych protokołów może umożliwić sieciom CDN znacznie wydajniejsze działanie. Z tego powodu obsługa protokołów HTTP/2 i HTTP/3 przez QUIC w HttpEngine i Cronet (oraz obsługa HTTP/2 w OkHttp) jest dużą zaletą w porównaniu z używaniem wbudowanego stosu sieciowego Androida, pod warunkiem że serwery, na których hostowane są treści, również obsługują te protokoły.

Jeśli chodzi o sam streaming multimediów, zalecamy używanie silnika HttpEngine lub Cronet dostarczanego przez Usługi Google Play, a w razie ich niedostępności – DefaultHttpDataSource. To zalecenie pozwala zachować równowagę między umożliwieniem korzystania z protokołów HTTP/2 i HTTP/3 przez QUIC na większości urządzeń a uniknięciem znacznego zwiększenia rozmiaru pliku APK. Od tej rekomendacji są wyjątki. W przypadkach, gdy usługi Google Play mogą być niedostępne na znacznej części urządzeń, na których będzie działać Twoja aplikacja, lepszym rozwiązaniem może być użycie Cronet Embedded lub OkHttp. Użycie wbudowanego stosu sieciowego może być akceptowalne, jeśli rozmiar pliku APK jest kluczowy lub jeśli strumieniowe przesyłanie multimediów jest tylko niewielką częścią funkcjonalności aplikacji.

Oprócz multimediów zwykle warto wybrać jeden stos sieciowy dla wszystkich operacji sieciowych wykonywanych przez aplikację. Dzięki temu zasoby (takie jak gniazda) mogą być efektywnie łączone i udostępniane między ExoPlayerem a innymi komponentami aplikacji.

Ponieważ aplikacja najprawdopodobniej będzie musiała wykonywać operacje sieciowe niezwiązane z odtwarzaniem multimediów, wybór stosu sieciowego powinien ostatecznie uwzględniać nasze zalecenia dotyczące odtwarzania strumieniowego multimediów, wymagania innych komponentów, które wykonują operacje sieciowe, oraz ich względne znaczenie dla aplikacji.

Zapisywanie multimediów w pamięci podręcznej

ExoPlayer obsługuje buforowanie wczytanych bajtów na dysku, aby zapobiec wielokrotnemu wczytywaniu tych samych bajtów z sieci. Jest to przydatne, gdy chcesz cofnąć się w aktualnie odtwarzanych multimediach lub powtórzyć ten sam element.

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();