Mendownload media

ExoPlayer menyediakan fungsi untuk mendownload media untuk pemutaran offline. Dalam sebagian besar kasus penggunaan, sebaiknya pengunduhan tetap dilanjutkan meskipun aplikasi Anda berada di latar belakang. Untuk kasus penggunaan ini, aplikasi Anda harus membuat subclass DownloadService dan mengirim perintah ke layanan untuk menambahkan, menghapus, dan mengontrol download. Diagram berikut menunjukkan class utama yang terlibat.

Class untuk mendownload media. Arah panah menunjukkan aliran data.

  • DownloadService: Membungkus DownloadManager dan meneruskan perintah kepadanya. Layanan ini memungkinkan DownloadManager tetap berjalan meskipun aplikasi berada di latar belakang.
  • DownloadManager: Mengelola beberapa unduhan, memuat (dan menyimpan) statusnya dari (dan ke) DownloadIndex, memulai dan menghentikan unduhan berdasarkan persyaratan seperti konektivitas jaringan, dan sebagainya. Untuk mengunduh konten, manajer biasanya akan membaca data yang diunduh dari HttpDataSource, dan menuliskannya ke dalam Cache.
  • DownloadIndex: Mempertahankan status download.

Membuat DownloadService

Untuk membuat DownloadService, buat subclass dan implementasikan metode abstraknya:

  • getDownloadManager(): Menampilkan DownloadManager yang akan digunakan.
  • getScheduler(): Mengembalikan Scheduler opsional, yang dapat memulai ulang layanan saat persyaratan yang dibutuhkan agar unduhan yang tertunda dapat dilanjutkan terpenuhi. ExoPlayer menyediakan implementasi berikut:
  • getForegroundNotification(): Mengembalikan pemberitahuan 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>

Lihat DemoDownloadService dan AndroidManifest.xml di aplikasi demo ExoPlayer untuk mengetahui contoh konkretnya.

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 mengetahui contoh konkretnya.

Menambahkan download

Untuk menambahkan download, buat DownloadRequest dan kirimkan ke DownloadService. Untuk streaming adaptif, gunakan DownloadHelper untuk membantu membangun DownloadRequest. Contoh berikut 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 pengenal unik untuk konten. Dalam kasus sederhana, contentUri sering kali dapat digunakan sebagai contentId, tetapi aplikasi bebas menggunakan skema ID apa pun yang paling sesuai dengan kasus penggunaannya. DownloadRequest.Builder juga memiliki beberapa penyetel opsional. Misalnya, setKeySetId dan setData dapat digunakan untuk menetapkan DRM dan data kustom yang ingin dikaitkan aplikasi dengan download, masing-masing. Jenis MIME konten juga dapat ditentukan menggunakan setMimeType, sebagai petunjuk untuk kasus ketika jenis konten tidak dapat disimpulkan dari contentUri.

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

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 berada di latar depan, parameter foreground biasanya harus disetel ke false karena DownloadService akan menempatkan dirinya di latar depan jika menentukan bahwa ada tugas yang harus dilakukan.

Menghapus download

Download dapat dihapus dengan mengirimkan perintah hapus 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 didownload dengan DownloadService.sendRemoveAllDownloads.

Memulai dan menghentikan download

Download hanya akan berjalan jika empat kondisi terpenuhi:

  • Download tidak memiliki alasan penghentian.
  • Download tidak dijeda.
  • Persyaratan agar download dapat dilanjutkan terpenuhi. Persyaratan dapat menentukan batasan pada jenis jaringan yang diizinkan, serta apakah perangkat harus dalam kondisi tidak aktif atau terhubung ke pengisi daya.
  • Jumlah download paralel maksimum tidak terlampaui.

Semua kondisi ini dapat dikontrol dengan mengirim perintah ke DownloadService.

Menetapkan dan menghapus alasan penghentian unduhan

Anda dapat menetapkan alasan penghentian satu atau semua download:

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 non-nol (Download.STOP_REASON_NONE = 0 adalah nilai khusus yang berarti bahwa download tidak dihentikan). Aplikasi yang memiliki beberapa alasan untuk menghentikan unduhan dapat menggunakan nilai yang berbeda untuk melacak mengapa setiap unduhan dihentikan. Menetapkan dan menghapus alasan penghentian untuk semua download berfungsi sama seperti menetapkan dan menghapus alasan penghentian untuk satu download, kecuali contentId harus ditetapkan ke null.

Bila unduhan memiliki alasan berhenti selain nol, unduhan akan berada dalam status Download.STATE_STOPPED. Alasan penghentian dipertahankan di DownloadIndex, dan akan tetap ada jika proses aplikasi dihentikan dan dimulai ulang.

