Tải nội dung nghe nhìn xuống

ExoPlayer cung cấp chức năng tải nội dung đa phương tiện xuống để phát khi không có mạng. Trong hầu hết các trường hợp sử dụng, bạn nên tiếp tục tải xuống ngay cả khi ứng dụng đang chạy trong nền. Đối với các trường hợp sử dụng này, ứng dụng của bạn nên tạo lớp con DownloadService và gửi lệnh đến dịch vụ để thêm, xoá và kiểm soát nội dung tải xuống. Biểu đồ sau đây cho thấy các lớp chính có liên quan.

Các lớp để tải nội dung nghe nhìn xuống. Hướng mũi tên cho biết luồng dữ liệu.

  • DownloadService: Gói DownloadManager và chuyển tiếp các lệnh đến DownloadManager đó. Dịch vụ này cho phép DownloadManager tiếp tục chạy ngay cả khi ứng dụng ở chế độ nền.
  • DownloadManager: Quản lý nhiều lượt tải xuống, tải (và lưu trữ) trạng thái của các lượt tải xuống từ (và đến) DownloadIndex, bắt đầu và dừng tải xuống dựa trên các yêu cầu như kết nối mạng, v.v. Để tải nội dung xuống, trình quản lý thường sẽ đọc dữ liệu đang được tải xuống từ HttpDataSource và ghi dữ liệu đó vào Cache.
  • DownloadIndex: Duy trì trạng thái của tệp đã tải xuống.

Tạo DownloadService

Để tạo DownloadService, hãy tạo lớp con và triển khai các phương thức trừu tượng của lớp con đó:

  • getDownloadManager(): Trả về DownloadManager sẽ được sử dụng.
  • getScheduler(): Trả về một Scheduler không bắt buộc, có thể khởi động lại dịch vụ khi các yêu cầu cần thiết đối với tiến trình tải xuống đang chờ xử lý được đáp ứng. ExoPlayer cung cấp các phương thức triển khai sau:
    • PlatformScheduler sử dụng JobScheduler (API tối thiểu là 21). Xem javadoc của PlatformScheduler để biết các yêu cầu về quyền cho ứng dụng.
    • WorkManagerScheduler sử dụng WorkManager.
  • getForegroundNotification(): Trả về một thông báo sẽ hiển thị khi dịch vụ đang chạy ở nền trước. Bạn có thể sử dụng DownloadNotificationHelper.buildProgressNotification để tạo thông báo theo kiểu mặc định.

Cuối cùng, hãy xác định dịch vụ trong tệp 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>

Hãy xem DemoDownloadServiceAndroidManifest.xml trong ứng dụng minh hoạ ExoPlayer để biết ví dụ cụ thể.

Tạo DownloadManager

Đoạn mã sau đây minh hoạ cách tạo bản sao của DownloadManager, mà getDownloadManager() có thể trả về trong 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);

Hãy xem DemoUtil trong ứng dụng minh hoạ để biết ví dụ cụ thể.

Thêm nội dung tải xuống

Để thêm một tệp tải xuống, hãy tạo một DownloadRequest rồi gửi tệp đó đến DownloadService. Đối với luồng thích ứng, hãy sử dụng DownloadHelper để tạo DownloadRequest. Ví dụ sau đây cho thấy cách tạo một yêu cầu tải xuống:

Kotlin

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

Java

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

Trong ví dụ này, contentId là giá trị nhận dạng duy nhất của nội dung. Trong các trường hợp đơn giản, contentUri thường có thể được dùng làm contentId, tuy nhiên, các ứng dụng có thể tự do sử dụng bất kỳ lược đồ mã nhận dạng nào phù hợp nhất với trường hợp sử dụng của chúng. DownloadRequest.Builder cũng có một số phương thức setter không bắt buộc. Ví dụ: setKeySetIdsetData có thể được dùng để đặt DRM và dữ liệu tuỳ chỉnh mà ứng dụng muốn liên kết tương ứng với nội dung tải xuống. Bạn cũng có thể chỉ định loại MIME của nội dung bằng setMimeType, dưới dạng gợi ý cho trường hợp không thể suy ra loại nội dung từ contentUri.

Sau khi tạo, yêu cầu có thể được gửi đến DownloadService để thêm tệp tải xuống:

Kotlin

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

Java

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

Trong ví dụ này, MyDownloadService là lớp con DownloadService của ứng dụng và thông số foreground kiểm soát việc dịch vụ có được khởi động ở nền trước hay không. Nếu ứng dụng của bạn đã chạy ở nền trước thì thông thường tham số foreground nên được đặt thành falseDownloadService sẽ tự đặt ở nền trước nếu xác định rằng nó có việc cần làm.

