Pobieram multimedia

ExoPlayer udostępnia funkcję pobierania multimediów na potrzeby odtwarzania offline. W większości przypadków dobrze jest, aby pobieranie było kontynuowane nawet wtedy, gdy aplikacja działa w tle. W takich przypadkach aplikacja powinna być podklasą DownloadService i wysyłać do usługi polecenia dodawania, usuwania i sterowania pobieraniem. Na tym diagramie widać główne klasy, które są zaangażowane w proces.

Zajęcia do pobierania multimediów. Kierunki strzałek wskazują przepływ danych.

  • DownloadService: zawija obiekt DownloadManager i przekazuje do niego polecenia. Dzięki tej usłudze usługa DownloadManager może działać nawet wtedy, gdy aplikacja działa w tle.
  • DownloadManager: zarządza wieloma pobieraniami, wczytuje (i przechowuje) ich stany z (i do) DownloadIndex, uruchamia i zatrzymuje pobieranie na podstawie wymagań takich jak łączność z internetem itp. Aby pobrać zawartość, menedżer zwykle odczytuje dane pobierane z HttpDataSource i zapisze je w Cache.
  • DownloadIndex: zachowuje stany pobierania.

Tworzenie usługi DownloadService

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

  • getDownloadManager(): zwraca DownloadManager do użycia.
  • getScheduler(): zwraca opcjonalny element Scheduler, który może ponownie uruchomić usługę, gdy zostaną spełnione wymagania dotyczące oczekujących pobrań. ExoPlayer udostępnia te implementacje:
    • PlatformScheduler, który używa JobScheduler (minimalna wersja interfejsu API to 21). Wymagania dotyczące uprawnień aplikacji znajdziesz w dokumentacji javadocs PlatformScheduler.
    • WorkManagerScheduler, który korzysta z WorkManagera.
  • getForegroundNotification(): zwraca powiadomienie wyświetlane, gdy usługa działa na pierwszym planie. Możesz użyć elementu 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>

Przykładem jest element DemoDownloadServiceAndroidManifest.xml w aplikacji demonstracyjnej ExoPlayer.

Tworzenie obiektu DownloadManager

Ten fragment kodu pokazuje, jak utworzyć instancję obiektu DownloadManager, który może być zwracany przez funkcję getDownloadManager() w Twojej funkcji 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 pliku do pobrania

Aby dodać plik do pobrania, utwórz DownloadRequest i wyślij go do DownloadService. W przypadku strumieni adaptacyjnych użyj DownloadHelper, aby utworzyć 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 prostych przypadkach contentUri może być używane jako contentId, ale aplikacje mogą stosować dowolny schemat identyfikatorów, który najlepiej pasuje do danego przypadku użycia. Obiekt DownloadRequest.Builder ma też niektóre opcjonalne settery. Na przykład za pomocą setKeySetIdsetData można odpowiednio ustawić DRM oraz dane niestandardowe, które aplikacja chce powiązać z pobieraniem. Typ MIME treści można też określić za pomocą atrybutu setMimeType, który służy jako podpowiedź w przypadkach, gdy nie można określić typu treści na podstawie atrybutu contentUri.

Utworzoną prośbę można wysłać do DownloadService, aby dodać do niej pobrany plik:

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 ma być uruchamiana na pierwszym planie. Jeśli aplikacja jest już na pierwszym planie, parametr foreground powinien być ustawiony na false, ponieważ DownloadService przejdzie na pierwszy plan, jeśli stwierdzi, że ma coś do zrobienia.

Usuwam pobrane

Pobranie można usunąć, wysyłając do DownloadService polecenie usunięcia, gdzie contentId wskazuje plik 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ą 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 wstrzymane.
  • Spełniono wymagania dotyczące pobierania plików. Wymagania mogą określać ograniczenia dotyczące dozwolonych typów sieci, a także określać, czy urządzenie ma być nieaktywne czy podłączone do ładowarki.
  • Nie jest przekraczana maksymalna liczba 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 określić powód 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 być dowolną wartością niezerową (Download.STOP_REASON_NONE = 0 to wartość specjalna oznaczająca, że pobieranie nie zostało zatrzymane). Aplikacje, które mają wiele powodów zatrzymania pobierania, mogą używać różnych wartości do śledzenia, dlaczego pobieranie zostało zatrzymane. Ustawianie i czyszczenie powodu zatrzymania wszystkich plików do pobrania działa tak samo jak ustawianie i czyszczenie powodu zatrzymania pojedynczego pliku do pobrania, z tym że wartość contentId powinna być ustawiona na null.

Gdy pobieranie ma niezerową wartość powodu zatrzymania, jest w stanie Download.STATE_STOPPED. Powody zatrzymania są przechowywane w DownloadIndex, dzięki czemu są zachowywane, jeśli proces aplikacji zostanie zatrzymany i później ponownie uruchomiony.

