Pobieram multimedia

ExoPlayer umożliwia pobieranie multimediów do odtwarzania offline. W większości przypadków pobieranie powinno być kontynuowane nawet wtedy, gdy aplikacja działa w tle. W takich przypadkach aplikacja powinna być podklasą klasy DownloadService i wysyłać do usługi polecenia dodawania, usuwania i kontrolowania pobierania. Poniższy diagram przedstawia główne klasy, które są zaangażowane w ten proces.

Klasy do pobierania multimediów. Kierunki strzałek wskazują przepływ danych.

  • DownloadService: Zawiera element DownloadManager i przekazuje do niego polecenia. Usługa umożliwia działanie DownloadManager nawet wtedy, gdy aplikacja działa w tle.
  • DownloadManager: zarządza wieloma pobieraniami, wczytując (i zapisując) ich stany z DownloadIndex (i do niego), rozpoczynając i zatrzymując pobieranie na podstawie wymagań, takich jak połączenie sieciowe itp. Aby pobrać treść, menedżer zwykle odczytuje dane pobierane z HttpDataSource i zapisuje je w Cache.
  • DownloadIndex: przechowuje stany pobierania.

Tworzenie DownloadService

Aby utworzyć DownloadService, utwórz jego podklasę i zaimplementuj jego metody abstrakcyjne:

  • getDownloadManager(): zwraca DownloadManager do użycia.
  • getScheduler(): zwraca opcjonalny Scheduler, który może ponownie uruchomić usługę, gdy zostaną spełnione wymagania niezbędne do kontynuowania pobierania. ExoPlayer udostępnia te implementacje:
    • PlatformScheduler, który korzysta z JobScheduler (minimalny interfejs API to 21). Wymagania dotyczące uprawnień aplikacji znajdziesz w dokumentacji Javadoc klasy PlatformScheduler.
    • WorkManagerScheduler, która korzysta z WorkManagera.
  • getForegroundNotification(): zwraca powiadomienie, które ma być wyświetlane, gdy usługa działa na pierwszym planie. Możesz użyć DownloadNotificationHelper.buildProgressNotification, aby utworzyć powiadomienie w domyślnym stylu.

Na koniec zdefiniuj usługę w pliku AndroidManifest.xml:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<application>
  <service android:name="com.myapp.MyDownloadService"
      android:exported="false"
      android:foregroundServiceType="dataSync">
    <!-- This is needed for Scheduler -->
    <intent-filter>
      <action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
      <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
  </service>
</application>

Konkretny przykład znajdziesz w DemoDownloadServiceAndroidManifest.xml w aplikacji w wersji demonstracyjnej ExoPlayera.

Tworzenie obiektu DownloadManager

Ten fragment kodu pokazuje, jak utworzyć instancję DownloadManager, która może być zwracana przez getDownloadManager()DownloadService:

Kotlin

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

// A download cache should not evict media, so should use a NoopCacheEvictor.
val downloadCache = SimpleCache(downloadDirectory, NoOpCacheEvictor(), databaseProvider)

// Create a factory for reading the data from the network.
val dataSourceFactory = DefaultHttpDataSource.Factory()

// Choose an executor for downloading data. Using Runnable::run will cause each download task to
// download data on its own thread. Passing an executor that uses multiple threads will speed up
// download tasks that can be split into smaller parts for parallel execution. Applications that
// already have an executor for background downloads may wish to reuse their existing executor.
val downloadExecutor = Executor(Runnable::run)

// Create the download manager.
val downloadManager =
  DownloadManager(context, databaseProvider, downloadCache, dataSourceFactory, downloadExecutor)

// Optionally, properties can be assigned to configure the download manager.
downloadManager.requirements = requirements
downloadManager.maxParallelDownloads = 3

Java

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

// A download cache should not evict media, so should use a NoopCacheEvictor.
downloadCache = new SimpleCache(downloadDirectory, new NoOpCacheEvictor(), databaseProvider);

// Create a factory for reading the data from the network.
dataSourceFactory = new DefaultHttpDataSource.Factory();

// Choose an executor for downloading data. Using Runnable::run will cause each download task to
// download data on its own thread. Passing an executor that uses multiple threads will speed up
// download tasks that can be split into smaller parts for parallel execution. Applications that
// already have an executor for background downloads may wish to reuse their existing executor.
Executor downloadExecutor = Runnable::run;

// Create the download manager.
downloadManager =
    new DownloadManager(
        context, databaseProvider, downloadCache, dataSourceFactory, downloadExecutor);

// Optionally, setters can be called to configure the download manager.
downloadManager.setRequirements(requirements);
downloadManager.setMaxParallelDownloads(3);

