ExoPlayer cung cấp chức năng tải xuống phương tiện để phát lại ngoại tuyến. 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ế độ nền. Đối với những 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, xóa 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ọcDownloadManagervà chuyển tiếp các lệnh đến đó. Dịch vụ này cho phépDownloadManagertiếp tục chạy ngay cả khi ứng dụng đang ở 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 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 xuống nội dung, trình quản lý thường sẽ đọc dữ liệu đang được tải xuống từHttpDataSourcevà ghi 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ềSchedulertùy chọn, có thể khởi động lại dịch vụ khi các yêu cầu cần thiết để tiến trình tải xuống đang chờ xử lý được đáp ứng. ExoPlayer cung cấp các 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 ứng dụng.WorkManagerScheduler, sử dụng WorkManager.
getForegroundNotification(): Trả về thông báo sẽ hiển thị khi dịch vụ đang chạy ở chế độ nền. 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 của bạn:
<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 mộ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);
Xem DemoUtil trong ứng dụng demo để biết ví dụ cụ thể.
Thêm nội dung tải xuống
Để thêm nội dung tải xuống, hãy tạo DownloadRequest và gửi đến DownloadService của bạn. Đối với các luồng thích ứng, hãy sử dụng DownloadHelper để giúp xây dựng DownloadRequest. Ví dụ sau đây cho thấy cách tạo 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à mã định danh duy nhất cho nội dung. Trong những trường hợp đơn giản, contentUri thường có thể được sử 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 đồ ID 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ố trình thiết lập tùy chọn. Ví dụ: setKeySetId và setData có thể được sử dụng để thiết lập DRM và dữ liệu tùy chỉnh mà ứng dụng muốn liên kết với quá trình tải xuống. Loại MIME của nội dung cũng có thể được chỉ định bằng cách sử dụng setMimeType, như một gợi ý cho những trường hợp không thể suy ra loại nội dung từ contentUri.
Sau khi được 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ụ có được khởi động ở chế độ nền trước hay không. Nếu ứng dụng của bạn đã ở chế độ nền trước, thì thông thường tham số foreground phải được đặt thành false vì DownloadService sẽ tự chuyển sang chế độ nền trước nếu xác định rằng ứng dụng có việc phải 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ể xóa toàn bộ 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:
- Quá trình tải xuống không có lý do dừng lại.
- Không tạm dừng tải xuống.
- Đáp ứng được các yêu cầu để quá trình tải xuống được tiến hành. 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 được vượt quá số lượng tải xuống song song tối đa.
Tất cả các điều kiện này có thể được kiểm soát bằng cách gửi lệnh đến DownloadService của bạn.
Đặ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 không (Download.STOP_REASON_NONE = 0 là giá trị đặc biệt có nghĩa là quá trình tải xuống không bị dừng lại). 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 tại sao mỗi lần tải xuống bị dừng. Việc thiết lập và xóa lý do dừng cho tất cả các lượt tải xuống có cùng cách hoạt động như thiết lập và xóa lý do dừng cho một lượt tải xuống duy nhất, ngoại trừ việc contentId phải được đặt thành null.
Khi quá trình tải xuống có lý do dừng khác 0, quá trình đó sẽ ở trạng thái Download.STATE_STOPPED. Lý do dừng được lưu trong DownloadIndex và do đó được giữ lại nếu quy trình ứng dụng bị dừng và sau đó được 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
Tất cả các lượt tải xuống có thể 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 quá trình tải xuống bị tạm dừng, chúng sẽ ở trạng thái Download.STATE_QUEUED.
Không giống như lý do dừng thiết lập, cách tiếp cận này không lưu lại bất kỳ thay đổi trạng thái nào. Nó chỉ ảnh hưởng đến trạng thái thời gian chạy của DownloadManager.
Thiết lập các yêu cầu để tải xuống tiến triển
Requirements có thể được sử dụng để chỉ định các ràng buộc phải đáp ứng để quá trình tải xuống có thể tiếp tục. Có thể thiết lập các yêu cầu bằng cách gọi DownloadManager.setRequirements() khi tạo DownloadManager, như trong ví dụ ở trên. Chúng cũng có thể được thay đổi động 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 quá trình tải xuống không thể tiếp tục vì không đáp ứng được 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 chưa đáp ứng bằng DownloadManager.getNotMetRequirements().
Thiết lập số lượng tải xuống song song tối đa
Có thể thiết lập số lượng tải xuống song song tối đa bằng cách gọi DownloadManager.setMaxParallelDownloads(). Thông thường, thao tác này sẽ đượ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 thể tiếp tục vì số lượng tải xuống song song tối đa đã đạt đến mức tối đa, quá trình sẽ ở trạng thái Download.STATE_QUEUED.
Truy vấn tải xuống
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 tải xuống đã hoàn tất hoặc không thành công. Có thể lấy DownloadIndex bằng cách gọi DownloadManager.getDownloadIndex(). Sau đó, có thể lấy được con trỏ lặp lại tất cả các lượt tải xuống bằng cách gọi DownloadIndex.getDownloads(). Ngoài ra, trạng thái của một lần 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 các lượt 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 pháp này hữu ích để cập nhật thông báo và các thành phần UI 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 tải xuống
Bạn có thể thêm trình lắng nghe vào DownloadManager để được 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. });
Xem DownloadManagerListener trong lớp DownloadTracker của ứng dụng demo để biết ví dụ cụ thể.
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ừ 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 CacheDataSource.Factory bằng cùng phiên bản Cache đã dùng để tải xuống và đưa phiên bản này vào DefaultMediaSourceFactory khi xây dựng 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 sử dụng để phát nội dung không tải xuống thì CacheDataSource.Factory phải được định cấu hình ở chế độ chỉ đọc để tránh tải xuống nội dung đó trong quá trình phát lại.
Sau khi trình phát được 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 đó, việc phát bản tải xuống cũng đơn giản như việc truyền MediaItem tương ứng cho trình phát. 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ước đó giúp bộ nhớ đệm tải xuống có thể phát lại tất cả MediaItem. Bạn cũng có thể cung cấp bộ đệm tải xuống cho từng phiên bản MediaSource riêng lẻ, 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 nhạc phương tiện. Thường có nhiều bản phụ đề chứa cùng một nội dung nhưng có chất lượng khác nhau (ví dụ: bản phụ đề 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 âm thanh bằng nhiều ngôn ngữ khác nhau).
Đối với nội dung 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ự, để tải xuống, có thể sử dụng DownloadHelper để chọn bản nhạc nào sẽ được tải xuống. Cách sử dụng thông thường của DownloadHelper tuân theo các bước sau:
- Xây dựng
DownloadHelperbằng cách sử dụng phiên bảnDownloadHelper.Factory. Chuẩn bị trình trợ giúp và đợi lệnh gọi lại.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);
- Tùy chọn, hãy kiểm tra các bản nhạc mặc định đã chọn bằng
getMappedTrackInfovàgetTrackSelections, rồi thực hiện điều chỉnh bằngclearTrackSelections,replaceTrackSelectionsvàaddTrackSelection. - Tạo
DownloadRequestcho các bản nhạc đã chọn bằng cách gọigetDownloadRequest. Bạn có thể chuyển yêu cầu đế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().
Để 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 xây dựng MediaItem, MediaItem.localConfiguration.streamKeys phải được đặt để khớp với DownloadRequest để trình phát chỉ cố gắng phát tập hợp con các bản nhạc đã được tải xuống. Sử dụng Download.request.toMediaItem và DownloadRequest.toMediaItem để xây dựng MediaItem sẽ giúp bạn thực hiện việc này.