插入广告

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

客户端广告插播

在客户端广告插播中,播放器会在播放内容和广告之间切换时,在从不同网址加载媒体之间切换。广告的相关信息与媒体是分开加载的,例如通过 XML VASTVMAP 广告代码进行加载。这可以包括相对于内容开头的广告插入位置、实际的广告媒体 URI 和元数据(例如给定广告是否可跳过)。

使用 ExoPlayer 的 AdsMediaSource 在客户端广告插播时,播放器会获得要播放的广告的相关信息。这样做有几个好处:

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

在此设置中,播放器负责在广告与内容之间切换,也就是说,应用无需控制为广告和内容控制多个独立的后台/前台播放器。

在准备用于客户端广告插播的内容视频和广告代码时,广告应位于内容视频的同步样本(关键帧)处,以便播放器可以无缝地继续播放内容。

对声明式广告的支持

在构建 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 组合请求广告代码并存储一次广告播放状态。这意味着,即使广告标记 URI 匹配,用户将看到具有广告具有独特媒体 ID 或内容 URI 的每个媒体项的广告。如果媒体项重复出现,则用户只会看到相应的广告一次(广告播放状态会存储广告是否已播放,以便这些广告在首次出现后会被跳过)。

您可以自定义此行为,方法是传递不透明广告标识符,根据对象相等性,指定媒体项的广告播放状态与该标识符相关联。在以下示例中,通过传递广告代码 URI 作为广告标识符,广告播放状态仅与广告代码 URI(而不是媒体 ID 和广告代码 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 广告代码。

界面注意事项

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

IMA SDK 可能会报告广告是否会被呈现在播放器顶部的应用提供的视图遮挡。如果需要叠加对控制播放至关重要的视图,应用必须在 IMA SDK 中注册此类视图,这样在计算可见度时便可忽略这些视图。将 PlayerView 用作 AdViewProvider 时,它会自动注册其控件叠加层。使用自定义播放器界面的应用必须通过从 AdViewProvider.getAdOverlayInfos 返回叠加层视图来注册这些视图。

如需详细了解叠加层视图,请参阅 IMA SDK 中的 Open Measurement

随播广告

某些广告代码包含可在应用界面的“广告位”中展示的额外随播广告。这些槽可通过 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 文档。

使用服务器端广告插播时,客户端可能需要动态解析媒体网址以获取拼接的视频流,可能需要在界面中展示广告叠加层,或者可能需要向广告 SDK 或广告服务器报告事件。

ExoPlayer 的 DefaultMediaSourceFactory 可以将所有这些任务委托给使用 ssai:// 架构的 URI 服务器端广告插播 MediaSource

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 构建一个网址,以加载您的 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();

界面注意事项

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

随播广告

某些广告代码包含可在应用界面的“广告位”中展示的额外随播广告。这些槽可通过 ImaServerSideAdInsertionMediaSource.AdsLoader.Builder.setCompanionAdSlots(slots) 传递。如需了解详情,请参阅添加随播广告

使用第三方广告 SDK

如果您需要使用第三方广告 SDK 加载广告,最好检查它是否已提供 ExoPlayer 集成。如果不是,建议提供一个自定义 MediaSource,它接受 ssai:// 架构类似于 ImaServerSideAdInsertionMediaSource 的 URI。

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

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