Mendownload media

ExoPlayer menyediakan fungsionalitas untuk mendownload media untuk pemutaran offline. Dalam sebagian besar sebaiknya download terus berlanjut bahkan saat aplikasi Anda berada dalam latar belakang. Untuk kasus penggunaan ini, aplikasi Anda harus membuat subclass DownloadService dan mengirimkan perintah ke layanan untuk menambahkan, menghapus, dan mengontrol download. Tujuan diagram berikut menunjukkan class utama yang terlibat.

Class untuk mendownload media. Arah panah menunjukkan aliran data.

  • DownloadService: Menggabungkan DownloadManager dan meneruskan perintah ke sana. Tujuan memungkinkan DownloadManager untuk tetap berjalan bahkan saat aplikasi berada dalam latar belakang.
  • DownloadManager: Mengelola beberapa download, memuat (dan menyimpan) status dari (dan ke) DownloadIndex, yang memulai dan menghentikan download berdasarkan sesuai kebutuhan, misalnya konektivitas jaringan, dan sebagainya. Untuk mendownload konten, pengelola biasanya akan membaca data yang diunduh dari HttpDataSource, lalu tulis ke Cache.
  • DownloadIndex: Mempertahankan status download.

Membuat DownloadService

Untuk membuat DownloadService, buat subclass dan implementasikan metode abstrak:

  • getDownloadManager(): Menampilkan DownloadManager yang akan digunakan.
  • getScheduler(): Menampilkan Scheduler opsional, yang dapat memulai ulang metode layanan saat persyaratan yang diperlukan agar progres download yang tertunda terpenuhi. ExoPlayer menyediakan implementasi ini:
  • getForegroundNotification(): Menampilkan notifikasi yang akan ditampilkan saat layanan berjalan di latar depan. Anda dapat menggunakan DownloadNotificationHelper.buildProgressNotification untuk membuat notifikasi dalam gaya default.

Terakhir, tentukan layanan di file AndroidManifest.xml Anda:

<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>

Melihat DemoDownloadService dan AndroidManifest.xml di ExoPlayer aplikasi demo untuk contoh konkret.

Membuat DownloadManager

Cuplikan kode berikut menunjukkan cara membuat instance DownloadManager, yang dapat ditampilkan oleh getDownloadManager() di DownloadService Anda:

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

Lihat DemoUtil di aplikasi demo untuk contoh konkret.

Menambahkan download

Untuk menambahkan download, buat DownloadRequest dan kirimkan ke DownloadService. Untuk streaming adaptif, gunakan DownloadHelper untuk membantu membuat DownloadRequest. Hal berikut contoh menunjukkan cara membuat permintaan download:

Kotlin

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

Java

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

Dalam contoh ini, contentId adalah ID unik untuk konten. Dalam kasus sederhana, contentUri sering kali dapat digunakan sebagai contentId, tetapi aplikasi dapat digunakan secara gratis skema ID apa pun yang paling sesuai dengan kasus penggunaan mereka. DownloadRequest.Builder juga memiliki beberapa penyetel opsional. Misalnya, setKeySetId dan setData dapat digunakan untuk menyetel DRM dan data khusus yang ingin dikaitkan oleh aplikasi dengan download, secara berurutan. Jenis MIME konten juga dapat ditetapkan menggunakan setMimeType, sebagai petunjuk untuk kasus saat jenis konten tidak dapat disimpulkan dari contentUri.

Setelah dibuat, permintaan dapat dikirim ke DownloadService untuk menambahkan unduh:

Kotlin

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

Java

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

Dalam contoh ini, MyDownloadService adalah subclass DownloadService aplikasi, dan Parameter foreground mengontrol apakah layanan akan dimulai di latar depan. Jika aplikasi Anda sudah ada di latar depan, foreground biasanya harus disetel ke false karena DownloadService akan menempatkan dirinya di latar depan jika ia menentukan bahwa ia memiliki pekerjaan yang harus dilakukan.

