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 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 ở chế độ nền. Đối với những trường hợp sử dụng này, ứng dụng của bạn phải tạo lớp con DownloadService và gửi các lệnh đến dịch vụ để thêm, xoá và kiểm soát các lượt tải xuống. Sơ đồ sau đây cho thấy các lớp chính có liên quan.
DownloadService: Bao bọc mộtDownloadManagervà chuyển tiếp các lệnh đếnDownloadManagerđó. Dịch vụ này cho phépDownloadManagertiế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ư khả năng 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ừHttpDataSourcevà ghi dữ liệu đó vàoCache.DownloadIndex: Duy trì trạng thái của các bản tải xuống.
Tạo DownloadService
Để tạo một DownloadService, hãy tạo lớp con cho DownloadService và triển khai các phương thức trừu tượng của DownloadService:
getDownloadManager(): Trả vềDownloadManagersẽ được dùng.getScheduler(): Trả về mộtSchedulerkhông bắt buộc.Schedulernày có thể khởi động lại dịch vụ khi các yêu cầu cần thiết để quá trình tải xuống đang chờ xử lý diễn ra đượ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). Hãy xem javadoc 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ể dùngDownloadNotificationHelper.buildProgressNotificationđể tạo một 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 DemoDownloadService và AndroidManifest.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 khởi tạo một 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 nội dung tải xuống
Để thêm một lượt tải xuống, hãy tạo một DownloadRequest rồi gửi đến DownloadService. Đối với luồng thích ứng, hãy sử dụng DownloadHelper để giúp 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 riêng biệ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ể 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 mình. DownloadRequest.Builder cũng có một số phương thức setter không bắt buộc. Ví dụ: setKeySetId và setData có thể được dùng để đặ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, tương ứ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 những 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 nội dung 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ụ sẽ bắt đầu ở nền trước hay không. Nếu ứng dụng của bạn đã ở nền trước, thì thông thường, bạn nên đặt tham số foreground thành false vì DownloadService sẽ tự đặt mình ở nền trước nếu xác định rằng 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:
- Tệp tải xuống không có lý do dừng.
- Không tạm dừng tải xuống.
- Các yêu cầu để tải xuống được đáp ứng. Các yêu cầu có thể chỉ định những hạn chế về các loại mạng được phép, cũng như việc thiết bị có nên ở trạng thái không hoạt động hay được kết nối với bộ sạc hay không.
- Không vượt quá số lượt 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 cho một hoặc tất cả các lượt 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). Những ứ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 dừng từng 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 hoạt động theo cách tương tự như việc đặt và xoá lý do dừng cho một lượt tải xuống duy nhất, ngoại trừ việc bạn phải đặt contentId thành null.
Khi một lượt tải xuống có lý do dừng khác 0, lượt tải xuống đó sẽ ở trạng thái Download.STATE_STOPPED. Lý do dừng được duy trì trong DownloadIndex và do đó được giữ lại nếu quy trình ứng dụng bị huỷ 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 lượt tải xuống theo cách 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, các tệp tải xuống 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ỳ thay đổi nào về trạng thái. Điều này chỉ ảnh hưởng đến trạng thái thời gian chạy của DownloadManager.
Đặt các yêu cầu để quá trình tải xuống diễn ra
Bạn có thể dùng Requirements để chỉ định các điều kiện ràng buộc phải đáp ứng để quá trình tải xuống tiếp tục. 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, tệp sẽ ở trạng thái Download.STATE_QUEUED. Bạn có thể truy vấn các yêu cầu không đáp ứng bằng 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 lượt tải xuống song song bằng cách gọi DownloadManager.setMaxParallelDownloads(). Thao tá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 vì đã đạt đến số lượng lượt tải xuống song song tối đa, lượt 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 lượt tải xuống, bao gồm cả những lượt đã 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 một 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 lượt tải xuống hiện tại (tức là chưa hoàn tất hoặc thất bại). 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 khác trên giao diện người dùng 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 tin 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ể.
Phát nội dung đã tải xuống
Việc phát nội dung đã tải xuố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ừ 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 chính 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 phiên bản trình phát cũng sẽ đượ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 quá trình 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 tệp tải xuống cũng đơn giản như việc truyền MediaItem tương ứng cho trình phát. Bạn có thể lấy MediaItem từ Download bằng cách sử dụng Download.request.toMediaItem hoặc trực tiếp từ DownloadRequest bằng cách sử dụng DownloadRequest.toMediaItem.
Cấu hình MediaSource
Ví dụ trên giúp bộ nhớ đệm tải xuống có sẵn để 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. 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 các 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 âm thanh và phụ đề. Thường có nhiều bản chứa cùng một nội dung với 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 phụ đề cùng loại nhưng chứa nội dung khác nhau (ví dụ: nhiều bản âm thanh bằng các ngôn ngữ khác nhau).
Đối với chế độ phát trực tuyến, bạn có thể dùng bộ chọn phụ đề để chọn phụ đề muốn phát. Tương tự, đối với việc tải xuống, bạn có thể dùng DownloadHelper để chọn bản nhạc cần tải xuống. Cách sử dụng DownloadHelper thông thường là làm theo các bước sau:
- Tạo một
DownloadHelperbằng cách dùng một thực thểDownloadHelper.Factory. Chuẩn bị trình trợ giúp và đợi lệnh gọi lại. - Bạn có thể kiểm tra các bản nhạc được chọn theo mặc định bằng cách sử dụng
getMappedTrackInfovàgetTrackSelections, đồng thời điều chỉnh bằng cách sử dụngclearTrackSelections,replaceTrackSelectionsvàaddTrackSelection. - Tạo
DownloadRequestcho các bản nhạc đã chọn bằng cách gọigetDownloadRequest. Yêu cầu có thể được truyền đếnDownloadServiceđể thêm nội dung tải xuống, như mô tả ở trên. - Giải phóng đối tượng hỗ trợ bằng cách dùng
release().
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);
Để 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 sao cho khớp với các DownloadRequest để trình phát chỉ cố gắng phát tập hợp con gồm những bản nhạc đã tải xuống. Việc sử dụng Download.request.toMediaItem và DownloadRequest.toMediaItem để tạo MediaItem sẽ giúp bạn xử lý vấn đề này.