广告插入

ExoPlayer 可用于客户端和服务器端广告插入。

客户端广告插入

在客户端广告插入中,播放器在内容和广告之间切换时,会在加载来自不同 URL 的媒体之间切换。广告信息与媒体分开加载,例如来自 XML VASTVMAP 广告标签。这可能包括相对于内容开头的广告提示位置、实际广告媒体 URI 和元数据,例如特定广告是否可跳过。

当使用 ExoPlayer 的 AdsMediaSource 进行客户端广告插入时,播放器会拥有有关要播放的广告的信息。这有几个好处

  • 播放器可以使用其 API 公开与广告相关的元数据和功能。
  • ExoPlayer UI 组件 可以自动显示广告位置的标记,并根据广告是否正在播放来更改其行为。
  • 在内部,播放器可以在广告和内容之间的过渡过程中保持一致的缓冲区。

在此设置中,播放器负责在广告和内容之间切换,这意味着应用程序无需负责控制用于广告和内容的多个独立的后台/前台播放器。

在准备用于客户端广告插入的内容视频和广告标签时,广告应理想地放置在内容视频的同步样本(关键帧)处,以便播放器可以无缝地恢复内容播放。

声明式广告支持

在构建 MediaItem 时可以指定广告标签 URI。

Kotlin

val mediaItem =
  MediaItem.Builder()
    .setUri(videoUri)
    .setAdsConfiguration(MediaItem.AdsConfiguration.Builder(adTagUri).build())
    .build()

Java

MediaItem mediaItem =
    new MediaItem.Builder()
        .setUri(videoUri)
        .setAdsConfiguration(
            new MediaItem.AdsConfiguration.Builder(adTagUri).build())
        .build();

要启用播放器对指定广告标签的媒体项目的支持,在创建播放器时,需要构建并注入一个配置了 AdsLoader.ProviderAdViewProviderDefaultMediaSourceFactory

Kotlin

val mediaSourceFactory: MediaSource.Factory =
  DefaultMediaSourceFactory(context).setLocalAdInsertionComponents(adsLoaderProvider, playerView)
val player = ExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build()

Java

MediaSource.Factory mediaSourceFactory =
    new DefaultMediaSourceFactory(context)
        .setLocalAdInsertionComponents(adsLoaderProvider, /* adViewProvider= */ playerView);
ExoPlayer player =
    new ExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build();

在内部,DefaultMediaSourceFactory 将内容媒体源包装在 AdsMediaSource 中。AdsMediaSource 将从 AdsLoader.Provider 获取 AdsLoader,并使用它根据媒体项目的广告标签插入广告。

ExoPlayer 的 PlayerView 实现 AdViewProvider。ExoPlayer IMA 库提供了一个易于使用的 AdsLoader,如下所述。

包含广告的播放列表

播放包含多个媒体项目的播放列表时,默认行为是为每个媒体 ID、内容 URI 和广告标签 URI 组合请求广告标签并存储广告播放状态。这意味着用户将看到具有不同媒体 ID 或内容 URI 的每个带有广告的媒体项目的广告,即使广告标签 URI 匹配也是如此。如果媒体项目重复,则用户只会看到相应的广告一次(广告播放状态存储广告是否已播放,因此在首次出现后会跳过它们)。

可以通过传递不透明的广告标识符来自定义此行为,该标识符将给定媒体项目的广告播放状态链接起来,基于对象相等性。以下是一个示例,其中广告播放状态仅链接到广告标签 URI,而不是媒体 ID 和广告标签 URI 的组合,方法是将广告标签 URI 作为广告标识符传递。其效果是广告只会加载一次,并且用户在从头到尾播放播放列表时不会在第二个项目上看到广告。

Kotlin

// Build the media items, passing the same ads identifier for both items,
// which means they share ad playback state so ads play only once.
val firstItem =
  MediaItem.Builder()
    .setUri(firstVideoUri)
    .setAdsConfiguration(MediaItem.AdsConfiguration.Builder(adTagUri).setAdsId(adTagUri).build())
    .build()
val secondItem =
  MediaItem.Builder()
    .setUri(secondVideoUri)
    .setAdsConfiguration(MediaItem.AdsConfiguration.Builder(adTagUri).setAdsId(adTagUri).build())
    .build()
player.addMediaItem(firstItem)
player.addMediaItem(secondItem)

Java

// Build the media items, passing the same ads identifier for both items,
// which means they share ad playback state so ads play only once.
MediaItem firstItem =
    new MediaItem.Builder()
        .setUri(firstVideoUri)
        .setAdsConfiguration(
            new MediaItem.AdsConfiguration.Builder(adTagUri).setAdsId(adTagUri).build())
        .build();
MediaItem secondItem =
    new MediaItem.Builder()
        .setUri(secondVideoUri)
        .setAdsConfiguration(
            new MediaItem.AdsConfiguration.Builder(adTagUri).setAdsId(adTagUri).build())
        .build();
