ExoPlayer 提供下載媒體功能,可供離線播放。在大多數情況下,我們建議即使應用程式在背景執行,也還是建議您繼續執行下載作業。針對這些用途,應用程式應將 DownloadService
設為子類別,並將指令傳送至服務,以便新增、移除及控制下載作業。下圖顯示相關的主要類別。
DownloadService
:包裝DownloadManager
,並將指令轉送至該包裝。這項服務可讓DownloadManager
在應用程式處於背景執行時繼續執行。DownloadManager
:管理多個下載作業,從DownloadIndex
載入 (和儲存) 其狀態,並根據網路連線等需求啟動及停止下載作業。為了下載內容,管理員通常會讀取從HttpDataSource
下載的資料,並將資料寫入Cache
。DownloadIndex
:儲存下載項目的狀態。
建立 DownloadService
如要建立 DownloadService
,請建立子類別並實作其抽象方法:
getDownloadManager()
:傳回要使用的DownloadManager
。getScheduler()
:傳回選用的Scheduler
,可在符合待處理下載項目的進度所需條件時重新啟動服務。ExoPlayer 提供以下實作方式:PlatformScheduler
,使用 JobScheduler (最低 API 級別為 21)。如需應用程式權限需求,請參閱 PlatformScheduler Javadoc。WorkManagerScheduler
,使用 WorkManager。
getForegroundNotification()
:在服務於前景執行時,傳回要顯示的通知。您可以使用DownloadNotificationHelper.buildProgressNotification
建立預設樣式的通知。
最後,請在 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>
如需具體範例,請參閱 ExoPlayer 示範應用程式中的 DemoDownloadService
和 AndroidManifest.xml
。
建立 DownloadManager
下列程式碼片段示範如何將 DownloadManager
例項化,這可由 DownloadService
中的 getDownloadManager()
傳回:
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);
如需具體範例,請參閱試用版應用程式中的 DemoUtil
。
新增下載
如要新增下載項目,請建立 DownloadRequest
並傳送至 DownloadService
。如要使用自動調整式串流,請使用 DownloadHelper
建構 DownloadRequest
。以下範例說明如何建立下載要求:
Kotlin
val downloadRequest = DownloadRequest.Builder(contentId, contentUri).build()
Java
DownloadRequest downloadRequest = new DownloadRequest.Builder(contentId, contentUri).build();
在這個範例中,contentId
是內容的專屬 ID。在簡單的情況下,contentUri
通常可用於 contentId
,但應用程式可自由使用任何最適合其用途的 ID 配置。DownloadRequest.Builder
也有一些選用的 setter。舉例來說,setKeySetId
和 setData
可分別用於設定 DRM 和應用程式要與下載項目建立關聯的自訂資料。您也可以使用 setMimeType
指定內容的 MIME 類型,做為在無法從 contentUri
推斷內容類型的情況下,用於提示的參考值。
建立完成後,系統會將要求傳送至 DownloadService
以新增下載項目:
Kotlin
DownloadService.sendAddDownload( context, MyDownloadService::class.java, downloadRequest, /* foreground= */ false )
Java
DownloadService.sendAddDownload( context, MyDownloadService.class, downloadRequest, /* foreground= */ false);
在此範例中,MyDownloadService
是應用程式的 DownloadService
子類別,而 foreground
參數則用於控制服務是否會在前景啟動。如果應用程式已處於前景,則 foreground
參數通常應設為 false
,因為 DownloadService
會在判斷有工作要做時,將自己置於前景。
正在移除下載內容
如要移除下載項目,請將移除指令傳送至 DownloadService
,其中 contentId
會標示要移除的下載項目:
Kotlin
DownloadService.sendRemoveDownload( context, MyDownloadService::class.java, contentId, /* foreground= */ false )
Java
DownloadService.sendRemoveDownload( context, MyDownloadService.class, contentId, /* foreground= */ false);
您也可以使用 DownloadService.sendRemoveAllDownloads
移除所有已下載的資料。
開始及停止下載
只有在符合以下四個條件時,系統才會開始下載:
- 下載作業沒有停止原因。
- 下載作業不會暫停。
- 符合下載進度的要求。您可以透過需求指定允許的網路類型限制,以及裝置應處於閒置狀態或連接充電器。
- 並行下載數量未超過上限。
您可以透過傳送指令至 DownloadService
,控制所有這些條件。
設定及清除下載停止原因
您可以為單次或所有下載作業設定停止的原因:
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
可以是任何非零值 (Download.STOP_REASON_NONE = 0
是特殊值,表示下載作業不會停止)。如果應用程式有多種原因會導致停止下載作業,便可使用不同的值來追蹤每次下載停止的原因。設定及清除所有下載項目的停止原因,與設定及清除單一下載項目的停止原因相同,唯一的差異是 contentId
應設為 null
。
如果下載作業的停止原因不為零,則會處於 Download.STATE_STOPPED
狀態。停止原因會保留在 DownloadIndex
中,因此如果應用程式程序遭到終止,並在稍後重新啟動,這些原因就會保留。
暫停及恢復所有下載作業
所有下載作業都可以暫停及繼續,方法如下:
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);
暫停下載時,下載作業會處於 Download.STATE_QUEUED
狀態。與設定停止原因不同,這個方法不會保留任何狀態變更。這只會影響 DownloadManager
的執行階段狀態。
設定下載進度的要求
Requirements
可用來指定必須符合的限制,才能繼續下載。您可以在建立 DownloadManager
時呼叫 DownloadManager.setRequirements()
,藉此設定需求,如上方的範例所示。您也可以傳送指令至 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);
如果下載作業因不符合規定而無法繼續,就會處於 Download.STATE_QUEUED
狀態。您可以使用 DownloadManager.getNotMetRequirements()
查詢不符合的規定。
設定並行下載數量上限
您可以呼叫 DownloadManager.setMaxParallelDownloads()
來設定平行下載數量上限。這通常會在建立 DownloadManager
時完成,如上方的範例所示。
如果並行下載數量已達上限,下載作業就無法繼續進行,並會處於 Download.STATE_QUEUED
狀態。
查詢下載內容
您可以查詢 DownloadManager
的 DownloadIndex
,瞭解所有下載作業的狀態,包括已完成或失敗的作業。您可以呼叫 DownloadManager.getDownloadIndex()
取得 DownloadIndex
。接著,您可以呼叫 DownloadIndex.getDownloads()
取得可遍歷所有下載項目的游標。或者,您也可以呼叫 DownloadIndex.getDownload()
來查詢單一下載作業的狀態。
DownloadManager
也提供 DownloadManager.getCurrentDownloads()
,只會傳回目前 (即未完成或失敗) 下載作業的狀態。這個方法適用於更新通知和其他 UI 元件,以顯示目前下載作業的進度和狀態。
聆聽下載內容
您可以為 DownloadManager
新增事件監聽器,在目前下載項目變更狀態時收到通知:
Kotlin
downloadManager.addListener( object : DownloadManager.Listener { // Override methods of interest here. } )
Java
downloadManager.addListener( new DownloadManager.Listener() { // Override methods of interest here. });
如需具體範例,請參閱試用版應用程式 DownloadTracker
類別中 DownloadManagerListener
。
播放已下載的內容
播放下載內容的方式與播放線上內容類似,差別在於系統會從下載的 Cache
讀取資料,而非透過網路讀取資料。
如要播放已下載的內容,請使用用於下載的相同 Cache
例項建立 CacheDataSource.Factory
,並在建構播放器時將其插入 DefaultMediaSourceFactory
:
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();
如果同一個播放器執行個體也用於播放未下載的內容,則應將 CacheDataSource.Factory
設為唯讀,以免在播放期間下載該內容。
使用 CacheDataSource.Factory
設定玩家後,玩家將可存取已下載的內容。如此一來,只要將對應的 MediaItem
傳遞至播放器,就能輕鬆播放下載。您可以使用 Download.request.toMediaItem
從 Download
取得 MediaItem
,也可以直接使用 DownloadRequest.toMediaItem
從 DownloadRequest
取得 MediaItem
。
MediaSource 設定
上述範例會讓下載快取可用於播放所有 MediaItem
。您也可以為個別 MediaSource
例項提供下載快取,可直接傳遞給播放器:
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();
下載及播放自適應串流
自適應串流 (例如 DASH、SmoothStreaming 和 HLS) 通常含有多個媒體音軌。通常會有多個軌道,其中包含不同畫質 (例如 SD、HD 和 4K 視訊軌) 的相同內容。同類型的音軌也可能包含不同內容 (例如不同語言的多個音軌)。
針對串流播放功能,您可以使用曲目選取器選擇要播放的曲目。同樣地,如果要下載音訊,您可以使用 DownloadHelper
選擇要下載的曲目。DownloadHelper
的一般用法步驟如下:
- 使用
DownloadHelper.forMediaItem
方法之一建構DownloadHelper
。準備輔助程式並等待回呼。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);
- 您可以選擇使用
getMappedTrackInfo
和getTrackSelections
檢查預設選取的曲目,並使用clearTrackSelections
、replaceTrackSelections
和addTrackSelection
進行調整。 - 呼叫
getDownloadRequest
即可為所選音軌建立DownloadRequest
。您可以按照上述方式將要求傳遞至DownloadService
新增下載內容。 - 使用
release()
釋出輔助程式。
如要播放下載的自動調整式內容,必須設定播放器並傳遞對應的 MediaItem
(如上所述)。
建構 MediaItem
時,MediaItem.localConfiguration.streamKeys
必須設為與 DownloadRequest
中的相符,這樣播放器就只會嘗試播放已下載的曲目子集。使用 Download.request.toMediaItem
和 DownloadRequest.toMediaItem
建構 MediaItem
即可解決這個問題。