下載媒體

ExoPlayer 可讓您下載媒體並離線播放。在多數情況下,即使應用程式在背景執行,下載作業還是能繼續。針對這些用途,您的應用程式應該將 DownloadService 設為子類別,並向服務傳送指令,藉此新增、移除及控制下載內容。下圖說明涉及的主要類別。

用來下載媒體的類別。箭頭方向指示資料流。

  • DownloadService:納入 DownloadManager 並向其轉送指令。即使應用程式在背景執行,DownloadManager 仍可繼續執行。
  • DownloadManager:管理多個下載內容、載入 (及儲存) 在 DownloadIndex 中的狀態,以及根據網路連線等需求開始和停止下載作業。如要下載內容,管理員通常會讀取從 HttpDataSource 下載的資料,並將其寫入 Cache
  • DownloadIndex:保留下載的狀態。

建立 DownloadService

如要建立 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 知道它可以使用,就會將其本身置於前景中。

正在移除下載內容

如要移除下載,請將移除指令傳送至 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 狀態。

查詢下載內容

您可以查詢 DownloadManagerDownloadIndex 以取得所有下載的狀態,包括已完成或失敗的下載狀態。您可以呼叫 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 傳遞至播放器即可。MediaItem 可以使用 Download.request.toMediaItemDownload 取得,或使用 DownloadRequest.toMediaItem 直接從 DownloadRequest 取得。

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. 您可以選擇使用 getMappedTrackInfogetTrackSelections 檢查預設選取的測試群組,並使用 clearTrackSelectionsreplaceTrackSelectionsaddTrackSelection 進行調整。
  3. 呼叫 getDownloadRequest,為所選曲目建立 DownloadRequest。系統可將要求傳遞至 DownloadService,以新增下載項目,如上所述。
  4. 使用 release() 釋出輔助程式。

如要播放下載的自動調整內容,您必須設定播放器並傳遞對應的 MediaItem。如上所述。

建構 MediaItem 時,MediaItem.localConfiguration.streamKeys 必須設為與 DownloadRequest 中的相符,讓玩家只會嘗試播放已下載的曲目子集。使用 Download.request.toMediaItemDownloadRequest.toMediaItem 建構 MediaItem 即可為您處理這項工作。