下載媒體

ExoPlayer 提供下載媒體以供離線播放的功能。大多數 那麼,即使您的應用程式位於下列國家/地區,仍建議您繼續下載 背景。針對這些用途,應用程式應將 DownloadService 和 傳送指令給服務,以便新增、移除及控管下載內容。 下圖顯示涉及的主要類別。

下載媒體的類別。箭頭方向代表資料的流向。

  • DownloadService:納入 DownloadManager 並將指令轉送至該函式。 服務允許 DownloadManager 即使應用程式處於執行狀態時也繼續執行 背景工作。
  • DownloadManager:管理多個下載、載入 (及儲存) 這些項目 在 DownloadIndex 之間 (以及到) 的狀態,根據 網路連線等需求如要下載 管理員通常會讀取 HttpDataSource 並寫入 Cache
  • DownloadIndex:保留下載狀態。

建立下載服務

如要建立 DownloadService,請將其設為子類別,並實作 抽象方法:

  • getDownloadManager():傳回要使用的 DownloadManager
  • getScheduler():傳回選用的 Scheduler,可重新啟動 服務。 ExoPlayer 提供以下實作方式:
  • 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 中查看 DemoDownloadServiceAndroidManifest.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。舉例來說,setKeySetIdsetData 可用於 設定應用程式要與下載內容連結的 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.setRequirements() 建立 DownloadManager,如 如上範例所示也可以傳送指令來動態變更 至 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 狀態。

查詢下載內容

您可以查詢 DownloadManagerDownloadIndex,以取得所有 下載的內容,包括已完成或失敗的下載內容。DownloadIndex 呼叫 DownloadManager.getDownloadIndex() 即可取得。遊標 所有下載皆會疊代 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 讀取,而不是透過網路讀取。

如要播放下載的內容,請CacheDataSource.Factory使用 用於下載的 Cache 執行個體,並插入該執行個體 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 給玩家一樣簡單。MediaItem 可以使用 Download.request.toMediaItemDownload 取得,或 直接從 DownloadRequest 使用 DownloadRequest.toMediaItem

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 的一般用量 步驟如下:

  1. 使用其中一個 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);
    
  2. 視需要使用 getMappedTrackInfo 檢查預設所選測試群組 和getTrackSelections,並使用 clearTrackSelectionsreplaceTrackSelectionsaddTrackSelection
  3. 呼叫 ,為所選音軌建立 DownloadRequest getDownloadRequest。要求可傳遞至您的 DownloadService,以便: 按照上述說明新增下載內容。
  4. 使用 release() 釋出輔助程式。

必須設定播放器和 按照上述方式傳遞對應的 MediaItem

建構 MediaItem 時,必須採用 MediaItem.localConfiguration.streamKeys 設為與 DownloadRequest 中的值相符,讓玩家只會嘗試 播放已下載的部分曲目。使用 Download.request.toMediaItemDownloadRequest.toMediaItem 用來建構 「MediaItem」會為你處理這個問題。