下载媒体

ExoPlayer 提供下载媒体以供离线播放的功能。大多数 用例,最好即使您的应用处于 背景。对于这些用例,您的应用应创建 DownloadService 和 向该服务发送添加、删除和控制下载内容的命令。通过 下图显示了涉及的主要类。

用于下载媒体的类。箭头方向表示数据的流动。

  • DownloadService:封装 DownloadManager 并将命令转发给它。通过 服务可让 DownloadManager 即使在应用处于运行状态时也保持运行 背景。
  • DownloadManager:管理多个下载内容,以及加载(和存储)其 状态包括从(和到)DownloadIndex,根据 网络连接等要求要下载 则管理器通常会读取从 HttpDataSource,并将其写入 Cache
  • DownloadIndex:保留下载状态。

创建 DownloadService

如需创建 DownloadService,请将其子类化并实现其 抽象方法:

  • getDownloadManager():返回要使用的 DownloadManager
  • getScheduler():返回一个可选的 Scheduler,它可能会重启 服务。 ExoPlayer 提供以下实现: <ph type="x-smartling-placeholder">
  • 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 是内容的唯一标识符。在简单的情况下, contentUri 通常可以用作 contentId,但应用可以免费使用 找到最适合他们的用例的 ID 方案。DownloadRequest.Builder还有 一些可选的 setter 方法。例如,setKeySetIdsetData 可用于 设置应用希望与下载内容关联的 DRM 和自定义数据; 。内容的 MIME 类型也可以使用 setMimeType 指定, ,针对无法从 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 发送 remove 命令,以移除下载内容。 其中 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(), 用于仅返回当前下载的状态(即未完成或失败)。这个 方法对于更新通知以及显示的其他界面组件非常有用 当前下载的进度和状态

收听下载内容

您可以向 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.FactoryCache 实例,并将其注入 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)通常包含 媒体轨道。系统中通常有多个轨道包含相同的内容 (例如标清、高清和 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,然后使用clearTrackSelections进行调整 replaceTrackSelectionsaddTrackSelection
  3. 通过调用以下方法来为所选轨道创建 DownloadRequestgetDownloadRequest。您可以将该请求传递给 DownloadService, 添加下载内容(如上所述)。
  4. 使用 release() 释放帮助程序。

若要播放已下载的自适应内容,您需要配置播放器和 传递相应的 MediaItem,如上所述。

构建 MediaItem 时,MediaItem.localConfiguration.streamKeys 必须为 设置为与 DownloadRequest 中的请求相匹配,以便播放器仅尝试 播放已下载的部分曲目。使用 Download.request.toMediaItemDownloadRequest.toMediaItem,用于构建 MediaItem 将为您处理。