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.
DownloadService
: GóiDownloadManager
và chuyển tiếp các lệnh đếnDownloadManager
đó. Dịch vụ này cho phépDownloadManager
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àoCache
.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ộtScheduler
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ụngDownloadNotificationHelper.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 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 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ụ: 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 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 false
vì DownloadService
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:
- Tạo
DownloadHelper
bằng một trong các phương thứcDownloadHelper.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);
- Bạn có thể kiểm tra các kênh được chọn mặc định bằng
getMappedTrackInfo
vàgetTrackSelections
, đồng thời điều chỉnh bằngclearTrackSelections
,replaceTrackSelections
vàaddTrackSelection
. - Tạo
DownloadRequest
cho các kênh đã chọn bằng cách gọigetDownloadRequest
. Bạn có thể chuyển yêu cầu này đếnDownloadService
để thêm nội dung tải xuống, như mô tả ở trên. - 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.toMediaItem
và DownloadRequest.toMediaItem
để tạo MediaItem
sẽ giải quyết vấn đề này cho bạn.