Konkretny przykład znajdziesz w sekcji DemoUtil w aplikacji w wersji demonstracyjnej.

Dodawanie pobierania

Aby dodać plik do pobrania, utwórz DownloadRequest i wyślij go do DownloadService. W przypadku strumieni adaptacyjnych użyj DownloadHelper, aby ułatwić utworzenie DownloadRequest. Ten przykład pokazuje, jak utworzyć żądanie pobierania:

Kotlin

val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()

Java

DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();

W tym przykładzie contentId to unikalny identyfikator treści. W prostszych przypadkach contentUri może być często używany jako contentId, jednak aplikacje mogą używać dowolnego schematu identyfikatorów, który najlepiej pasuje do ich przypadku użycia. DownloadRequest.Builder ma też kilka opcjonalnych setterów. Na przykład setKeySetIdsetData można używać do ustawiania odpowiednio DRM i danych niestandardowych, które aplikacja chce powiązać z pobraniem. Typ MIME treści można też określić za pomocą parametru setMimeType, jako wskazówkę w przypadkach, gdy typu treści nie można wywnioskować z parametru contentUri.

Po utworzeniu żądanie można wysłać do DownloadService, aby dodać pobieranie:

Kotlin

DownloadService.sendAddDownload(
  context,
  MyDownloadService::class.java,
  downloadRequest,
  /* foreground= */ false,
)

Java

DownloadService.sendAddDownload(
    context, MyDownloadService.class, downloadRequest, /* foreground= */ false);

W tym przykładzie MyDownloadService to podklasa DownloadService aplikacji, a parametr foreground określa, czy usługa zostanie uruchomiona na pierwszym planie. Jeśli aplikacja jest już na pierwszym planie, parametr foreground powinien być zwykle ustawiony na false, ponieważ DownloadService przejdzie na pierwszy plan, jeśli uzna, że ma coś do zrobienia.

Usuwam pobrane

Pobieranie można usunąć, wysyłając polecenie usuwania do DownloadService, gdzie contentId identyfikuje pobieranie do usunięcia:

Kotlin

DownloadService.sendRemoveDownload(
  context,
  MyDownloadService::class.java,
  contentId,
  /* foreground= */ false,
)

Java

DownloadService.sendRemoveDownload(
    context, MyDownloadService.class, contentId, /* foreground= */ false);

Możesz też usunąć wszystkie pobrane dane za pomocą opcji DownloadService.sendRemoveAllDownloads.

Rozpoczynanie i zatrzymywanie pobierania

Pobieranie będzie postępować tylko wtedy, gdy zostaną spełnione 4 warunki:

  • Pobieranie nie ma przyczyny zatrzymania.
  • Pobieranie nie jest wstrzymywane.
  • Wymagania dotyczące postępu pobierania są spełnione. Wymagania mogą określać ograniczenia dotyczące dozwolonych typów sieci, a także to, czy urządzenie powinno być w stanie bezczynności, czy podłączone do ładowarki.
  • nie przekracza maksymalnej liczby równoległych pobrań;

Wszystkie te warunki można kontrolować, wysyłając polecenia do urządzenia DownloadService.

Ustawianie i usuwanie przyczyn zatrzymania pobierania

Możesz podać powód zatrzymania jednego lub wszystkich pobrań:

Kotlin

// Set the stop reason for a single download.
DownloadService.sendSetStopReason(
  context,
  MyDownloadService::class.java,
  contentId,
  stopReason,
  /* foreground= */ false,
)

// Clear the stop reason for a single download.
DownloadService.sendSetStopReason(
  context,
  MyDownloadService::class.java,
  contentId,
  Download.STOP_REASON_NONE,
  /* foreground= */ false,
)

Java

// Set the stop reason for a single download.
DownloadService.sendSetStopReason(
    context, MyDownloadService.class, contentId, stopReason, /* foreground= */ false);

// Clear the stop reason for a single download.
DownloadService.sendSetStopReason(
    context,
    MyDownloadService.class,
    contentId,
    Download.STOP_REASON_NONE,
    /* foreground= */ false);

stopReason może być dowolną wartością różną od zera (Download.STOP_REASON_NONE = 0 to wartość specjalna oznaczająca, że pobieranie nie jest zatrzymane). Aplikacje, które mają wiele powodów zatrzymania pobierania, mogą używać różnych wartości, aby śledzić, dlaczego każde pobieranie zostało zatrzymane. Ustawianie i usuwanie przyczyny zatrzymania wszystkich pobrań działa tak samo jak ustawianie i usuwanie przyczyny zatrzymania pojedynczego pobierania, z tym że w tym przypadku wartość contentId powinna być ustawiona na null.