player.addMediaItem(firstItem);
player.addMediaItem(secondItem);

ExoPlayer IMA 库

ExoPlayer IMA 库 提供了 ImaAdsLoader,使将客户端广告插入集成到您的应用中变得容易。它包装了 客户端 IMA SDK 的功能,以支持插入 VAST/VMAP 广告。有关如何使用该库的说明,包括如何处理后台和恢复播放,请参阅 README

演示应用程序 使用 IMA 库,并在示例列表中包含几个示例 VAST/VMAP 广告标签。

UI 注意事项

PlayerView 在默认情况下会在播放广告期间隐藏其传输控件,但应用程序可以通过调用 setControllerHideDuringAds 切换此行为。IMA SDK 将在广告播放期间在播放器顶部显示其他视图(例如,“更多信息”链接和跳过按钮,如果适用)。

IMA SDK 可能会报告广告是否被渲染在播放器顶部的应用程序提供的视图遮挡。需要覆盖对于控制播放至关重要的视图的应用程序必须将其注册到 IMA SDK,以便可以将其从可视性计算中省略。当使用 PlayerView 作为 AdViewProvider 时,它将自动注册其控制覆盖。使用自定义播放器 UI 的应用程序必须通过从 AdViewProvider.getAdOverlayInfos 返回覆盖视图来注册覆盖视图。

有关覆盖视图的更多信息,请参阅 IMA SDK 中的开放测量

伴侣广告

一些广告标签包含其他伴侣广告,这些广告可以在应用程序 UI 中的“插槽”中显示。这些插槽可以通过 ImaAdsLoader.Builder.setCompanionAdSlots(slots) 传递。有关更多信息,请参阅 添加伴侣广告

独立广告

IMA SDK 旨在将广告插入媒体内容中,而不是单独播放独立广告。因此,IMA 库不支持播放独立广告。我们建议为此用例改用 Google 移动广告 SDK

使用第三方广告 SDK

如果您需要通过第三方广告 SDK 加载广告,则值得检查它是否已提供 ExoPlayer 集成。如果没有,实现一个包装第三方广告 SDK 的自定义 AdsLoader 是推荐的方法,因为它提供了上面描述的 AdsMediaSource 的优势。ImaAdsLoader 作为示例实现。

或者,您可以使用 ExoPlayer 的 播放列表支持 来构建广告和内容片段的序列。

Kotlin

// A pre-roll ad.
val preRollAd = MediaItem.fromUri(preRollAdUri)
// The start of the content.
val contentStart =
  MediaItem.Builder()
    .setUri(contentUri)
    .setClippingConfiguration(ClippingConfiguration.Builder().setEndPositionMs(120000).build())
    .build()
// A mid-roll ad.
val midRollAd = MediaItem.fromUri(midRollAdUri)
// The rest of the content
val contentEnd =
  MediaItem.Builder()
    .setUri(contentUri)
    .setClippingConfiguration(ClippingConfiguration.Builder().setStartPositionMs(120000).build())
    .build()

// Build the playlist.
player.addMediaItem(preRollAd)
player.addMediaItem(contentStart)
player.addMediaItem(midRollAd)
player.addMediaItem(contentEnd)

Java

// A pre-roll ad.
MediaItem preRollAd = MediaItem.fromUri(preRollAdUri);
// The start of the content.
MediaItem contentStart =
    new MediaItem.Builder()
        .setUri(contentUri)
        .setClippingConfiguration(
            new ClippingConfiguration.Builder().setEndPositionMs(120_000).build())
        .build();
// A mid-roll ad.
MediaItem midRollAd = MediaItem.fromUri(midRollAdUri);
// The rest of the content
MediaItem contentEnd =
    new MediaItem.Builder()
        .setUri(contentUri)
        .setClippingConfiguration(
            new ClippingConfiguration.Builder().setStartPositionMs(120_000).build())
        .build();

// Build the playlist.
player.addMediaItem(preRollAd);
player.addMediaItem(contentStart);
player.addMediaItem(midRollAd);
player.addMediaItem(contentEnd);

服务器端广告插入

在服务器端广告插入(也称为动态广告插入或 DAI)中,媒体流同时包含广告和内容。DASH 清单可以指向内容和广告片段,可能在不同的周期中。对于 HLS,请参阅 Apple 有关 将广告合并到播放列表中 的文档。

使用服务器端广告插入时,客户端可能需要动态解析媒体 URL 以获取拼接流,可能需要在 UI 中显示广告叠加层,或者可能需要向广告 SDK 或广告服务器报告事件。

ExoPlayer 的 DefaultMediaSourceFactory 可以将所有这些任务委托给服务器端广告插入 MediaSource,用于使用 ssai:// 方案的 URI。

Kotlin

val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setServerSideAdInsertionMediaSourceFactory(ssaiFactory)
    )
    .build()

Java

Player player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context)
                .setServerSideAdInsertionMediaSourceFactory(ssaiFactory))
        .build();

