Pobieram multimedia

ExoPlayer umożliwia pobieranie multimediów do odtwarzania offline. W większości przypadków zalecane jest kontynuowanie pobierania aplikacji nawet w tle. W takich przypadkach aplikacja powinna podklasyfikować DownloadService i wysyłać do usługi polecenia pozwalające na dodawanie i usuwanie plików oraz zarządzanie pobranymi plikami. Poniższy diagram przedstawia główne klasy, które są uwzględniane.

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

  • DownloadService: zawija obiekt DownloadManager i przekazuje do niego polecenia. Usługa umożliwia działanie DownloadManager nawet wtedy, gdy aplikacja działa w tle.
  • DownloadManager: zarządza wielokrotnym pobieraniem, wczytuje (i zapisuje) ich stany z (i do) DownloadIndex oraz uruchamia i wstrzymuje pobieranie na podstawie wymagań takich jak połączenie sieciowe itp. Aby pobrać treść, menedżer zwykle odczytuje dane pobierane z urządzenia HttpDataSource i zapisuje je w pliku Cache.
  • DownloadIndex: utrzymuje stan pobierania.

Tworzenie usługi DownloadService

Aby utworzyć DownloadService, podklasuj go i zaimplementuj jego metody abstrakcyjne:

  • getDownloadManager(): zwraca wartość DownloadManager do użycia.
  • getScheduler(): zwraca opcjonalny element Scheduler, który może ponownie uruchomić usługę, gdy zostaną spełnione wymagania dotyczące postępu oczekujących pobierania. ExoPlayer udostępnia takie implementacje:
    • PlatformScheduler, który używa JobScheduler (minimum API to 21). Wymagania dotyczące uprawnień aplikacji znajdziesz w dokumentacji Java PlatformScheduler.
    • WorkManagerScheduler, która używa WorkManagera.
  • getForegroundNotification(): zwraca powiadomienie, które jest wyświetlane, gdy usługa działa na pierwszym planie. Możesz użyć DownloadNotificationHelper.buildProgressNotification, aby utworzyć powiadomienie w stylu domyślnym.

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 sekcji DemoDownloadService i AndroidManifest.xml w prezentacji aplikacji ExoPlayer.

Tworzenie menedżera pobierania

Ten fragment kodu pokazuje, jak utworzyć instancję DownloadManager, która może zostać zwrócona przez getDownloadManager() w 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 demonstracyjnej.

Dodawanie pobranego pliku

Aby dodać pobrany plik, utwórz dokument DownloadRequest i wyślij go na urządzenie DownloadService. W przypadku transmisji adaptacyjnych użyj parametru DownloadHelper, aby utworzyć DownloadRequest. Poniższy przykład pokazuje, jak utworzyć żądanie pobrania:

Kotlin

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

Java

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

W tym przykładzie contentId jest unikalnym identyfikatorem treści. W prostych przypadkach obiekt contentUri może być często używany jako contentId, ale aplikacje mogą korzystać z dowolnego schematu identyfikatora, który najlepiej pasuje do ich zastosowania. DownloadRequest.Builder ma też opcjonalne ustawienia ustawiające. Na przykład zasady setKeySetId i setData mogą posłużyć do skonfigurowania DRM i danych niestandardowych, które aplikacja chce powiązać odpowiednio z pobieranym plikiem. Typ MIME treści można też określić za pomocą atrybutu setMimeType, aby wskazać sytuacje, w których nie można wywnioskować typu treści z contentUri.

Po utworzeniu można ją wysłać do DownloadService, aby dodać dane do pobrania:

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 zwykle mieć wartość false, ponieważ DownloadService pojawia się na pierwszym planie, jeśli uzna, że ma wykonać jakieś zadania.

Usuwam pobrane

Pobrany plik można usunąć, wysyłając do interfejsu DownloadService polecenie usuwania, gdzie contentId wskazuje dane do usunięcia:

Kotlin

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

Java

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

Wszystkie pobrane dane możesz też usunąć w aplikacji DownloadService.sendRemoveAllDownloads.

Rozpoczynanie i zatrzymywanie pobierania

Pobieranie rozpocznie się tylko wtedy, gdy zostaną spełnione 4 warunki:

  • Pobieranie nie ma powodu 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 oraz to, czy urządzenie powinno być bezczynne lub podłączone do ładowarki.
  • Maksymalna liczba równoległych pobrań nie została przekroczona.

Wszystkimi tymi warunkami możesz zarządzać, wysyłając polecenia do: DownloadService.

Ustawianie i usuwanie przyczyn zatrzymania pobierania

Możesz ustawić przyczynę zatrzymania pobierania jednego lub wszystkich plików:

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 mieć dowolną wartość inną niż 0 (Download.STOP_REASON_NONE = 0 to wartość specjalna, która oznacza, że pobieranie nie jest zatrzymane). Aplikacje, które mają wiele powodów do zatrzymania pobierania, mogą używać różnych wartości do śledzenia przyczyn zatrzymania pobierania. Ustawianie i usuwanie przyczyny zatrzymania dla wszystkich pobierania działa tak samo jak ustawianie i usuwanie przyczyny zatrzymania w przypadku pojedynczego pobierania, z tym że contentId ma wartość null.

Jeśli przyczyna zatrzymania jest inna niż 0, pobieranie to jest w stanie Download.STATE_STOPPED. Przyczyny zatrzymania są zachowane w DownloadIndex, więc są przechowywane w przypadku zakończenia procesu aplikacji i późniejszego ponownego jego uruchomienia.

Wstrzymywanie i wznawianie pobierania wszystkich plików

