Skip to content

Commit

Permalink
Signal an ads identifier to the AdsLoader
Browse files Browse the repository at this point in the history
In a later change, the AdPlaybackState will include the playing adsId (set by
the AdsLoader) and the ads loader will use this to determine what ad
information is associated with the playing/next periods, to allow loading ads
in playlists.

Apps can continue to pass just a URI for an ad tag with their MediaItem, in
which case the associated playlist will request that ad tag just and the same
state will be used for all occurrences of the ad tag.

This change has breaking changes to the AdsLoader interface and removes
deprecated ways of passing the ad tag, as it's very likely to go into a major
release anyway and not needing to handle the deprecated cases simplifies
ImaAdsLoader.

Issue: #3750
PiperOrigin-RevId: 340438580
  • Loading branch information
andrewlewis committed Nov 6, 2020
1 parent f937e40 commit 5fd1601
Show file tree
Hide file tree
Showing 15 changed files with 334 additions and 330 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ private static void addPlaybackPropertiesToIntent(
.putExtra(MIME_TYPE_EXTRA + extrasKeySuffix, playbackProperties.mimeType)
.putExtra(
AD_TAG_URI_EXTRA + extrasKeySuffix,
playbackProperties.adTagUri != null ? playbackProperties.adTagUri.toString() : null);
playbackProperties.adsConfiguration != null
? playbackProperties.adsConfiguration.adTagUri.toString()
: null);
if (playbackProperties.drmConfiguration != null) {
addDrmConfigurationToIntent(playbackProperties.drmConfiguration, intent, extrasKeySuffix);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Pair;
import android.view.KeyEvent;
Expand Down Expand Up @@ -102,12 +101,11 @@ public class PlayerActivity extends AppCompatActivity
private int startWindow;
private long startPosition;

// Fields used only for ad playback.
// For ad playback only.

private AdsLoader adsLoader;
private Uri loadedAdTagUri;

// Activity lifecycle
// Activity lifecycle.

@Override
public void onCreate(Bundle savedInstanceState) {
Expand Down Expand Up @@ -355,24 +353,20 @@ private List<MediaItem> createMediaItems(Intent intent) {
return Collections.emptyList();
}
}
hasAds |= mediaItem.playbackProperties.adTagUri != null;
hasAds |= mediaItem.playbackProperties.adsConfiguration != null;
}
if (!hasAds) {
releaseAdsLoader();
}
return mediaItems;
}

