Skip to content

Commit

Permalink
Pass ad tags via AdsMediaSource
Browse files Browse the repository at this point in the history
This is in preparation for supporting playlists of ads media sources using
ImaAdsLoader.

Existing ways of passing ad tags should still function but are deprecated (and
won't be supported with playlists).

Issue: #3750
PiperOrigin-RevId: 335618364
  • Loading branch information
andrewlewis authored and kim-vde committed Oct 6, 2020
1 parent 6ed371a commit 39277eb
Show file tree
Hide file tree
Showing 13 changed files with 289 additions and 45 deletions.
5 changes: 5 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@
([#7956](https://github.com/google/ExoPlayer/issues/7956)).
* Allow apps to specify a `VideoAdPlayerCallback`
([#7944](https://github.com/google/ExoPlayer/issues/7944)).
* Accept ad tags via the `AdsMediaSource` constructor and deprecate
passing them via the `ImaAdsLoader` constructor/builders. Passing the
ad tag via media item playback properties continues to be supported.
This is in preparation for supporting ads in playlists
([#3750](https://github.com/google/ExoPlayer/issues/3750)).

### 2.12.0 (2020-09-11) ###

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ private AdsLoader getAdsLoader(Uri adTagUri) {
}
// The ads loader is reused for multiple playbacks, so that ad playback can resume.
if (adsLoader == null) {
adsLoader = new ImaAdsLoader(/* context= */ PlayerActivity.this, adTagUri);
adsLoader = new ImaAdsLoader.Builder(/* context= */ this).build();
}
adsLoader.setPlayer(player);
return adsLoader;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.max;

import android.content.Context;
Expand Down Expand Up @@ -92,6 +91,15 @@
* #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling
* {@link #release()}.
*
* <p>See https://developers.google.com/interactive-media-ads/docs/sdks/android/compatibility for
* information on compatible ad tag formats. 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
* com.google.android.exoplayer2.source.ads.AdsMediaSource} (if using media sources directly). For
* the latter case, please note that this implementation delegates loading of the data spec to the
* IMA SDK, so range and headers specifications will be ignored in ad tag URIs. Literal ads
* responses can be encoded as data scheme data specs, for example, by constructing the data spec
* using a URI generated via {@link Util#getDataUriForString(String, String)}.
*
* <p>The IMA SDK can report obstructions to the ad view for accurate viewability measurement. This
* means that any overlay views that obstruct the ad overlay but are essential for playback need to
* be registered via the {@link AdViewProvider} passed to the {@link
Expand Down Expand Up @@ -331,7 +339,12 @@ public Builder setPlayAdBeforeStartPosition(boolean playAdBeforeStartPosition) {
* 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
* com.google.android.exoplayer2.source.ads.AdsMediaSource} (if using media sources
* directly).
*/
@Deprecated
public ImaAdsLoader buildForAdTag(Uri adTagUri) {
return new ImaAdsLoader(
/* builder= */ this, /* adTagUri= */ adTagUri, /* adsResponse= */ null);
Expand All @@ -343,10 +356,21 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) {
* @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
* com.google.android.exoplayer2.source.ads.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(/* builder= */ this, /* adTagUri= */ null, adsResponse);
}

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

private static final boolean DEBUG = false;
Expand Down Expand Up @@ -400,6 +424,8 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) {
*/
private static final int IMA_AD_STATE_PAUSED = 2;

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

private final Context context;
@Nullable private final Uri adTagUri;
@Nullable private final String adsResponse;
Expand Down Expand Up @@ -430,6 +456,7 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) {
private List<String> supportedMimeTypes;
@Nullable private EventListener eventListener;
@Nullable private Player player;
private DataSpec adTagDataSpec;
private VideoProgressUpdate lastContentProgress;
private VideoProgressUpdate lastAdProgress;
private int lastVolumePercent;
Expand Down Expand Up @@ -505,14 +532,18 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) {
* @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 com.google.android.exoplayer2.source.ads.AdsMediaSource} (if using
* media sources directly).
*/
@Deprecated
public ImaAdsLoader(Context context, Uri adTagUri) {
this(new Builder(context), adTagUri, /* adsResponse= */ null);
}

@SuppressWarnings({"nullness:argument.type.incompatible", "methodref.receiver.bound.invalid"})
private ImaAdsLoader(Builder builder, @Nullable Uri adTagUri, @Nullable String adsResponse) {
checkArgument(adTagUri != null || adsResponse != null);
this.context = builder.context.getApplicationContext();
this.adTagUri = adTagUri;
this.adsResponse = adsResponse;
Expand Down Expand Up @@ -547,6 +578,7 @@ private ImaAdsLoader(Builder builder, @Nullable Uri adTagUri, @Nullable String a
updateAdProgressRunnable = this::updateAdProgress;
adInfoByAdMediaInfo = HashBiMap.create();
supportedMimeTypes = Collections.emptyList();
adTagDataSpec = EMPTY_AD_TAG_DATA_SPEC;
lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
Expand Down Expand Up @@ -592,12 +624,62 @@ public AdDisplayContainer getAdDisplayContainer() {
*
* @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.
*
* <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 adTagDataSpec The data specification of the ad tag to load. See class javadoc for
* information about compatible ad tag formats.
* @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI, or {@code
* null} if playing audio-only ads.
*/
public void requestAds(DataSpec adTagDataSpec, @Nullable ViewGroup adViewGroup) {
if (hasAdPlaybackState || adsManager != null || pendingAdRequestContext != null) {
// Ads have already been requested.
return;
}

if (EMPTY_AD_TAG_DATA_SPEC.equals(adTagDataSpec)) {
// Handle deprecated ways of specifying the ad tag.
if (adTagUri != null) {
adTagDataSpec = new DataSpec(adTagUri);
} else if (adsResponse != null) {
adTagDataSpec = new DataSpec(Util.getDataUriForString(adsResponse, "text/xml"));
} else {
throw new IllegalStateException();
}
}

AdsRequest request;
try {
request = ImaUtil.getAdsRequestForAdTagDataSpec(imaFactory, adTagDataSpec);
} catch (IOException e) {
hasAdPlaybackState = true;
updateAdPlaybackState();
pendingAdLoadError = AdLoadException.createForAllAds(e);
maybeNotifyPendingAdLoadError();
return;
}
this.adTagDataSpec = adTagDataSpec;
pendingAdRequestContext = new Object();
request.setUserRequestContext(pendingAdRequestContext);
if (vastLoadTimeoutMs != TIMEOUT_UNSET) {
request.setVastLoadTimeout(vastLoadTimeoutMs);
}
request.setContentProgressProvider(componentListener);

if (adViewGroup != null) {
adDisplayContainer =
imaFactory.createAdDisplayContainer(adViewGroup, /* player= */ componentListener);
Expand All @@ -608,24 +690,13 @@ public void requestAds(@Nullable ViewGroup adViewGroup) {
if (companionAdSlots != null) {
adDisplayContainer.setCompanionSlots(companionAdSlots);
}

adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
adsLoader.addAdErrorListener(componentListener);
if (adErrorListener != null) {
adsLoader.addAdErrorListener(adErrorListener);
}
adsLoader.addAdsLoadedListener(componentListener);
AdsRequest request = imaFactory.createAdsRequest();
if (adTagUri != null) {
request.setAdTagUrl(adTagUri.toString());
} else {
request.setAdsResponse(castNonNull(adsResponse));
}
if (vastLoadTimeoutMs != TIMEOUT_UNSET) {
request.setVastLoadTimeout(vastLoadTimeoutMs);
}
request.setContentProgressProvider(componentListener);
pendingAdRequestContext = new Object();
request.setUserRequestContext(pendingAdRequestContext);
adsLoader.requestAds(request);
}

Expand Down Expand Up @@ -674,6 +745,11 @@ public void setSupportedContentTypes(@C.ContentType int... contentTypes) {
this.supportedMimeTypes = Collections.unmodifiableList(supportedMimeTypes);
}

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

@Override
public void start(EventListener eventListener, AdViewProvider adViewProvider) {
checkState(
Expand All @@ -700,7 +776,7 @@ public void start(EventListener eventListener, AdViewProvider adViewProvider) {
updateAdPlaybackState();
} else {
// Ads haven't loaded yet, so request them.
requestAds(adViewProvider.getAdViewGroup());
requestAds(adTagDataSpec, adViewProvider.getAdViewGroup());
}
if (adDisplayContainer != null) {
for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) {
Expand Down Expand Up @@ -1431,7 +1507,7 @@ private void updateAdPlaybackState() {

private void maybeNotifyPendingAdLoadError() {
if (pendingAdLoadError != null && eventListener != null) {
eventListener.onAdLoadError(pendingAdLoadError, getAdsDataSpec(adTagUri));
eventListener.onAdLoadError(pendingAdLoadError, adTagDataSpec);
pendingAdLoadError = null;
}
}
Expand All @@ -1446,8 +1522,7 @@ private void maybeNotifyInternalError(String name, Exception cause) {
updateAdPlaybackState();
if (eventListener != null) {
eventListener.onAdLoadError(
AdLoadException.createForUnexpected(new RuntimeException(message, cause)),
getAdsDataSpec(adTagUri));
AdLoadException.createForUnexpected(new RuntimeException(message, cause)), adTagDataSpec);
}
}

Expand Down Expand Up @@ -1500,10 +1575,6 @@ private String getAdMediaInfoString(AdMediaInfo adMediaInfo) {
return "AdMediaInfo[" + adMediaInfo.getUrl() + (adInfo != null ? ", " + adInfo : "") + "]";
}

private static DataSpec getAdsDataSpec(@Nullable Uri adTagUri) {
return new DataSpec(adTagUri != null ? adTagUri : Uri.EMPTY);
}

private static long getContentPeriodPositionMs(
Player player, Timeline timeline, Timeline.Period period) {
long contentWindowPositionMs = player.getContentPosition();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.source.ads.AdsLoader.OverlayInfo;
import com.google.android.exoplayer2.upstream.DataSchemeDataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

Expand Down Expand Up @@ -116,6 +120,24 @@ public static AdPlaybackState getInitialAdPlaybackStateForCuePoints(List<Float>
return new AdPlaybackState(adGroupTimesUs);
}

/** Returns an {@link AdsRequest} based on the specified ad tag {@link DataSpec}. */
public static AdsRequest getAdsRequestForAdTagDataSpec(
ImaFactory imaFactory, DataSpec adTagDataSpec) throws IOException {
AdsRequest request = imaFactory.createAdsRequest();
if (DataSchemeDataSource.SCHEME_DATA.equals(adTagDataSpec.uri.getScheme())) {
DataSchemeDataSource dataSchemeDataSource = new DataSchemeDataSource();
try {
dataSchemeDataSource.open(adTagDataSpec);
request.setAdsResponse(Util.fromUtf8Bytes(Util.readToEnd(dataSchemeDataSource)));
} finally {
dataSchemeDataSource.close();
}
} else {
request.setAdTagUrl(adTagDataSpec.uri.toString());
}
return request;
}

/** Returns whether the ad error indicates that an entire ad group failed to load. */
public static boolean isAdGroupLoadError(AdError adError) {
// TODO: Find out what other errors need to be handled (if any), and whether each one relates to
Expand Down
Loading

0 comments on commit 39277eb

Please sign in to comment.