Pobieranie wszystkich plików możesz wstrzymywać i wznawiać 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 mają stan Download.STATE_QUEUED. W odróżnieniu od ustawiania powodów zatrzymania to podejście nie powoduje trwałych zmian stanu. Ma to wpływ tylko na stan środowiska wykonawczego DownloadManager.

Ustawianie wymagań dotyczących postępu pobierania

Requirements pozwala określić ograniczenia, które muszą zostać spełnione, aby można było kontynuować pobieranie. Wymagania można ustawić, wywołując DownloadManager.setRequirements() podczas tworzenia obiektu DownloadManager, jak w przykładzie powyżej. Można je też zmieniać dynamicznie, wysyłając do interfejsu DownloadService polecenie:

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 nie można kontynuować pobierania z powodu niespełnienia wymagań, plik będzie miał stan Download.STATE_QUEUED. Zapytanie o niespełnione wymagania możesz wysłać za pomocą funkcji DownloadManager.getNotMetRequirements().

Ustawianie maksymalnej liczby równoległych pobrań

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

Jeśli nie można kontynuować pobierania, ponieważ trwa już maksymalna liczba równoległych pobrań, stan to Download.STATE_QUEUED.

Zapytania dotyczące pobranych plików

Do zapytania DownloadIndex obiektu DownloadManager można wysyłać zapytania o stan wszystkich operacji pobierania, w tym tych zakończonych lub niezaliczonych. Obiekt DownloadIndex można uzyskać, wywołując metodę DownloadManager.getDownloadIndex(). Kursor wykonujący iteracje dla wszystkich pobranych plików można następnie uzyskać, wywołując metodę DownloadIndex.getDownloads(). Zapytanie o stan pojedynczego pobrania można też uzyskać, wywołując DownloadIndex.getDownload().

DownloadManager udostępnia też parametr DownloadManager.getCurrentDownloads(), który zwraca tylko stan bieżącego pobierania (tj. nie ukończono lub nie udało się pobrać). Ta metoda przydaje się do aktualizowania powiadomień i innych komponentów interfejsu, które wyświetlają informacje o postępie i stanie pobierania.

Odsłuchiwanie pobranych plików

Możesz dodać detektor do aplikacji DownloadManager, aby otrzymywać informacje o zmianie stanu pobranych plików:

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 zasobie DownloadManagerListener w klasie DownloadTracker aplikacji demonstracyjnej.

Odtwarzanie pobranych treści

Odtwarzanie pobranych treści przypomina odtwarzanie treści online, z tą różnicą, że dane są odczytywane z pobieranego elementu Cache, a nie przez sieć.

Aby odtworzyć pobrane treści, utwórz CacheDataSource.Factory przy użyciu tej samej instancji Cache, która była używana do pobierania, i wstrzyknij ją do 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 niepobranych treści, element CacheDataSource.Factory powinien być skonfigurowany jako przeznaczony tylko do odczytu, aby uniknąć pobierania również tych treści podczas odtwarzania.

Gdy w odtwarzaczu skonfigurujesz CacheDataSource.Factory, będzie on miał dostęp do pobranych treści i będzie mógł je odtwarzać. Odtworzenie pobranego pliku sprowadza się do przekazania odpowiedniego elementu MediaItem do odtwarzacza. MediaItem można uzyskać z Download przy użyciu Download.request.toMediaItem lub bezpośrednio z DownloadRequest przy użyciu DownloadRequest.toMediaItem.

Konfiguracja MediaSource

Poprzedni przykład udostępnia pamięć podręczną pobierania do odtwarzania wszystkich elementów MediaItem. Możesz też udostępnić pamięć podręczną pobierania dla poszczególnych instancji MediaSource, które będą przesyłane 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 rozdzielczości SD, HD i 4K). Może się też zdarzyć, że wiele ścieżek tego samego typu będzie zawierać różne treści (np. wiele ścieżek audio w różnych językach).

W przypadku odtwarzania strumieniowego za pomocą selektora ścieżek można wybrać utwór do odtworzenia. Podobnie w przypadku pobierania, za pomocą DownloadHelper można wybrać utwory do pobrania. Typowe użycie DownloadHelper występuje w ten sposób:

  1. Utwórz DownloadHelper, korzystając z jednej z metod DownloadHelper.forMediaItem. Przygotuj pomocnika i zaczekaj na oddzwonienie.

    Kotlin

    val downloadHelper =
     DownloadHelper.forMediaItem(
       context,
       MediaItem.fromUri(contentUri),
       DefaultRenderersFactory(context),
       dataSourceFactory
     )
    downloadHelper.prepare(callback)
    

    Java

    DownloadHelper downloadHelper =
       DownloadHelper.forMediaItem(
           context,
           MediaItem.fromUri(contentUri),
           new DefaultRenderersFactory(context),
           dataSourceFactory);
    downloadHelper.prepare(callback);
    
  2. Opcjonalnie sprawdź domyślnie wybrane ścieżki za pomocą getMappedTrackInfo i getTrackSelections oraz wprowadź zmiany za pomocą clearTrackSelections, replaceTrackSelections i addTrackSelection.
  3. Aby utworzyć DownloadRequest dla wybranych ścieżek, wywołaj getDownloadRequest. Prośba może zostać przekazana do DownloadService w celu dodania pliku do pobrania w sposób opisany powyżej.
  4. Zwolnij pomocnika za pomocą release().

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

Podczas tworzenia pliku MediaItem parametr MediaItem.localConfiguration.streamKeys musi być ustawiony tak, aby pasował do tych w DownloadRequest, aby odtwarzacz próbował odtwarzać tylko podzbiór pobranych utworów. Użycie Download.request.toMediaItem i DownloadRequest.toMediaItem do utworzenia MediaItem pomoże Ci to zrobić.