private AdsLoader getAdsLoader(Uri adTagUri) {
private AdsLoader getAdsLoader(MediaItem.AdsConfiguration adsConfiguration) {
if (mediaItems.size() > 1) {
showToast(R.string.unsupported_ads_in_playlist);
releaseAdsLoader();
return null;
}
if (!adTagUri.equals(loadedAdTagUri)) {
releaseAdsLoader();
loadedAdTagUri = adTagUri;
}
// The ads loader is reused for multiple playbacks, so that ad playback can resume.
if (adsLoader == null) {
adsLoader = new ImaAdsLoader.Builder(/* context= */ this).build();
Expand Down Expand Up @@ -401,7 +395,6 @@ private void releaseAdsLoader() {
if (adsLoader != null) {
adsLoader.release();
adsLoader = null;
loadedAdTagUri = null;
playerView.getOverlayFrameLayout().removeAllViews();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ private int getDownloadUnsupportedStringId(PlaylistHolder playlistHolder) {
}
MediaItem.PlaybackProperties playbackProperties =
checkNotNull(playlistHolder.mediaItems.get(0).playbackProperties);
if (playbackProperties.adTagUri != null) {
if (playbackProperties.adsConfiguration != null) {
return R.string.download_ads_unsupported;
}
String scheme = playbackProperties.uri.getScheme();
Expand Down
24 changes: 13 additions & 11 deletions extensions/ima/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,19 @@ of the developer guide. The `AdsLoaderProvider` passed to the player's
extension only supports players which are accessed on the application's main
thread.

Resuming the player after entering the background requires some special handling
when playing ads. The player and its media source are released on entering the
background, and are recreated when returning to the foreground. When playing ads
it is necessary to persist ad playback state while in the background by keeping
a reference to the `ImaAdsLoader`. When re-entering the foreground, pass the
same instance back when `AdsLoaderProvider.getAdsLoader(Uri adTagUri)` is called
to restore the state. It is also important to persist the player position when
entering the background by storing the value of `player.getContentPosition()`.
On returning to the foreground, seek to that position before preparing the new
player instance. Finally, it is important to call `ImaAdsLoader.release()` when
playback has finished and will not be resumed.
Resuming the player after entering the background requires some special
handling when playing ads. The player and its media source are released on
entering the background, and are recreated when returning to the foreground.
When playing ads it is necessary to persist ad playback state while in the
background by keeping a reference to the `ImaAdsLoader`. When re-entering the
foreground, pass the same instance back when
`AdsLoaderProvider.getAdsLoader(MediaItem.AdsConfiguration adsConfiguration)`
is called to restore the state. It is also important to persist the player
position when entering the background by storing the value of
`player.getContentPosition()`. On returning to the foreground, seek to that
position before preparing the new player instance. Finally, it is important to
call `ImaAdsLoader.release()` when playback has finished and will not be
resumed.

You can try the IMA extension in the ExoPlayer demo app, which has test content
in the "IMA sample ad tags" section of the sample chooser. The demo app's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ protected MediaSource buildSource(
return new AdsMediaSource(
contentMediaSource,
adTagDataSpec,
/* adsId= */ adTagDataSpec.uri,
new DefaultMediaSourceFactory(dataSourceFactory),
Assertions.checkNotNull(imaAdsLoader),
new AdViewProvider() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import static com.google.android.exoplayer2.util.Assertions.checkState;

import android.content.Context;
import android.net.Uri;
import android.os.Looper;
import android.view.View;
import android.view.ViewGroup;
Expand Down Expand Up @@ -343,125 +342,44 @@ public Builder setDebugModeEnabled(boolean debugModeEnabled) {
return this;
}

/**
* Returns a new {@link ImaAdsLoader} for the specified ad tag.
*
* @param adTagUri The URI of a compatible ad tag to load. See
* https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* information on compatible ad tags.
* @return The new {@link ImaAdsLoader}.
* @deprecated Pass the ad tag URI when setting media item playback properties (if using the
* media item API) or as a {@link DataSpec} when constructing the {@link AdsMediaSource} (if
* using media sources directly).
*/
@Deprecated
public ImaAdsLoader buildForAdTag(Uri adTagUri) {
return new ImaAdsLoader(
context,
getConfiguration(),
imaFactory,
/* adTagUri= */ adTagUri,
/* adsResponse= */ null);
}

/**
* Returns a new {@link ImaAdsLoader} with the specified sideloaded ads response.
*
* @param adsResponse The sideloaded VAST, VMAP, or ad rules response to be used instead of
* making a request via an ad tag URL.
* @return The new {@link ImaAdsLoader}.
* @deprecated Pass the ads response as a data URI when setting media item playback properties
* (if using the media item API) or as a {@link DataSpec} when constructing the {@link
* AdsMediaSource} (if using media sources directly). {@link
* Util#getDataUriForString(String, String)} can be used to construct a data URI from
* literal string ads response (with MIME type text/xml).
*/
@Deprecated
public ImaAdsLoader buildForAdsResponse(String adsResponse) {
return new ImaAdsLoader(
context, getConfiguration(), imaFactory, /* adTagUri= */ null, adsResponse);
}

/** Returns a new {@link ImaAdsLoader}. */
public ImaAdsLoader build() {
return new ImaAdsLoader(
context, getConfiguration(), imaFactory, /* adTagUri= */ null, /* adsResponse= */ null);
}

// TODO(internal: b/169646419): Remove/hide once the deprecated constructor has been removed.
/* package */ ImaUtil.Configuration getConfiguration() {
return new ImaUtil.Configuration(
adPreloadTimeoutMs,
vastLoadTimeoutMs,
mediaLoadTimeoutMs,
focusSkipButtonWhenAvailable,
playAdBeforeStartPosition,
mediaBitrate,
adMediaMimeTypes,
adUiElements,
companionAdSlots,
adErrorListener,
adEventListener,
videoAdPlayerCallback,
imaSdkSettings,
debugModeEnabled);
context,
new ImaUtil.Configuration(
adPreloadTimeoutMs,
vastLoadTimeoutMs,
mediaLoadTimeoutMs,
focusSkipButtonWhenAvailable,
playAdBeforeStartPosition,
mediaBitrate,
adMediaMimeTypes,
adUiElements,
companionAdSlots,
adErrorListener,
adEventListener,
videoAdPlayerCallback,
imaSdkSettings,
debugModeEnabled),
imaFactory);
}
}

private static final DataSpec EMPTY_AD_TAG_DATA_SPEC = new DataSpec(Uri.EMPTY);

private final ImaUtil.Configuration configuration;
private final Context context;
private final ImaUtil.ImaFactory imaFactory;
@Nullable private final DataSpec deprecatedAdTagDataSpec;

private boolean wasSetPlayerCalled;
@Nullable private Player nextPlayer;
@Nullable private AdTagLoader adTagLoader;
private List<String> supportedMimeTypes;
private DataSpec adTagDataSpec;
@Nullable private Player player;

/**
* Creates a new IMA ads loader.
*
* <p>If you need to customize the ad request, use {@link ImaAdsLoader.Builder} instead.
*
* @param context The context.
* @param adTagUri The {@link Uri} of an ad tag compatible with the Android IMA SDK. See
* https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* more information.
* @deprecated Use {@link Builder} to create an instance. Pass the ad tag URI when setting media
* item playback properties (if using the media item API) or as a {@link DataSpec} when
* constructing the {@link AdsMediaSource} (if using media sources directly).
*/
@Deprecated
public ImaAdsLoader(Context context, Uri adTagUri) {
this(
context,
new Builder(context).getConfiguration(),
new DefaultImaFactory(),
adTagUri,
/* adsResponse= */ null);
}

private ImaAdsLoader(
Context context,
ImaUtil.Configuration configuration,
ImaUtil.ImaFactory imaFactory,
@Nullable Uri adTagUri,
@Nullable String adsResponse) {
Context context, ImaUtil.Configuration configuration, ImaUtil.ImaFactory imaFactory) {
this.context = context.getApplicationContext();
this.configuration = configuration;
this.imaFactory = imaFactory;
deprecatedAdTagDataSpec =
adTagUri != null
? new DataSpec(adTagUri)
: adsResponse != null
? new DataSpec(
Util.getDataUriForString(/* mimeType= */ "text/xml", /* data= */ adsResponse))
: null;
adTagDataSpec = EMPTY_AD_TAG_DATA_SPEC;
supportedMimeTypes = ImmutableList.of();
}

Expand Down Expand Up @@ -490,24 +408,6 @@ public AdDisplayContainer getAdDisplayContainer() {
return adTagLoader != null ? adTagLoader.getAdDisplayContainer() : null;
}

/**
* Requests ads, if they have not already been requested. Must be called on the main thread.
*
* <p>Ads will be requested automatically when the player is prepared if this method has not been
* called, so it is only necessary to call this method if you want to request ads before preparing
* the player.
*
* @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code
* null} if playing audio-only ads.
* @deprecated Use {@link #requestAds(DataSpec, ViewGroup)}, specifying the ad tag data spec to
* request, and migrate off deprecated builder methods/constructor that require an ad tag or
* ads response.
*/
@Deprecated
public void requestAds(@Nullable ViewGroup adViewGroup) {
requestAds(adTagDataSpec, adViewGroup);
}

/**
* Requests ads, if they have not already been requested. Must be called on the main thread.
*
Expand All @@ -521,16 +421,11 @@ public void requestAds(@Nullable ViewGroup adViewGroup) {
* null} if playing audio-only ads.
*/
public void requestAds(DataSpec adTagDataSpec, @Nullable ViewGroup adViewGroup) {
if (adTagLoader != null) {
return;
}

if (EMPTY_AD_TAG_DATA_SPEC.equals(adTagDataSpec)) {
adTagDataSpec = checkNotNull(deprecatedAdTagDataSpec);
if (adTagLoader == null) {
adTagLoader =
new AdTagLoader(
context, configuration, imaFactory, supportedMimeTypes, adTagDataSpec, adViewGroup);
}
adTagLoader =
new AdTagLoader(
context, configuration, imaFactory, supportedMimeTypes, adTagDataSpec, adViewGroup);
}

/**
Expand Down Expand Up @@ -579,12 +474,12 @@ public void setSupportedContentTypes(@C.ContentType int... contentTypes) {
}

@Override
public void setAdTagDataSpec(DataSpec adTagDataSpec) {
this.adTagDataSpec = adTagDataSpec;
}

@Override
public void start(EventListener eventListener, AdViewProvider adViewProvider) {
public void start(
AdsMediaSource adsMediaSource,
DataSpec adTagDataSpec,
Object adsId,
AdViewProvider adViewProvider,
EventListener eventListener) {
checkState(
wasSetPlayerCalled, "Set player using adsLoader.setPlayer before preparing the player.");
player = nextPlayer;
Expand Down
Loading

0 comments on commit 5fd1601

Please sign in to comment.