Jeśli pobieranie ma przyczynę zatrzymania inną niż zero, będzie w stanie Download.STATE_STOPPED. Przyczyny zatrzymania są zapisywane w DownloadIndex, więc są zachowywane, jeśli proces aplikacji zostanie zakończony i później ponownie uruchomiony.

Wstrzymywanie i wznawianie wszystkich pobrań

Wszystkie pobierania można wstrzymać i wznowić w ten sposób:

Kotlin

// Pause all downloads.
DownloadService.sendPauseDownloads(
  context,
  MyDownloadService::class.java,
  /* foreground= */ false,
)

// Resume all downloads.
DownloadService.sendResumeDownloads(
  context,
  MyDownloadService::class.java,
  /* foreground= */ false,
)

Java

// Pause all downloads.
DownloadService.sendPauseDownloads(context, MyDownloadService.class, /* foreground= */ false);

// Resume all downloads.
DownloadService.sendResumeDownloads(context, MyDownloadService.class, /* foreground= */ false);

Wstrzymane pobieranie będzie miało stan Download.STATE_QUEUED. W przeciwieństwie do ustawiania przyczyn zatrzymania to podejście nie powoduje utrwalania żadnych zmian stanu. Ma to wpływ tylko na stan środowiska wykonawczego DownloadManager.

Ustawianie wymagań dotyczących postępu pobierania

Requirements można użyć do określenia ograniczeń, które muszą zostać spełnione, aby można było pobrać pliki. Wymagania można ustawić, wywołując funkcję DownloadManager.setRequirements() podczas tworzenia funkcji DownloadManager, jak w przykładzie powyżej. Można je też zmieniać dynamicznie, wysyłając polecenie do DownloadService:

Kotlin

// Set the download requirements.
DownloadService.sendSetRequirements(
  context,
  MyDownloadService::class.java,
  requirements,
  /* foreground= */ false,
)

Java

// Set the download requirements.
DownloadService.sendSetRequirements(
    context, MyDownloadService.class, requirements, /* foreground= */ false);

Jeśli pobieranie nie może być kontynuowane, ponieważ nie są spełnione wymagania, będzie ono w stanie Download.STATE_QUEUED. Zapytanie dotyczące niespełnionych wymagań możesz wysłać za pomocą DownloadManager.getNotMetRequirements().

Ustawianie maksymalnej liczby równoległych pobrań

Maksymalną liczbę równoległych pobrań można ustawić, wywołując funkcję DownloadManager.setMaxParallelDownloads(). Zwykle robi się to podczas tworzenia DownloadManager, jak w przykładzie powyżej.

Gdy pobieranie nie może być kontynuowane, ponieważ osiągnięto maksymalną liczbę równoległych pobrań, będzie ono w stanie Download.STATE_QUEUED.

Wykonywanie zapytań dotyczących pobranych plików

Możesz wysłać zapytanie do DownloadIndex DownloadManager, aby sprawdzić stan wszystkich pobrań, w tym tych, które zostały zakończone lub nie powiodły się. DownloadIndex Można je uzyskać, dzwoniąc pod numer DownloadManager.getDownloadIndex(). Kursor, który iteruje po wszystkich pobranych plikach, można uzyskać, wywołując funkcję DownloadIndex.getDownloads(). Stan pojedynczego pobierania można też sprawdzić, wywołując funkcję DownloadIndex.getDownload().

DownloadManager udostępnia też DownloadManager.getCurrentDownloads(), które zwraca stan tylko bieżących (czyli nieukończonych lub nieudanych) pobrań. Ta metoda jest przydatna do aktualizowania powiadomień i innych komponentów interfejsu, które wyświetlają postęp i stan bieżących pobrań.

Słuchanie pobranych plików

Możesz dodać detektor do DownloadManager, aby otrzymywać powiadomienia o zmianie stanu bieżących pobrań:

Kotlin

downloadManager.addListener(
  object : DownloadManager.Listener { // Override methods of interest here.
  }
)

Java

downloadManager.addListener(
    new DownloadManager.Listener() {
      // Override methods of interest here.
    });

Konkretny przykład znajdziesz w klasie DownloadTrackerDownloadManagerListener w aplikacji demonstracyjnej.

Odtwarzanie pobranych treści

Odtwarzanie pobranych treści jest podobne do odtwarzania treści online, z tym że dane są odczytywane z pobranego plikuCache, a nie z sieci.

Aby odtworzyć pobrane treści, utwórz obiekt CacheDataSource.Factory za pomocą tej samej instancji Cache, która została użyta do pobierania, i wstrzyknij go do obiektu DefaultMediaSourceFactory podczas tworzenia odtwarzacza:

Kotlin