Wstrzymywanie i wznawianie wszystkich pobieranych plików

Wszystkie pliki pobierane można 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 będzie miało stan Download.STATE_QUEUED. W przeciwieństwie do ustawienia przyczyn zatrzymania to podejście nie zachowuje żadnych zmian stanu. Ma to wpływ tylko na stan DownloadManager w czasie wykonywania.

Ustawianie wymagań dotyczących pobierania

Requirements można użyć do określenia ograniczeń, które muszą zostać spełnione, aby można było pobrać plik. Wymagania można ustawić, wywołując DownloadManager.setRequirements() podczas tworzenia DownloadManager, jak w powyższym przykładzie. Można je też zmieniać dynamicznie, wysyłając do 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);

Gdy pobieranie nie może zostać ukończone, ponieważ nie są spełnione wymagania, będzie mieć stan Download.STATE_QUEUED. Za pomocą zapytania DownloadManager.getNotMetRequirements() możesz sprawdzić wymagania, które nie zostały spełnione.

Ustawianie maksymalnej liczby równoległych pobrań

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

Gdy pobieranie nie może się rozpocząć, ponieważ jest już w toku maksymalna liczba równoczesnych pobrań, jest w stanie Download.STATE_QUEUED.

Przesyłanie zapytań dotyczących pobrań

Zapytanie DownloadIndex elementu DownloadManager można wysłać w przypadku stanu wszystkich pobrań, w tym tych, które zostały ukończone lub zakończyły się niepowodzeniem. DownloadIndex można uzyskać, wywołując funkcję DownloadManager.getDownloadIndex(). Kursor, który wyświetli wszystkie pobrane pliki, można uzyskać, wywołując funkcję DownloadIndex.getDownloads(). Stan pojedynczego pobierania można też sprawdzić, wywołując funkcję DownloadIndex.getDownload().

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

Słuchanie pobranych plików

Możesz dodać do DownloadManager słuchacza, aby otrzymywać powiadomienia o zmianie stanu bieżących pobieranych 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 zawiera DownloadManagerListener w klasie DownloadTracker aplikacji w wersji demonstracyjnej.

Odtwarzam pobrane treści

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

Aby odtworzyć pobrane treści, utwórz plik CacheDataSource.Factory przy użyciu tego samego wystąpienia Cache, które było używane do pobierania, i wstrzyknij go do obszaru 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 używana do odtwarzania niepobranych treści, CacheDataSource.Factory należy skonfigurować jako tylko do odczytu, aby uniknąć pobierania tych treści podczas odtwarzania.

Po skonfigurowaniu odtwarzacza za pomocą CacheDataSource.Factory uzyska on dostęp do pobranych treści i będzie można je odtworzyć. Odtworzenie pobranego pliku jest tak proste, jak przekazanie odpowiedniego MediaItem do odtwarzacza. Wartość MediaItem można uzyskać z poziomu Download za pomocą funkcji Download.request.toMediaItem lub bezpośrednio z poziomu DownloadRequest za pomocą funkcji DownloadRequest.toMediaItem.

Konfiguracja MediaSource

Poprzedni przykład udostępnia pamięć podręczną pobierania na potrzeby odtwarzania wszystkich plików MediaItem. Możesz też udostępnić pamięć podręczną pobierania dla poszczególnych instancji 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 z tymi samymi treściami w różnej jakości (np. ścieżki wideo SD, HD i 4K). Mogą też występować ścieżki tego samego typu zawierające różne treści (np. ścieżki audio w różnych językach).

W przypadku odtwarzania strumieniowego można użyć selektora utworów, aby wybrać, które utwory mają być odtwarzane. Podobnie w przypadku pobierania, DownloadHelper może posłużyć do określenia, które utwory mają zostać pobrane. Typowe zastosowanie DownloadHelper:

  1. Utwórz DownloadHelper, korzystając z jednej z metod DownloadHelper.forMediaItem. Przygotuj pomocnika i zaczekaj na połączenie zwrotne.

    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 możesz sprawdzić domyślnie wybrane ścieżki za pomocą opcji getMappedTrackInfo i getTrackSelections oraz wprowadzić korekty za pomocą opcji clearTrackSelections, replaceTrackSelections i addTrackSelection.
  3. Utwórz DownloadRequest dla wybranych utworów, dzwoniąc pod numer getDownloadRequest. Prośbę można przekazać do DownloadService, aby dodać możliwość pobrania, jak opisano powyżej.
  4. Zwalnij pomocnika, używając release().

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

Podczas tworzenia MediaItem musisz ustawić MediaItem.localConfiguration.streamKeys tak, aby odpowiadał wartościom w DownloadRequest, aby odtwarzacz próbował odtworzyć tylko podzbiór pobranych utworów. Użycie Download.request.toMediaItem i DownloadRequest.toMediaItem do utworzenia MediaItem załatwi Ci to zadanie.