ExoPlayer 可用于客户端广告插播和服务器端广告插播。
客户端广告插播
在客户端广告插播中,播放器在播放内容和广告之间切换时,会从不同的网址加载媒体。广告相关信息与媒体分开加载,例如通过 XML VAST 或 VMAP 广告代码加载。这可以包括相对于内容开始时间的广告提示位置、实际广告媒体 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.Provider 和 AdViewProvider 的 DefaultMediaSourceFactory:
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 作为广告标识符传递,将广告播放状态仅与广告代码 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 附带 HlsInterstitialsAdsLoader,该组件支持在客户端自动插入 HLS 播放列表中定义的广告。请参阅 HLS 页面上有关 HlsInterstitialsAdsLoader 的部分。
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。
随播广告
某些广告代码包含可在应用界面中的“插槽”中展示的其他随播广告。这些 slot 可以通过 ImaAdsLoader.Builder.setCompanionAdSlots(slots) 传递。如需了解详情,请参阅添加随播广告。
独立广告
IMA SDK 旨在将广告插入媒体内容中,而不是单独播放广告。因此,IMA 库不支持播放独立广告。对于此使用情形,我们建议您改用 Google 移动广告 SDK。
使用第三方广告 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(MediaItem.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(MediaItem.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 MediaItem.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 MediaItem.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 的服务器端插入的广告流集成到应用中。它封装了 IMA DAI SDK for Android 的功能,并将提供的广告元数据完全集成到播放器中。例如,这使您能够使用 Player.isPlayingAd() 等方法,监听内容广告过渡,并让播放器处理广告播放逻辑,例如跳过已播放的广告。
如需使用此类,您需要设置 ImaServerSideAdInsertionMediaSource.AdsLoader 和 ImaServerSideAdInsertionMediaSource.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();
界面注意事项
与客户端广告插播相同的 界面注意事项也适用于服务器端广告插播。
随播广告
某些广告代码包含可在应用界面中的“插槽”中展示的其他随播广告。这些 slot 可以通过 ImaServerSideAdInsertionMediaSource.AdsLoader.Builder.setCompanionAdSlots(slots) 传递。
如需了解详情,请参阅添加随播广告。
使用第三方广告 SDK
如果您需要使用第三方广告 SDK 加载广告,不妨检查一下该 SDK 是否已提供 ExoPlayer 集成。如果不是,建议您提供接受具有 ssai:// 方案(类似于 ImaServerSideAdInsertionMediaSource)的 URI 的自定义 MediaSource。
创建广告结构的实际逻辑可以委托给通用 ServerSideAdInsertionMediaSource,该对象封装了流 MediaSource,并允许用户设置和更新表示广告元数据的 AdPlaybackState。
通常,服务器端插播的广告流包含定时事件,用于向播放器通知广告元数据。如需了解 ExoPlayer 支持哪些定时元数据格式,请参阅支持的格式。自定义广告 SDK MediaSource 实现可以使用 Player.Listener.onMetadata 监听播放器的定时元数据事件。