ExoPlayer IMA 库

ExoPlayer IMA 库 提供了 ImaServerSideAdInsertionMediaSource,使轻松集成您的应用中的 IMA 服务器端插入的广告流变得容易。它包装了 Android 版 IMA DAI SDK 的功能,并将提供的广告元数据完全集成到播放器中。例如,这允许您使用诸如 Player.isPlayingAd() 之类的方法,监听内容-广告转换,并让播放器处理跳过已播放广告等广告播放逻辑。

为了使用此类,您需要设置 ImaServerSideAdInsertionMediaSource.AdsLoaderImaServerSideAdInsertionMediaSource.Factory 并将它们连接到播放器。

Kotlin

// MediaSource.Factory to load the actual media stream.
val defaultMediaSourceFactory = DefaultMediaSourceFactory(context)
// AdsLoader that can be reused for multiple playbacks.
val adsLoader =
  ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(context, adViewProvider).build()
// MediaSource.Factory to create the ad sources for the current player.
val adsMediaSourceFactory =
  ImaServerSideAdInsertionMediaSource.Factory(adsLoader, defaultMediaSourceFactory)
// Configure DefaultMediaSourceFactory to create both IMA DAI sources and
// regular media sources. If you just play IMA DAI streams, you can also use
// adsMediaSourceFactory directly.
defaultMediaSourceFactory.setServerSideAdInsertionMediaSourceFactory(adsMediaSourceFactory)
// Set the MediaSource.Factory on the Player.
val player = ExoPlayer.Builder(context).setMediaSourceFactory(defaultMediaSourceFactory).build()
// Set the player on the AdsLoader
adsLoader.setPlayer(player)

Java

// MediaSource.Factory to load the actual media stream.
DefaultMediaSourceFactory defaultMediaSourceFactory = new DefaultMediaSourceFactory(context);
// AdsLoader that can be reused for multiple playbacks.
ImaServerSideAdInsertionMediaSource.AdsLoader adsLoader =
    new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(context, adViewProvider).build();
// MediaSource.Factory to create the ad sources for the current player.
ImaServerSideAdInsertionMediaSource.Factory adsMediaSourceFactory =
    new ImaServerSideAdInsertionMediaSource.Factory(adsLoader, defaultMediaSourceFactory);
// Configure DefaultMediaSourceFactory to create both IMA DAI sources and
// regular media sources. If you just play IMA DAI streams, you can also use
// adsMediaSourceFactory directly.
defaultMediaSourceFactory.setServerSideAdInsertionMediaSourceFactory(adsMediaSourceFactory);
// Set the MediaSource.Factory on the Player.
Player player =
    new ExoPlayer.Builder(context).setMediaSourceFactory(defaultMediaSourceFactory).build();
// Set the player on the AdsLoader
adsLoader.setPlayer(player);

通过使用 ImaServerSideAdInsertionUriBuilder 构建 URL 来加载您的 IMA 资源密钥或内容源 ID 和视频 ID。

Kotlin

val ssaiUri =
  ImaServerSideAdInsertionUriBuilder()
    .setAssetKey(assetKey)
    .setFormat(C.CONTENT_TYPE_HLS)
    .build()
player.setMediaItem(MediaItem.fromUri(ssaiUri))

Java

Uri ssaiUri =
    new ImaServerSideAdInsertionUriBuilder()
        .setAssetKey(assetKey)
        .setFormat(C.CONTENT_TYPE_HLS)
        .build();
player.setMediaItem(MediaItem.fromUri(ssaiUri));

最后,在不再使用广告加载器时释放它。

Kotlin

adsLoader.release()

Java

adsLoader.release();

UI 注意事项

与客户端广告插入相同的 UI 注意事项 也适用于服务器端广告插入。

伴侣广告

一些广告标签包含其他伴侣广告,这些广告可以在应用程序 UI 中的“插槽”中显示。这些插槽可以通过 ImaServerSideAdInsertionMediaSource.AdsLoader.Builder.setCompanionAdSlots(slots) 传递。有关更多信息,请参阅 添加伴侣广告

使用第三方广告 SDK

如果您需要使用第三方广告 SDK 加载广告,则值得检查它是否已提供 ExoPlayer 集成。如果没有,建议提供一个自定义的 MediaSource,该源接受使用 ssai:// 方案的 URI,类似于 ImaServerSideAdInsertionMediaSource

创建广告结构的实际逻辑可以委托给通用目的的 ServerSideAdInsertionMediaSource,它包装一个流 MediaSource 并允许用户设置和更新表示广告元数据的 AdPlaybackState

通常,服务器端插入的广告流包含定时事件,以通知播放器有关广告元数据的信息。有关 ExoPlayer 支持哪些定时元数据格式的信息,请参阅 支持的格式。自定义广告 SDK MediaSource 实现可以使用 Player.Listener.onMetadata 监听来自播放器的定时元数据事件。