Menjeda dan melanjutkan semua unduhan

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

Jika unduhan dijeda, unduhan akan berada dalam status Download.STATE_QUEUED. Tidak seperti menetapkan alasan berhenti, pendekatan ini tidak mempertahankan perubahan status apa pun. Ini hanya memengaruhi status runtime DownloadManager.

Menetapkan persyaratan agar unduhan dapat dilanjutkan

Requirements dapat digunakan untuk menentukan batasan yang harus dipenuhi agar pengunduhan dapat dilanjutkan. Persyaratan dapat ditetapkan dengan memanggil DownloadManager.setRequirements() saat membuat DownloadManager, seperti pada contoh di atas. Mereka 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 unduhan tidak dapat dilanjutkan karena persyaratan tidak terpenuhi, maka akan berada dalam status Download.STATE_QUEUED. Anda dapat menanyakan persyaratan yang tidak terpenuhi dengan DownloadManager.getNotMetRequirements().

Mengatur jumlah maksimum unduhan paralel

Jumlah maksimum unduhan paralel dapat diatur dengan memanggil DownloadManager.setMaxParallelDownloads(). Hal ini biasanya dilakukan saat membuat DownloadManager, seperti pada contoh di atas.

Jika pengunduhan tidak dapat dilanjutkan karena jumlah maksimum pengunduhan paralel sudah berlangsung, maka pengunduhan akan berada dalam status Download.STATE_QUEUED.

Menanyakan unduhan

DownloadIndex dari DownloadManager dapat ditanyakan mengenai status semua unduhan, termasuk unduhan yang telah selesai atau gagal. DownloadIndex dapat diperoleh dengan memanggil DownloadManager.getDownloadIndex(). Kursor yang mengulangi semua unduhan kemudian dapat diperoleh dengan memanggil DownloadIndex.getDownloads(). Alternatifnya, status unduhan tunggal dapat ditanyakan dengan memanggil DownloadIndex.getDownload().

DownloadManager juga menyediakan DownloadManager.getCurrentDownloads(), yang mengembalikan status unduhan saat ini saja (yakni belum selesai atau gagal). Metode ini berguna untuk memperbarui notifikasi dan komponen UI lainnya yang menampilkan kemajuan dan status unduhan saat ini.

Mendengarkan unduhan

Anda dapat menambahkan pendengar ke DownloadManager untuk mendapatkan informasi saat unduhan saat ini berubah status:

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 contoh konkret.

Memutar konten yang didownload

Memutar konten yang diunduh mirip dengan memutar konten daring, kecuali bahwa data dibaca dari unduhan Cache, bukan melalui jaringan.

Untuk memutar konten yang didownload, buat CacheDataSource.Factory menggunakan instance Cache yang sama yang digunakan untuk mendownload, dan masukkan ke DefaultMediaSourceFactory saat membuat pemutar:

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, CacheDataSource.Factory harus dikonfigurasi sebagai hanya baca untuk menghindari konten tersebut didownload juga selama pemutaran.

Setelah dikonfigurasi dengan CacheDataSource.Factory, pemutar akan memiliki akses ke konten yang didownload untuk diputar. Memutar hasil 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. Anda juga dapat menyediakan cache download 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();

Mengunduh dan memutar aliran adaptif

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

Untuk pemutaran streaming, pemilih trek dapat digunakan untuk memilih trek mana yang diputar. Demikian pula, untuk mengunduh, DownloadHelper dapat digunakan untuk memilih trek mana yang diunduh. Penggunaan DownloadHelper yang umum mengikuti langkah-langkah berikut:

  1. Bangun DownloadHelper menggunakan instance DownloadHelper.Factory. Siapkan pembantu dan tunggu callback.

    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);
  2. Jika perlu, periksa trek yang dipilih secara default menggunakan getMappedTrackInfo dan getTrackSelections, lalu sesuaikan menggunakan clearTrackSelections, replaceTrackSelections, dan addTrackSelection.
  3. Buat DownloadRequest untuk trek yang dipilih dengan memanggil getDownloadRequest. Permintaan dapat diteruskan ke DownloadService Anda untuk menambahkan download, seperti yang dijelaskan di atas.
  4. Lepaskan helper menggunakan release().

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

Saat membuat MediaItem, MediaItem.localConfiguration.streamKeys harus diatur agar sesuai dengan yang ada di DownloadRequest sehingga pemutar hanya mencoba memutar subset trek yang telah diunduh. Menggunakan Download.request.toMediaItem dan DownloadRequest.toMediaItem untuk membangun MediaItem akan menanganinya untuk Anda.