Menghapus download

Download dapat dihapus dengan mengirimkan perintah {i>remove<i} ke DownloadService, dengan contentId mengidentifikasi download yang akan dihapus:

Kotlin

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

Java

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

Anda juga dapat menghapus semua data yang diunduh menggunakan DownloadService.sendRemoveAllDownloads.

Memulai dan menghentikan download

Download hanya akan berlangsung jika empat kondisi terpenuhi:

  • Download tidak memiliki alasan untuk berhenti.
  • Download tidak dijeda.
  • Persyaratan download untuk progres terpenuhi. Persyaratan dapat menentukan pada jenis jaringan yang diizinkan, serta apakah perangkat tidak ada aktivitas atau tersambung ke pengisi daya.
  • Jumlah maksimum download paralel tidak terlampaui.

Semua kondisi ini dapat dikontrol dengan mengirimkan perintah ke DownloadService.

Menyetel dan menghapus alasan penghentian download

Anda dapat menetapkan alasan untuk satu atau semua download dihentikan:

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 dapat berupa nilai apa pun yang bukan nol (Download.STOP_REASON_NONE = 0 adalah nilai khusus yang berarti bahwa download tidak dihentikan). Aplikasi yang memiliki beberapa alasan untuk menghentikan download dapat menggunakan nilai yang berbeda untuk melacak alasan setiap download dihentikan. Menetapkan dan menghapus alasan penghentian untuk semua download bekerja dengan cara yang sama seperti menyetel dan menghapus alasan penghentian untuk download tunggal, kecuali bahwa contentId harus disetel ke null.

Ketika download memiliki alasan yang bukan nol berhenti, itu akan berada di status Download.STATE_STOPPED. Alasan penghentian disimpan di DownloadIndex, sehingga dipertahankan jika proses aplikasi dihentikan dan kemudian dimulai ulang.

Menjeda dan melanjutkan semua download

Semua download dapat dijeda dan dilanjutkan sebagai berikut:

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

Saat dijeda, download akan berada dalam status Download.STATE_QUEUED. Tidak seperti menetapkan alasan penghentian, pendekatan ini tidak mempertahankan status apa pun perubahan. Hal ini hanya memengaruhi status runtime DownloadManager.

Menetapkan persyaratan untuk melanjutkan download

Requirements dapat digunakan untuk menentukan batasan yang harus dipenuhi untuk unduhan untuk melanjutkan. Persyaratannya bisa diatur dengan memanggil DownloadManager.setRequirements() saat membuat DownloadManager, seperti dalam contoh di atas. Nama itu juga dapat diubah secara dinamis dengan mengirimkan perintah ke 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);

Jika download tidak dapat dilanjutkan karena persyaratan tidak terpenuhi, download akan berada dalam status Download.STATE_QUEUED. Anda dapat mengkueri data tidak terpenuhi persyaratan dengan DownloadManager.getNotMetRequirements().

Menetapkan jumlah maksimum download paralel

Jumlah maksimum download paralel dapat ditetapkan dengan memanggil DownloadManager.setMaxParallelDownloads(). Ini biasanya akan dilakukan ketika membuat DownloadManager, seperti pada contoh di atas.

Saat download tidak dapat dilanjutkan karena jumlah maksimum download paralel sedang berlangsung, serta akan berada dalam status Download.STATE_QUEUED.

Membuat kueri download

DownloadIndex dari DownloadManager dapat dikueri status semua download, termasuk download yang telah selesai atau gagal. DownloadIndex dapat diperoleh dengan memanggil DownloadManager.getDownloadIndex(). Kursor yang melakukan iterasi pada semua download kemudian dapat diperoleh dengan memanggil DownloadIndex.getDownloads(). Atau, status download tunggal dapat dikueri dengan memanggil DownloadIndex.getDownload().