Đang xóa các mục đã tải xuống

Bạn có thể xoá một tệp tải xuống bằng cách gửi lệnh xoá đến DownloadService, trong đó contentId xác định tệp tải xuống cần xoá:

Kotlin

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

Java

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

Bạn cũng có thể xoá tất cả dữ liệu đã tải xuống bằng DownloadService.sendRemoveAllDownloads.

Bắt đầu và dừng tải xuống

Quá trình tải xuống sẽ chỉ diễn ra nếu đáp ứng 4 điều kiện sau:

  • Không có lý do dừng để tải nội dung xuống.
  • Tải xuống không bị tạm dừng.
  • Đáp ứng các yêu cầu để tải xuống. Các yêu cầu có thể chỉ định các quy tắc ràng buộc đối với các loại mạng được phép, cũng như liệu thiết bị có ở trạng thái rảnh hay đang kết nối với bộ sạc.
  • Không vượt quá số lượng tải xuống song song tối đa.

Bạn có thể kiểm soát tất cả các điều kiện này bằng cách gửi lệnh đến DownloadService.

Đặt và xoá lý do dừng tải xuống

Bạn có thể đặt lý do dừng một hoặc tất cả các lượt tải xuống:

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 có thể là bất kỳ giá trị nào khác 0 (Download.STOP_REASON_NONE = 0 là một giá trị đặc biệt, nghĩa là quá trình tải xuống không bị dừng). Các ứng dụng có nhiều lý do dừng tải xuống có thể sử dụng nhiều giá trị để theo dõi lý do dừng mỗi lượt tải xuống. Việc đặt và xoá lý do dừng cho tất cả các lượt tải xuống cũng giống như việc đặt và xoá lý do dừng cho một lượt tải xuống, ngoại trừ việc bạn phải đặt contentId thành null.

Khi một tệp tải xuống có lý do dừng khác 0, tệp đó sẽ ở trạng thái Download.STATE_STOPPED. Các lý do dừng vẫn tồn tại trong DownloadIndex, và vì vậy sẽ được giữ lại nếu quá trình đăng ký bị dừng và sau đó khởi động lại.

Tạm dừng và tiếp tục tất cả các lượt tải xuống

Bạn có thể tạm dừng và tiếp tục tất cả các tệp tải xuống như sau:

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

Khi bị tạm dừng, nội dung tải xuống sẽ ở trạng thái Download.STATE_QUEUED. Không giống như cách đặt lý do dừng, phương pháp này không duy trì bất kỳ thay đổi trạng thái nào. Phương thức này chỉ ảnh hưởng đến trạng thái thời gian chạy của DownloadManager.

Đặt yêu cầu để tải xuống

Bạn có thể sử dụng Requirements để chỉ định các điều kiện ràng buộc phải được đáp ứng để quá trình tải xuống diễn ra. Bạn có thể đặt các yêu cầu bằng cách gọi DownloadManager.setRequirements() khi tạo DownloadManager, như trong ví dụ ở trên. Bạn cũng có thể thay đổi các giá trị này một cách linh hoạt bằng cách gửi lệnh đến 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);

Khi không thể tiếp tục tải xuống do không đáp ứng các yêu cầu, quá trình tải xuống sẽ ở trạng thái Download.STATE_QUEUED. Bạn có thể truy vấn các yêu cầu không được đáp ứng bằng DownloadManager.getNotMetRequirements().

Đặt số lượng tải xuống song song tối đa

Bạn có thể đặt số lượng tải xuống song song tối đa bằng cách gọi DownloadManager.setMaxParallelDownloads(). Bạn thường thực hiện việc này khi tạo DownloadManager, như trong ví dụ ở trên.

Khi không thể tiếp tục tải xuống vì số lượng tải xuống song song tối đa đang diễn ra, quá trình tải xuống sẽ ở trạng thái Download.STATE_QUEUED.

Truy vấn nội dung tải xuống

Bạn có thể truy vấn DownloadIndex của DownloadManager để biết trạng thái của tất cả các tệp tải xuống, bao gồm cả những tệp đã hoàn tất hoặc không thành công. Bạn có thể lấy DownloadIndex bằng cách gọi DownloadManager.getDownloadIndex(). Sau đó, bạn có thể lấy con trỏ lặp lại trên tất cả các tệp tải xuống bằng cách gọi DownloadIndex.getDownloads(). Ngoài ra, bạn có thể truy vấn trạng thái của một lượt tải xuống bằng cách gọi DownloadIndex.getDownload().

