330 lines
14 KiB
Markdown
330 lines
14 KiB
Markdown
|
---
|
|||
|
title: Ad insertion
|
|||
|
---
|
|||
|
|
|||
|
ExoPlayer can be used for both client-side and server-side ad insertion.
|
|||
|
|
|||
|
## Client-side ad insertion ##
|
|||
|
|
|||
|
In client-side ad insertion, the player switches between loading media from
|
|||
|
different URLs as it transitions between playing content and ads. Information
|
|||
|
about ads is loaded separately from the media, such as from an XML [VAST][] or
|
|||
|
[VMAP][] ad tag. This can include ad cue positions relative to the start of the
|
|||
|
content, the actual ad media URIs and metadata such as whether a given ad is
|
|||
|
skippable.
|
|||
|
|
|||
|
When using ExoPlayer's `AdsMediaSource` for client-side ad insertion, the player
|
|||
|
has information about the ads to be played. This has several benefits:
|
|||
|
|
|||
|
* The player can expose metadata and functionality relating to ads via its API.
|
|||
|
* [ExoPlayer UI components][] can show markers for ad positions automatically,
|
|||
|
and change their behavior depending on whether ad is playing.
|
|||
|
* Internally, the player can keep a consistent buffer across transitions between
|
|||
|
ads and content.
|
|||
|
|
|||
|
In this setup, the player takes care of switching between ads and content, which
|
|||
|
means that apps don't need to take care of controlling multiple separate
|
|||
|
background/foreground players for ads and content.
|
|||
|
|
|||
|
When preparing content videos and ad tags for use with client-side ad insertion,
|
|||
|
ads should ideally be positioned at synchronization samples (keyframes) in the
|
|||
|
content video so that the player can resume content playback seamlessly.
|
|||
|
|
|||
|
### Declarative ad support ###
|
|||
|
|
|||
|
An ad tag URI can be specified when building a `MediaItem`:
|
|||
|
|
|||
|
~~~
|
|||
|
MediaItem mediaItem =
|
|||
|
new MediaItem.Builder()
|
|||
|
.setUri(videoUri)
|
|||
|
.setAdsConfiguration(
|
|||
|
new MediaItem.AdsConfiguration.Builder(adTagUri).build())
|
|||
|
.build();
|
|||
|
~~~
|
|||
|
{: .language-java}
|
|||
|
|
|||
|
To enable player support for media items that specify ad tags, it's necessary to
|
|||
|
build and inject a `DefaultMediaSourceFactory` configured with an
|
|||
|
`AdsLoader.Provider` and an `AdViewProvider` when creating the player:
|
|||
|
|
|||
|
~~~
|
|||
|
MediaSource.Factory mediaSourceFactory =
|
|||
|
new DefaultMediaSourceFactory(context)
|
|||
|
.setAdsLoaderProvider(adsLoaderProvider)
|
|||
|
.setAdViewProvider(playerView);
|
|||
|
ExoPlayer player = new ExoPlayer.Builder(context)
|
|||
|
.setMediaSourceFactory(mediaSourceFactory)
|
|||
|
.build();
|
|||
|
~~~
|
|||
|
{: .language-java}
|
|||
|
|
|||
|
Internally, `DefaultMediaSourceFactory` will wrap the content media source in an
|
|||
|
`AdsMediaSource`. The `AdsMediaSource` will obtain an `AdsLoader` from the
|
|||
|
`AdsLoader.Provider` and use it to insert ads as defined by the media item's ad
|
|||
|
tag.
|
|||
|
|
|||
|
ExoPlayer's `StyledPlayerView` implements `AdViewProvider`. The IMA extension
|
|||
|
provides an easy to use `AdsLoader`, as described below.
|
|||
|
|
|||
|
### Playlists with ads ###
|
|||
|
|
|||
|
When playing a [playlist][] with multiple media items, the default behavior is
|
|||
|
to request the ad tag and store ad playback state once for each media ID,
|
|||
|
content URI and ad tag URI combination. This means that users will see ads for
|
|||
|
every media item with ads that has a distinct media ID or content URI, even if
|
|||
|
the ad tag URIs match. If a media item is repeated, the user will see the
|
|||
|
corresponding ads only once (the ad playback state stores whether ads have been
|
|||
|
played, so they are skipped after their first occurrence).
|
|||
|
|
|||
|
It's possible to customize this behavior by passing an opaque ads identifier
|
|||
|
with which ad playback state for a given media item is linked, based on object
|
|||
|
equality. Here is an example where ad playback state is linked to the ad tag
|
|||
|
URI only, rather than the combination of the media ID and ad tag URI, by
|
|||
|
passing the ad tag URI as the ads identifier. The effect is that ads will load
|
|||
|
only once and the user will not see ads on the second item when playing the
|
|||
|
playlist from start to finish.
|
|||
|
|
|||
|
~~~
|
|||
|
// 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);
|
|||
|
~~~
|
|||
|
{: .language-java}
|
|||
|
|
|||
|
### IMA extension ###
|
|||
|
|
|||
|
The [ExoPlayer IMA extension][] provides `ImaAdsLoader`, making it easy to
|
|||
|
integrate client-side ad insertion into your app. It wraps the functionality of
|
|||
|
the [client-side IMA SDK][] to support insertion of VAST/VMAP ads. For
|
|||
|
instructions on how to use the extension, including how to handle backgrounding
|
|||
|
and resuming playback, please see the [README][].
|
|||
|
|
|||
|
The [demo application][] uses the IMA extension, and includes several sample
|
|||
|
VAST/VMAP ad tags in the sample list.
|
|||
|
|
|||
|
#### UI considerations ####
|
|||
|
|
|||
|
`StyledPlayerView` hides its transport controls during playback of ads by
|
|||
|
default, but apps can toggle this behavior by calling
|
|||
|
`setControllerHideDuringAds`. The IMA SDK will show additional views on top of
|
|||
|
the player while an ad is playing (e.g., a 'more info' link and a skip button,
|
|||
|
if applicable).
|
|||
|
|
|||
|
Since advertisers expect a consistent experience across apps, the IMA SDK does
|
|||
|
not allow customization of the views that it shows while an ad is playing. It is
|
|||
|
therefore not possible to remove or reposition the skip button, change the
|
|||
|
fonts, or make other customizations to the visual appearance of these views.
|
|||
|
{:.info}
|
|||
|
|
|||
|
The IMA SDK may report whether ads are obscured by application provided views
|
|||
|
rendered on top of the player. Apps that need to overlay views that are
|
|||
|
essential for controlling playback must register them with the IMA SDK so that
|
|||
|
they can be omitted from viewability calculations. When using `StyledPlayerView`
|
|||
|
as the `AdViewProvider`, it will automatically register its control overlays.
|
|||
|
Apps that use a custom player UI must register overlay views by returning them
|
|||
|
from `AdViewProvider.getAdOverlayInfos`.
|
|||
|
|
|||
|
For more information about overlay views, see
|
|||
|
[Open Measurement in the IMA SDK][].
|
|||
|
|
|||
|
#### Companion ads ####
|
|||
|
|
|||
|
Some ad tags contain additional companion ads that can be shown in 'slots' in an
|
|||
|
app UI. These slots can be passed via
|
|||
|
`ImaAdsLoader.Builder.setCompanionAdSlots(slots)`. For more information see
|
|||
|
[Adding Companion Ads][].
|
|||
|
|
|||
|
#### Standalone ads ####
|
|||
|
|
|||
|
The IMA SDK is designed for inserting ads into media content, not for playing
|
|||
|
standalone ads by themselves. Hence playback of standalone ads is not supported
|
|||
|
by the IMA extension. We recommend using the [Google Mobile Ads SDK][] instead
|
|||
|
for this use case.
|
|||
|
|
|||
|
### Using a third-party ads SDK ###
|
|||
|
|
|||
|
If you need to load ads via a third-party ads SDK, it's worth checking whether
|
|||
|
it already provides an ExoPlayer integration. If not, implementing a custom
|
|||
|
`AdsLoader` that wraps the third-party ads SDK is the recommended approach,
|
|||
|
since it provides the benefits of `AdsMediaSource` described above.
|
|||
|
`ImaAdsLoader` acts as an example implementation.
|
|||
|
|
|||
|
Alternatively, you can use ExoPlayer's [playlist support][] to build a sequence
|
|||
|
of ads and content clips:
|
|||
|
|
|||
|
~~~
|
|||
|
// 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);
|
|||
|
~~~
|
|||
|
{: .language-java}
|
|||
|
|
|||
|
## Server-side ad insertion ##
|
|||
|
|
|||
|
In server-side ad insertion (also called dynamic ad insertion, or DAI), the
|
|||
|
media stream contains both ads and content. A DASH manifest may point to both
|
|||
|
content and ad segments, possibly in separate periods. For HLS, see the Apple
|
|||
|
documentation on [incorporating ads into a playlist][].
|
|||
|
|
|||
|
When using server-side ad insertion, the client may need to resolve the media
|
|||
|
URL dynamically to get the stitched stream, it may need to display ads overlays
|
|||
|
in the UI or it may need to report events to an ads SDK or ad server.
|
|||
|
|
|||
|
ExoPlayer's `DefaultMediaSourceFactory` can delegate all these tasks to a
|
|||
|
server-side ad insertion `MediaSource` for URIs using the `ssai://` scheme:
|
|||
|
|
|||
|
```
|
|||
|
Player player =
|
|||
|
new ExoPlayer.Builder(context)
|
|||
|
.setMediaSourceFactory(
|
|||
|
new DefaultMediaSourceFactory(dataSourceFactory)
|
|||
|
.setServerSideAdInsertionMediaSourceFactory(ssaiFactory))
|
|||
|
.build();
|
|||
|
```
|
|||
|
|
|||
|
### IMA extension ###
|
|||
|
|
|||
|
The [ExoPlayer IMA extension][] provides `ImaServerSideAdInsertionMediaSource`,
|
|||
|
making it easy to integrate with IMA's server-side inserted ad streams in your
|
|||
|
app. It wraps the functionality of the [IMA DAI SDK for Android][] and fully
|
|||
|
integrates the provided ad metadata into the player. For example, this allows
|
|||
|
you to use methods like `Player.isPlayingAd()`, listen to content-ad transitions
|
|||
|
and let the player handle ad playback logic like skipping already played ads.
|
|||
|
|
|||
|
In order to use this class, you need to set up the
|
|||
|
`ImaServerSideAdInsertionMediaSource.AdsLoader` and the
|
|||
|
`ImaServerSideAdInsertionMediaSource.Factory` and connect them to the player:
|
|||
|
|
|||
|
```
|
|||
|
// MediaSource.Factory to load the actual media stream.
|
|||
|
DefaultMediaSourceFactory defaultMediaSourceFactory =
|
|||
|
new DefaultMediaSourceFactory(dataSourceFactory);
|
|||
|
// 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);
|
|||
|
```
|
|||
|
|
|||
|
Load your IMA asset key, or content source id and video id, by building an URL
|
|||
|
with `ImaServerSideAdInsertionUriBuilder`:
|
|||
|
|
|||
|
```
|
|||
|
Uri ssaiUri =
|
|||
|
new ImaServerSideAdInsertionUriBuilder().setAssetKey(assetKey).build();
|
|||
|
player.setMediaItem(MediaItem.fromUri(ssaiUri));
|
|||
|
```
|
|||
|
|
|||
|
Finally, release your ads loader once it's no longer used:
|
|||
|
```
|
|||
|
adsLoader.release();
|
|||
|
```
|
|||
|
|
|||
|
Currently only a single IMA server-side ad insertion stream is supported in the
|
|||
|
same playlist. You can combine the stream with other media but not with another
|
|||
|
IMA server-side ad insertion stream.
|
|||
|
{:.info}
|
|||
|
|
|||
|
#### UI considerations ####
|
|||
|
|
|||
|
The same [UI considerations as for client-side ad insertion][] apply to
|
|||
|
server-side ad insertion too.
|
|||
|
|
|||
|
#### Companion ads ####
|
|||
|
|
|||
|
Some ad tags contain additional companion ads that can be shown in 'slots' in an
|
|||
|
app UI. These slots can be passed via
|
|||
|
`ImaServerSideAdInsertionMediaSource.AdsLoader.Builder.setCompanionAdSlots(slots)`.
|
|||
|
For more information see [Adding Companion Ads][].
|
|||
|
|
|||
|
### Using a third-party ads SDK ###
|
|||
|
|
|||
|
If you need to load ads via a third-party ads SDK, it’s worth checking whether
|
|||
|
it already provides an ExoPlayer integration. If not, it's recommended to
|
|||
|
provide a custom `MediaSource` that accepts URIs with the `ssai://` scheme
|
|||
|
similar to `ImaServerSideAdInsertionMediaSource`.
|
|||
|
|
|||
|
The actual logic of creating the ad structure can be delegated to the general
|
|||
|
purpose `ServerSideAdInsertionMediaSource`, which wraps a stream `MediaSource`
|
|||
|
and allows the user to set and update the `AdPlaybackState` representing the ad
|
|||
|
metadata.
|
|||
|
|
|||
|
Often, server-side inserted ad streams contain timed events to notify the player
|
|||
|
about ad metadata. Please see [supported formats][] for information on what
|
|||
|
timed metadata formats are supported by ExoPlayer. Custom ads SDK `MediaSource`s
|
|||
|
can listen for timed metadata events from the player, e.g., via
|
|||
|
`ExoPlayer.addMetadataOutput`.
|
|||
|
|
|||
|
[VAST]: https://www.iab.com/wp-content/uploads/2015/06/VASTv3_0.pdf
|
|||
|
[VMAP]: https://www.iab.com/guidelines/digital-video-multiple-ad-playlist-vmap-1-0-1/
|
|||
|
[ExoPlayer UI components]: {{ site.baseurl }}/ui-components.html
|
|||
|
[ExoPlayer IMA extension]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/ima
|
|||
|
[client-side IMA SDK]: https://developers.google.com/interactive-media-ads/docs/sdks/android
|
|||
|
[README]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/ima
|
|||
|
[demo application]: {{ site.baseurl }}/demo-application.html
|
|||
|
[Open Measurement in the IMA SDK]: https://developers.google.com/interactive-media-ads/docs/sdks/android/omsdk
|
|||
|
[Adding Companion Ads]: https://developers.google.com/interactive-media-ads/docs/sdks/android/companions
|
|||
|
[playlist]: {{ site.baseurl }}/playlists.html
|
|||
|
[playlist support]: {{ site.baseurl }}/playlists.html
|
|||
|
[incorporating ads into a playlist]: https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/incorporating_ads_into_a_playlist
|
|||
|
[supported formats]: {{ site.baseurl }}/supported-formats.html
|
|||
|
[Google Mobile Ads SDK]: https://developers.google.com/admob/android/quick-start
|
|||
|
[IMA DAI SDK for Android]: https://developers.google.com/interactive-media-ads/docs/sdks/android/dai
|
|||
|
[UI considerations as for client-side ad insertion]: #ui-considerations
|