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

ExoPlayer cung cấp chức năng tải nội dung nghe nhìn xuống để phát lại mà không cần kết nối mạng. Trong hầu hết các trường hợp sử dụng, quá trình tải xuống vẫn cần tiếp tục ngay cả khi ứng dụng chạy ở chế độ nền. Đối với những trường hợp sử dụng này, ứng dụng nên phân lớp con DownloadService và gửi lệnh đến dịch vụ để thêm, xoá và kiểm soát tệp tải xuống. Sơ đồ 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 đó. 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 tệp tải xuống, tải (và lưu trữ) trạng thái của chú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 đượ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 một DownloadService, hãy tạo lớp con và triển khai các phương thức trừu tượng của nó:

  • getDownloadManager(): Trả về DownloadManager sẽ được sử dụng.
  • getScheduler(): Trả về một Scheduler không bắt buộc. Mã này có thể khởi động lại dịch vụ khi đáp ứng 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ý. ExoPlayer cung cấp các cách triển khai sau:
    • PlatformScheduler, sử dụng JobScheduler (API tối thiểu là 21). Hãy xem tài liệu javadocs về 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 Trình quản lý tải xuống

Đoạn mã sau đây minh hoạ cách tạo thực thể cho một DownloadManagergetDownloadManager() có thể trả về trong DownloadService của bạn:

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

Đang thêm một tệp tải xuống

Để thêm một tệp tải xuống, hãy tạo DownloadRequest rồi gửi đến DownloadService của bạn. Đối với các luồng thích ứng, hãy sử dụng DownloadHelper để tạo DownloadRequest. Ví dụ sau đây cho biết 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 cho 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ể thoải mái 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ụ: bạn có thể sử dụng setKeySetIdsetData để đặt DRM và dữ liệu tuỳ chỉnh tương ứng mà ứng dụng muốn liên kết với tệp tải xuống. Bạn cũng có thể chỉ định loại MIME của nội dung bằng cách sử dụng setMimeType làm 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à tham số foreground kiểm soát việc dịch vụ có được bắt đầu ở nền trước hay không. Nếu ứng dụng của bạn đã chạy ở nền trước, thì tham số foreground thường được đặt thành falseDownloadService sẽ tự đặt ở nền trước nếu nó 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:

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

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 cho một hoặc tất cả các nội dung tải xuống bị dừ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 các giá trị khác nhau để theo dõi lý do mỗi lượt tải xuống bị dừng. Việc đặt và xoá lý do dừng cho tất cả tệp tải xuống hoạt động giống như việc đặt và xoá lý do dừng một lượt tải xuống, ngoại trừ việc bạn nên đặt contentId thành null.

Khi lý do dừng tải xuống khác 0, tệp tải xuống sẽ ở trạng thái Download.STATE_STOPPED. Lý do dừng vẫn được duy trì trong DownloadIndex và sẽ được giữ lại nếu quy trình xử lý ứng dụng bị loại bỏ và sau đó khởi động lại.

Tạm dừng và tiếp tục tất cả nội dung tải xuống

Tất cả các tệp đã tải xuống đều có thể bị tạm dừng và tiếp tục 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, tệp tải xuống sẽ ở trạng thái Download.STATE_QUEUED. Không giống như việc đặt lý do cho việc dừng, phương pháp này không duy trì bất kỳ thay đổi nào về trạng thái. Việc này chỉ ảnh hưởng đến trạng thái thời gian chạy của DownloadManager.

Thiết lập yêu cầu để tiến hành tải xuống

Bạn có thể dùng Requirements để chỉ định các quy tắc ràng buộc phải được đáp ứng để tiếp tục tải xuống. 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 đối số 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 đa cho lượt tải xuống song song

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

Khi không thể tiếp tục tải xuống do số lượng lượt 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ả tệp tải xuống, bao gồm cả những lượt tải xuống đã 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 mọi 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 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 khi cập nhật thông báo và các thành phần giao diện người dùng khác cho thấy 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 để nhận thông báo khi trạng thái tải xuống hiện tại thay đổ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ư việc 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 chính thực thể Cache đã dùng để tải xuống rồi chèn 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 phiên bản trình phát cũng được dùng để phát nội dung không tải xuống, thì bạn nên định cấu hình CacheDataSource.Factory ở chế độ chỉ đọ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 đó, việc phát một nội dung tải xuống chỉ đơn giản như việc truyền MediaItem tương ứng đến trình phát. 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ả cá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 riêng lẻ. Những thực thể này có thể 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

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

Đối với các lượt phát trực tuyến, bạn có thể sử dụng bộ chọn bản nhạc để chọn bản nhạc sẽ phát. Tương tự như vậy, để 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 DownloadHelper thông thường bao gồm 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 mặc định đã chọn bằng cách sử dụng getMappedTrackInfogetTrackSelections, cũng như điều chỉnh bằng clearTrackSelections, replaceTrackSelectionsaddTrackSelection (không bắt buộc).
  3. Tạo DownloadRequest cho các bản nhạc đã chọn bằng cách gọi getDownloadRequest. Yêu cầu có thể được truyền đế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 cách sử dụng release().

Để phát nội dung thích ứng đã tải xuống, bạn phải đị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, MediaItem.localConfiguration.streamKeys phải được thiết lập sao cho khớp với các bản nhạc trong DownloadRequest để người chơi chỉ cố gắng phát tập hợp con các bản nhạc đã đượ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.