DownloadManager cũng cung cấp DownloadManager.getCurrentDownloads(), chỉ trả về trạng thái của các tệp tải xuống hiện tại (tức là chưa hoàn tất hoặc không thành công). Phương thức này rất hữu ích để cập nhật thông báo và các thành phần giao diện người dùng khác hiển thị tiến trình và trạng thái của các tệp tải xuống hiện tại.

Nghe nội dung đã tải xuống

Bạn có thể thêm trình nghe vào DownloadManager để được thông báo khi nội dung tải xuống hiện tại thay đổi trạng thái:

Kotlin

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

Java

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

Hãy xem DownloadManagerListener trong lớp DownloadTracker của ứng dụng minh hoạ để biết ví dụ cụ thể.

Đang phát nội dung đã tải xuống

Việc phát nội dung đã tải xuống cũng tương tự như phát nội dung trực tuyến, ngoại trừ việc dữ liệu được đọc từ Cache tải xuống thay vì qua mạng.

Để phát nội dung đã tải xuống, hãy tạo một CacheDataSource.Factory bằng cách sử dụng cùng một thực thể Cache đã dùng để tải xuống và chèn thực thể đó vào DefaultMediaSourceFactory khi tạo trình phát:

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

Nếu cùng một thực thể trình phát cũng được dùng để phát nội dung chưa tải xuống, thì bạn nên định cấu hình CacheDataSource.Factory ở chế độ chỉ có thể đọc để tránh tải nội dung đó xuống trong khi phát.

Sau khi được định cấu hình bằng CacheDataSource.Factory, trình phát sẽ có quyền truy cập vào nội dung đã tải xuống để phát. Sau đó, bạn chỉ cần truyền MediaItem tương ứng cho trình phát để phát nội dung tải xuống. Bạn có thể lấy MediaItem từ Download bằng Download.request.toMediaItem hoặc trực tiếp từ DownloadRequest bằng DownloadRequest.toMediaItem.

Cấu hình MediaSource

Ví dụ trước cung cấp bộ nhớ đệm tải xuống để phát tất cả MediaItem. Bạn cũng có thể cung cấp bộ nhớ đệm tải xuống cho từng thực thể MediaSource. Các thực thể này có thể được truyền trực tiếp đến trình phát:

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

Tải xuống và phát luồng thích ứng

Luồng thích ứng (ví dụ: DASH, SmoothStreaming và HLS) thường chứa nhiều kênh nội dung đa phương tiện. Thường có nhiều kênh chứa cùng một nội dung ở nhiều chất lượng (ví dụ: kênh video SD, HD và 4K). Cũng có thể có nhiều kênh thuộc cùng một loại chứa nội dung khác nhau (ví dụ: nhiều kênh âm thanh bằng nhiều ngôn ngữ).

Đối với chế độ phát trực tuyến, bạn có thể dùng bộ chọn bản nhạc để chọn bản nhạc sẽ phát. Tương tự, để tải xuống, bạn có thể sử dụng DownloadHelper để chọn bản nhạc sẽ được tải xuống. Cách sử dụng thông thường của DownloadHelper là làm theo các bước sau:

  1. Tạo DownloadHelper bằng một trong các phương thức DownloadHelper.forMediaItem. Chuẩn bị trình trợ giúp và chờ lệnh gọi lại.

    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. Bạn có thể kiểm tra các kênh được chọn mặc định bằng getMappedTrackInfogetTrackSelections, đồng thời điều chỉnh bằng clearTrackSelections, replaceTrackSelectionsaddTrackSelection.
  3. Tạo DownloadRequest cho các kênh đã chọn bằng cách gọi getDownloadRequest. Bạn có thể chuyển yêu cầu này đến DownloadService để thêm nội dung tải xuống, như mô tả ở trên.
  4. Giải phóng trình trợ giúp bằng release().

Để phát nội dung thích ứng đã tải xuống, bạn cần định cấu hình trình phát và truyền MediaItem tương ứng, như mô tả ở trên.

Khi tạo MediaItem, bạn phải đặt MediaItem.localConfiguration.streamKeys khớp với các tệp trong DownloadRequest để trình phát chỉ cố gắng phát một số bản nhạc đã tải xuống. Việc sử dụng Download.request.toMediaItemDownloadRequest.toMediaItem để tạo MediaItem sẽ giải quyết vấn đề này cho bạn.