// Create a read-only cache data source factory using the download cache.
val cacheDataSourceFactory: DataSource.Factory =
  CacheDataSource.Factory()
    .setCache(downloadCache)
    .setUpstreamDataSourceFactory(httpDataSourceFactory)
    .setCacheWriteDataSinkFactory(null) // Disable writing.

val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory)
    )
    .build()

Java

// Create a read-only cache data source factory using the download cache.
DataSource.Factory cacheDataSourceFactory =
    new CacheDataSource.Factory()
        .setCache(downloadCache)
        .setUpstreamDataSourceFactory(httpDataSourceFactory)
        .setCacheWriteDataSinkFactory(null); // Disable writing.

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory))
        .build();

Jeśli ta sama instancja odtwarzacza będzie też używana do odtwarzania treści niepobranych, parametr CacheDataSource.Factory powinien być skonfigurowany jako tylko do odczytu, aby uniknąć pobierania tych treści podczas odtwarzania.

Po skonfigurowaniu odtwarzacza za pomocą CacheDataSource.Factory będzie on mieć dostęp do pobranych treści, które będzie można odtwarzać. Odtwarzanie pobranego pliku jest wtedy tak proste, jak przekazanie odpowiedniego MediaItem odtwarzaczowi. MediaItem można uzyskać z Download za pomocą Download.request.toMediaItem lub bezpośrednio z DownloadRequest za pomocą DownloadRequest.toMediaItem.

Konfiguracja MediaSource

W powyższym przykładzie pamięć podręczna pobierania jest dostępna do odtwarzania wszystkich MediaItem. Możesz też udostępnić pamięć podręczną pobierania poszczególnym instancjom MediaSource, które można przekazać bezpośrednio do odtwarzacza:

Kotlin

val mediaSource =
  ProgressiveMediaSource.Factory(cacheDataSourceFactory)
    .createMediaSource(MediaItem.fromUri(contentUri))
player.setMediaSource(mediaSource)
player.prepare()

Java

ProgressiveMediaSource mediaSource =
    new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
        .createMediaSource(MediaItem.fromUri(contentUri));
player.setMediaSource(mediaSource);
player.prepare();

Pobieranie i odtwarzanie strumieni adaptacyjnych

Strumienie adaptacyjne (np. DASH, SmoothStreaming i HLS) zwykle zawierają wiele ścieżek multimedialnych. Często istnieje wiele ścieżek zawierających te same treści w różnej jakości (np. ścieżki wideo w jakości SD, HD i 4K). Może też być wiele ścieżek tego samego typu zawierających różne treści (np. wiele ścieżek audio w różnych językach).

W przypadku odtwarzania strumieniowego można użyć selektora ścieżek, aby wybrać, które ścieżki mają być odtwarzane. Podobnie w przypadku pobierania możesz użyć ikony DownloadHelper, aby wybrać utwory do pobrania. Typowe użycie DownloadHelper obejmuje te czynności:

  1. Utwórz DownloadHelper za pomocą instancji DownloadHelper.Factory. Przygotuj osobę pomagającą i poczekaj na oddzwonienie.
  2. Opcjonalnie możesz sprawdzić domyślnie wybrane ścieżki za pomocą przycisków getMappedTrackInfogetTrackSelections oraz wprowadzić zmiany za pomocą przycisków clearTrackSelections, replaceTrackSelectionsaddTrackSelection.
  3. Utwórz DownloadRequest dla wybranych ścieżek, wywołując getDownloadRequest. Żądanie można przekazać do DownloadService, aby dodać pobieranie, jak opisano powyżej.
  4. Zwolnij metodę pomocniczą za pomocą release().

Kotlin

val downloadHelper =
  DownloadHelper.Factory()
    .setRenderersFactory(DefaultRenderersFactory(context))
    .setDataSourceFactory(dataSourceFactory)
    .create(MediaItem.fromUri(contentUri))
downloadHelper.prepare(callback)

Java

DownloadHelper downloadHelper =
    new DownloadHelper.Factory()
        .setRenderersFactory(new DefaultRenderersFactory(context))
        .setDataSourceFactory(dataSourceFactory)
        .create(MediaItem.fromUri(contentUri));
downloadHelper.prepare(callback);

Odtwarzanie pobranych treści adaptacyjnych wymaga skonfigurowania odtwarzacza i przekazania odpowiedniego parametru MediaItem, jak opisano powyżej.

Podczas tworzenia MediaItem parametr MediaItem.localConfiguration.streamKeys musi być zgodny z parametrami w DownloadRequest, aby odtwarzacz próbował odtworzyć tylko pobrany podzbiór ścieżek. Użycie Download.request.toMediaItemDownloadRequest.toMediaItem do utworzenia MediaItem rozwiąże ten problem.