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 khi không có mạng. Trong hầu hết trong 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 ở 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. Chiến lược phát hành đĩa đơn sơ đồ dưới đây cho thấy các lớp chính có tham gia.

Lớp học tải nội dung nghe nhìn xuống. Hướng mũi tên biểu thị luồng dữ liệu.

  • DownloadService: Gói một DownloadManager và chuyển tiếp các lệnh đến đó. Chiến lược phát hành đĩa đơn cho phép DownloadManager tiếp tục chạy ngay cả khi ứng dụng đang ở nền.
  • DownloadManager: Quản lý nhiều tệp đã tải xuống, tải (và lưu trữ) các tệp trạng thái từ (và đến) DownloadIndex, bắt đầu và dừng tải xuống dựa trên dựa trên các yêu cầu như kết nối mạng, v.v. Để tải xuống thì trình quản lý thường sẽ đọc dữ liệu được tải xuống từ HttpDataSource rồi ghi mã này 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 lớp này phương thức trừu tượng:

  • 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 khi đáp ứng các yêu cầu cần thiết về quá trình tải xuống đang chờ xử lý. ExoPlayer cung cấp những cách triển khai sau:
    • PlatformScheduler, sử dụng jobScheduler (API tối thiểu là 21). Xem javadocs 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ẽ được hiển thị khi đang chạy trên nền trước. Bạn có thể sử dụng DownloadNotificationHelper.buildProgressNotification để tạo một 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>

Xem DemoDownloadServiceAndroidManifest.xml trong ExoPlayer ứng dụng minh hoạ để có một ví dụ cụ thể.

Tạo DownloadManager

Đoạn mã sau đây minh hoạ cách tạo DownloadManager, có thể được getDownloadManager() 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ể.

Thêm một tệp 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 để giúp tạo DownloadRequest. Nội dung sau đây ví dụ 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 cho nội dung. Trong những 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 miễn phí sử dụng bất kỳ lược đồ ID nào phù hợp nhất với trường hợp sử dụng của họ. DownloadRequest.Builder cũng có một số phương thức setter tuỳ chọn. Ví dụ: bạn có thể dùng setKeySetIdsetData để đặt DRM và dữ liệu tuỳ chỉnh mà ứng dụng muốn liên kết 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, là gợi ý cho trường hợp không thể suy ra loại nội dung từ contentUri.

Sau khi tạo xong, bạn có thể gửi yêu cầu tới DownloadService để thêm 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ó bắt đầu trong nền trước. Nếu ứng dụng của bạn đã chạy ở nền trước, thì foreground tham số thường phải được đặt thành false, vì DownloadService sẽ tự đặt nó ở nền trước nếu xác định rằng nó còn nhiều 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á tới DownloadService, trong đó contentId xác định tệp tải xuống sẽ bị 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:

  • Không có lý do dừng để tải xuống.
  • Quá trình tải xuống không bị tạm dừng.
  • Yêu cầu đối với quá trình tải xuống được đáp ứng. Các yêu cầu có thể chỉ định các hạn chế đối với các loại mạng được phép cũng như liệu thiết bị có nên ở trạng thái rảnh hoặc kết nối với bộ sạc.
  • Không vượt quá số lượng tối đa lượt tải xuống song song.

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.

Thiết lập và xoá lý do ngừng tải xuống

Bạn có thể đặt lý do dừng một hoặc tất cả nội dung 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 sẽ không bị dừng lại). Ứng dụng có nhiều lý do khiến bạn dừng tải xuống có thể dùng các giá trị khác nhau để tiếp tục theo dõi về lý do khiến mỗi quá trình tải xuống bị dừng. Đặt và xoá lý do ngừng cho tất cả tải xuống hoạt động giống như đặt và xoá lý do ngừng cho một lượt tải xuống, ngoại trừ việc contentId phải được đặt thành null.

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

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

Bạn có thể tạm dừng và tiếp tục tất cả nội dung 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 tải xuống bị tạm dừng, các tệp sẽ ở trạng thái Download.STATE_QUEUED. Không giống như đặt lý do dừng, phương pháp này không duy trì bất kỳ trạng thái nào thay đổi. Việ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 quy tắc ràng buộc phải được đáp ứng cho tải xuống để tiếp tục. Bạn có thể đặt các yêu cầu này 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 mã này một cách linh động bằng cách gửi một lệnh vào 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 quá trình tải xuống không thể tiếp tục do không đáp ứng các yêu cầu, sẽ ở trạng thái Download.STATE_QUEUED. Bạn có thể truy vấn cụm từ không được đáp ứng với DownloadManager.getNotMetRequirements().

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

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

Khi quá trình tải xuống không tiếp tục được thực hiện do đã đạt số lượng tối đa cho phép tải xuống song song đang diễn ra, mục này sẽ ở trạng thái Download.STATE_QUEUED.

Truy vấn tệp đã 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 tệp đã hoàn tất hoặc không thành công. DownloadIndex có thể được lấy bằng cách gọi DownloadManager.getDownloadIndex(). Con trỏ lặp lại tất cả các lần tải xuống sau đó có thể được lấy bằng cách gọi DownloadIndex.getDownloads(). Ngoài ra, trạng thái của một lượt tải xuống có thể được truy vấn 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). Chiến dịch này rất hữu ích trong việc 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 lượt tải xuống hiện tại.

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

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

Kotlin

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

Java

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

Xem DownloadManagerListener trong lớp DownloadTracker của ứng dụng minh hoạ để biết một ví dụ cụ thể.

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

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ừ thì dữ liệu sẽ được đọc từ tệp tải xuống Cache 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ùng một Thực thể Cache được dùng để tải xuống rồi chèn vào thực thể đó 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 được tải xuống thì bạn nên định cấu hình CacheDataSource.Factory thành chỉ đọc để tránh tải nội dung đó xuống trong lúc 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 lại. Sau đó, phát nội dung tải xuống chỉ đơn giản như truyền MediaItem tương ứng đến người chơi. Một MediaItem có thể lấy được 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 lại tất cả MediaItem giây. Bạn cũng có thể cung cấp bộ nhớ đệm tải xuống cho thực thể MediaSource riêng lẻ có thể được truyền trực tiếp đến người chơi:

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 và phát luồng thích ứng

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

Đối với các lần 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 được phát. Tương tự, để tải xuống, bạn có thể sử dụng DownloadHelper để chọn bản nhạc để tải xuống. Cách sử dụng thông thường của DownloadHelper hãy làm theo các bước sau:

  1. Tạo DownloadHelper bằng một trong 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. Kiểm tra các kênh mặc định được chọn bằng cách sử dụng getMappedTrackInfo (không bắt buộc) và getTrackSelections, đồng thời điều chỉnh bằng clearTrackSelections, replaceTrackSelectionsaddTrackSelection.
  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 chuyển đến DownloadService của bạn tới thêm tệp tải xuống như đã mô tả ở trên.
  4. Phát hành 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 được thiết lập để khớp với các thông báo trong DownloadRequest để người chơi chỉ cố gắng phát một nhóm bản nhạc đã được tải xuống. Sử dụng Download.request.toMediaItemDownloadRequest.toMediaItem để tạo MediaItem sẽ xử lý vấn đề này cho bạn.