DownloadManager juga menyediakan DownloadManager.getCurrentDownloads(), yang mengembalikan status download saat ini saja (yaitu tidak selesai atau gagal). Ini berguna untuk memperbarui notifikasi dan komponen UI lain yang menampilkan kemajuan dan status unduhan saat ini.

Mendengarkan download

Anda dapat menambahkan pemroses ke DownloadManager untuk diberi tahu saat pemroses status perubahan download:

Kotlin

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

Java

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

Lihat DownloadManagerListener di class DownloadTracker aplikasi demo untuk mengetahui sebuah contoh konkret.

Memutar konten yang didownload

Memutar konten yang didownload mirip dengan memutar konten online, hanya saja data dibaca dari download Cache, bukan melalui jaringan.

Untuk memutar konten yang didownload, buat CacheDataSource.Factory menggunakan Instance Cache yang digunakan untuk mendownload, dan memasukkannya ke dalam DefaultMediaSourceFactory saat membangun pemain:

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

Jika instance pemutar yang sama juga akan digunakan untuk memutar konten yang tidak didownload maka CacheDataSource.Factory harus dikonfigurasi sebagai hanya-baca untuk menghindari mengunduh konten itu juga selama pemutaran.

Setelah pemutar dikonfigurasi dengan CacheDataSource.Factory, pemutar tersebut akan memiliki akses ke konten yang didownload untuk pemutaran. Memutar download kemudian semudah meneruskan MediaItem yang sesuai ke pemutar. MediaItem dapat diperoleh dari Download menggunakan Download.request.toMediaItem, atau langsung dari DownloadRequest menggunakan DownloadRequest.toMediaItem.

Konfigurasi MediaSource

Contoh sebelumnya membuat cache download tersedia untuk pemutaran semua MediaItem dtk. Anda juga dapat membuat {i> cache<i} download tersedia untuk instance MediaSource individual, yang dapat diteruskan langsung ke pemutar:

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

Mendownload dan memutar streaming adaptif

Streaming adaptif (misalnya DASH, SmoothStreaming, dan HLS) biasanya berisi beberapa trek media. Sering kali ada beberapa lagu yang berisi konten yang sama di kualitas yang berbeda (misalnya trek video SD, HD, dan 4K). Mungkin juga ada beberapa trek dari jenis yang sama yang berisi konten berbeda (mis. beberapa trek audio dalam berbagai bahasa).

Untuk pemutaran streaming, pemilih trek dapat digunakan untuk memilih trek lagu diputar. Demikian pula, untuk mendownload, DownloadHelper dapat digunakan untuk memilih lagu yang akan didownload. Penggunaan standar DownloadHelper mengikuti langkah-langkah berikut:

  1. Membangun DownloadHelper menggunakan salah satu DownloadHelper.forMediaItem metode. Siapkan helper dan tunggu callback.

    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. Anda juga dapat memeriksa trek default yang dipilih menggunakan getMappedTrackInfo dan getTrackSelections, serta melakukan penyesuaian menggunakan clearTrackSelections, replaceTrackSelections dan addTrackSelection.
  3. Buat DownloadRequest untuk trek yang dipilih dengan memanggil getDownloadRequest. Permintaan dapat diteruskan ke DownloadService Anda untuk tambahkan download, seperti dijelaskan di atas.
  4. Rilis helper menggunakan release().

Pemutaran konten adaptif yang didownload memerlukan konfigurasi pemutar dan meneruskan MediaItem yang sesuai, seperti yang dijelaskan di atas.

Saat membuat MediaItem, MediaItem.localConfiguration.streamKeys harus diatur agar sesuai dengan yang ada di DownloadRequest sehingga pemain hanya mencoba memutar sebagian lagu yang telah didownload. Menggunakan Download.request.toMediaItem dan DownloadRequest.toMediaItem untuk membuat MediaItem akan menanganinya untuk Anda.