mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add Builder for ImaAdsLoader and allow early requestAds
Also fix propagation of ad errors that occur when no player is attached. Issue: #3548 Issue: #3556 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=178767997
This commit is contained in:
parent
a4ae206ebe
commit
e2bba1567e
@ -51,6 +51,10 @@
|
||||
* Fix ad loading when there is no preroll.
|
||||
* Add an option to turn off hiding controls during ad playback
|
||||
([#3532](https://github.com/google/ExoPlayer/issues/3532)).
|
||||
* Support specifying an ads response instead of an ad tag
|
||||
([#3548](https://github.com/google/ExoPlayer/issues/3548)).
|
||||
* Support overriding the ad load timeout
|
||||
([#3556](https://github.com/google/ExoPlayer/issues/3556)).
|
||||
|
||||
### 2.6.0 ###
|
||||
|
||||
|
@ -19,6 +19,7 @@ import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebView;
|
||||
@ -65,10 +66,80 @@ import java.util.Map;
|
||||
*/
|
||||
public final class ImaAdsLoader extends Player.DefaultEventListener implements AdsLoader,
|
||||
VideoAdPlayer, ContentProgressProvider, AdErrorListener, AdsLoadedListener, AdEventListener {
|
||||
|
||||
static {
|
||||
ExoPlayerLibraryInfo.registerModule("goog.exo.ima");
|
||||
}
|
||||
|
||||
/** Builder for {@link ImaAdsLoader}. */
|
||||
public static final class Builder {
|
||||
|
||||
private final Context context;
|
||||
|
||||
private @Nullable ImaSdkSettings imaSdkSettings;
|
||||
private long vastLoadTimeoutMs;
|
||||
|
||||
/**
|
||||
* Creates a new builder for {@link ImaAdsLoader}.
|
||||
*
|
||||
* @param context The context;
|
||||
*/
|
||||
public Builder(Context context) {
|
||||
this.context = Assertions.checkNotNull(context);
|
||||
vastLoadTimeoutMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the IMA SDK settings. The provided settings instance's player type and version fields
|
||||
* may be overwritten.
|
||||
*
|
||||
* <p>If this method is not called the default settings will be used.
|
||||
*
|
||||
* @param imaSdkSettings The {@link ImaSdkSettings}.
|
||||
* @return This builder, for convenience.
|
||||
*/
|
||||
public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) {
|
||||
this.imaSdkSettings = Assertions.checkNotNull(imaSdkSettings);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the VAST load timeout, in milliseconds.
|
||||
*
|
||||
* @param vastLoadTimeoutMs The VAST load timeout, in milliseconds.
|
||||
* @return This builder, for convenience.
|
||||
* @see AdsRequest#setVastLoadTimeout(float)
|
||||
*/
|
||||
public Builder setVastLoadTimeoutMs(long vastLoadTimeoutMs) {
|
||||
Assertions.checkArgument(vastLoadTimeoutMs >= 0);
|
||||
this.vastLoadTimeoutMs = vastLoadTimeoutMs;
|
||||
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}.
|
||||
*/
|
||||
public ImaAdsLoader buildForAdTag(Uri adTagUri) {
|
||||
return new ImaAdsLoader(context, adTagUri, imaSdkSettings, null, vastLoadTimeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
public ImaAdsLoader buildForAdsResponse(String adsResponse) {
|
||||
return new ImaAdsLoader(context, null, imaSdkSettings, adsResponse, vastLoadTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "ImaAdsLoader";
|
||||
|
||||
@ -94,9 +165,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
private static final String FOCUS_SKIP_BUTTON_WORKAROUND_JS = "javascript:"
|
||||
+ "try{ document.getElementsByClassName(\"videoAdUiSkipButton\")[0].focus(); } catch (e) {}";
|
||||
|
||||
/**
|
||||
* The state of ad playback based on IMA's calls to {@link #playAd()} and {@link #pauseAd()}.
|
||||
*/
|
||||
/** The state of ad playback. */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED})
|
||||
private @interface ImaAdState {}
|
||||
@ -113,7 +182,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
*/
|
||||
private static final int IMA_AD_STATE_PAUSED = 2;
|
||||
|
||||
private final Uri adTagUri;
|
||||
private final @Nullable Uri adTagUri;
|
||||
private final @Nullable String adsResponse;
|
||||
private final long vastLoadTimeoutMs;
|
||||
private final Timeline.Period period;
|
||||
private final List<VideoAdPlayerCallback> adCallbacks;
|
||||
private final ImaSdkFactory imaSdkFactory;
|
||||
@ -129,6 +200,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
private VideoProgressUpdate lastAdProgress;
|
||||
|
||||
private AdsManager adsManager;
|
||||
private AdErrorEvent pendingAdErrorEvent;
|
||||
private Timeline timeline;
|
||||
private long contentDurationMs;
|
||||
private int podIndexOffset;
|
||||
@ -144,9 +216,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
* Whether IMA has sent an ad event to pause content since the last resume content event.
|
||||
*/
|
||||
private boolean imaPausedContent;
|
||||
/**
|
||||
* The current ad playback state based on IMA's calls to {@link #playAd()} and {@link #stopAd()}.
|
||||
*/
|
||||
/** The current ad playback state. */
|
||||
private @ImaAdState int imaAdState;
|
||||
/**
|
||||
* Whether {@link com.google.ads.interactivemedia.v3.api.AdsLoader#contentComplete()} has been
|
||||
@ -189,13 +259,15 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public ImaAdsLoader(Context context, Uri adTagUri) {
|
||||
this(context, adTagUri, null);
|
||||
this(context, adTagUri, null, null, C.TIME_UNSET);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,9 +279,23 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
* more information.
|
||||
* @param imaSdkSettings {@link ImaSdkSettings} used to configure the IMA SDK, or {@code null} to
|
||||
* use the default settings. If set, the player type and version fields may be overwritten.
|
||||
* @deprecated Use {@link ImaAdsLoader.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public ImaAdsLoader(Context context, Uri adTagUri, ImaSdkSettings imaSdkSettings) {
|
||||
this(context, adTagUri, imaSdkSettings, null, C.TIME_UNSET);
|
||||
}
|
||||
|
||||
private ImaAdsLoader(
|
||||
Context context,
|
||||
@Nullable Uri adTagUri,
|
||||
@Nullable ImaSdkSettings imaSdkSettings,
|
||||
@Nullable String adsResponse,
|
||||
long vastLoadTimeoutMs) {
|
||||
Assertions.checkArgument(adTagUri != null || adsResponse != null);
|
||||
this.adTagUri = adTagUri;
|
||||
this.adsResponse = adsResponse;
|
||||
this.vastLoadTimeoutMs = vastLoadTimeoutMs;
|
||||
period = new Timeline.Period();
|
||||
adCallbacks = new ArrayList<>(1);
|
||||
imaSdkFactory = ImaSdkFactory.getInstance();
|
||||
@ -238,6 +324,37 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
return adsLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
||||
*/
|
||||
public void requestAds(ViewGroup adUiViewGroup) {
|
||||
if (adPlaybackState != null || adsManager != null || pendingAdRequestContext != null) {
|
||||
// Ads have already been requested.
|
||||
return;
|
||||
}
|
||||
adDisplayContainer.setAdContainer(adUiViewGroup);
|
||||
pendingAdRequestContext = new Object();
|
||||
AdsRequest request = imaSdkFactory.createAdsRequest();
|
||||
if (adTagUri != null) {
|
||||
request.setAdTagUrl(adTagUri.toString());
|
||||
} else /* adsResponse != null */ {
|
||||
request.setAdsResponse(adsResponse);
|
||||
}
|
||||
if (vastLoadTimeoutMs != C.TIME_UNSET) {
|
||||
request.setVastLoadTimeout(vastLoadTimeoutMs);
|
||||
}
|
||||
request.setAdDisplayContainer(adDisplayContainer);
|
||||
request.setContentProgressProvider(this);
|
||||
request.setUserRequestContext(pendingAdRequestContext);
|
||||
adsLoader.requestAds(request);
|
||||
}
|
||||
|
||||
// AdsLoader implementation.
|
||||
|
||||
@Override
|
||||
@ -268,14 +385,19 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
lastContentProgress = null;
|
||||
adDisplayContainer.setAdContainer(adUiViewGroup);
|
||||
player.addListener(this);
|
||||
maybeNotifyAdError();
|
||||
if (adPlaybackState != null) {
|
||||
// Pass the ad playback state to the player, and resume ads if necessary.
|
||||
eventListener.onAdPlaybackState(adPlaybackState.copy());
|
||||
if (imaPausedContent && player.getPlayWhenReady()) {
|
||||
adsManager.resume();
|
||||
}
|
||||
} else if (adsManager != null) {
|
||||
// Ads have loaded but the ads manager is not initialized.
|
||||
startAdPlayback();
|
||||
} else {
|
||||
pendingContentPositionMs = player.getCurrentPosition();
|
||||
requestAds();
|
||||
// Ads haven't loaded yet, so request them.
|
||||
requestAds(adUiViewGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,49 +434,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
return;
|
||||
}
|
||||
pendingAdRequestContext = null;
|
||||
|
||||
long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints());
|
||||
adPlaybackState = new AdPlaybackState(adGroupTimesUs);
|
||||
|
||||
this.adsManager = adsManager;
|
||||
adsManager.addAdErrorListener(this);
|
||||
adsManager.addAdEventListener(this);
|
||||
|
||||
ImaSdkFactory imaSdkFactory = ImaSdkFactory.getInstance();
|
||||
AdsRenderingSettings adsRenderingSettings = imaSdkFactory.createAdsRenderingSettings();
|
||||
adsRenderingSettings.setEnablePreloading(ENABLE_PRELOADING);
|
||||
adsRenderingSettings.setMimeTypes(supportedMimeTypes);
|
||||
int adGroupIndexForPosition =
|
||||
getAdGroupIndexForPosition(adGroupTimesUs, C.msToUs(pendingContentPositionMs));
|
||||
if (adGroupIndexForPosition == 0) {
|
||||
podIndexOffset = 0;
|
||||
} else if (adGroupIndexForPosition == C.INDEX_UNSET) {
|
||||
pendingContentPositionMs = C.TIME_UNSET;
|
||||
// There is no preroll and midroll pod indices start at 1.
|
||||
podIndexOffset = -1;
|
||||
} else /* adGroupIndexForPosition > 0 */ {
|
||||
// Skip ad groups before the one at or immediately before the playback position.
|
||||
for (int i = 0; i < adGroupIndexForPosition; i++) {
|
||||
adPlaybackState.playedAdGroup(i);
|
||||
if (player != null) {
|
||||
// If a player is attached already, start playback immediately.
|
||||
startAdPlayback();
|
||||
}
|
||||
// Play ads after the midpoint between the ad to play and the one before it, to avoid issues
|
||||
// with rounding one of the two ad times.
|
||||
long adGroupForPositionTimeUs = adGroupTimesUs[adGroupIndexForPosition];
|
||||
long adGroupBeforeTimeUs = adGroupTimesUs[adGroupIndexForPosition - 1];
|
||||
double midpointTimeUs = (adGroupForPositionTimeUs + adGroupBeforeTimeUs) / 2d;
|
||||
adsRenderingSettings.setPlayAdsAfterTime(midpointTimeUs / C.MICROS_PER_SECOND);
|
||||
|
||||
// We're removing one or more ads, which means that the earliest ad (if any) will be a
|
||||
// midroll/postroll. Midroll pod indices start at 1.
|
||||
podIndexOffset = adGroupIndexForPosition - 1;
|
||||
}
|
||||
|
||||
adsManager.init(adsRenderingSettings);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Initialized with ads rendering settings: " + adsRenderingSettings);
|
||||
}
|
||||
|
||||
updateAdPlaybackState();
|
||||
}
|
||||
|
||||
// AdEvent.AdEventListener implementation.
|
||||
@ -384,14 +470,12 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
adGroupIndex =
|
||||
podIndex == -1 ? (adPlaybackState.adGroupCount - 1) : (podIndex + podIndexOffset);
|
||||
int adPosition = adPodInfo.getAdPosition();
|
||||
int adCountInAdGroup = adPodInfo.getTotalAds();
|
||||
int adCount = adPodInfo.getTotalAds();
|
||||
adsManager.start();
|
||||
if (DEBUG) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"Loaded ad " + adPosition + " of " + adCountInAdGroup + " in group " + adGroupIndex);
|
||||
Log.d(TAG, "Loaded ad " + adPosition + " of " + adCount + " in group " + adGroupIndex);
|
||||
}
|
||||
adPlaybackState.setAdCount(adGroupIndex, adCountInAdGroup);
|
||||
adPlaybackState.setAdCount(adGroupIndex, adCount);
|
||||
updateAdPlaybackState();
|
||||
break;
|
||||
case CONTENT_PAUSE_REQUESTED:
|
||||
@ -434,14 +518,15 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
Log.d(TAG, "onAdError " + adErrorEvent);
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// No ads were loaded, so allow playback to start without any ads.
|
||||
pendingAdRequestContext = null;
|
||||
adPlaybackState = new AdPlaybackState(new long[0]);
|
||||
updateAdPlaybackState();
|
||||
}
|
||||
if (eventListener != null) {
|
||||
IOException exception = new IOException("Ad error: " + adErrorEvent, adErrorEvent.getError());
|
||||
eventListener.onLoadError(exception);
|
||||
if (pendingAdErrorEvent == null) {
|
||||
pendingAdErrorEvent = adErrorEvent;
|
||||
}
|
||||
maybeNotifyAdError();
|
||||
}
|
||||
|
||||
// ContentProgressProvider implementation.
|
||||
@ -654,18 +739,56 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private void requestAds() {
|
||||
if (pendingAdRequestContext != null) {
|
||||
// Ad request already in flight.
|
||||
return;
|
||||
private void startAdPlayback() {
|
||||
ImaSdkFactory imaSdkFactory = ImaSdkFactory.getInstance();
|
||||
AdsRenderingSettings adsRenderingSettings = imaSdkFactory.createAdsRenderingSettings();
|
||||
adsRenderingSettings.setEnablePreloading(ENABLE_PRELOADING);
|
||||
adsRenderingSettings.setMimeTypes(supportedMimeTypes);
|
||||
|
||||
// Set up the ad playback state, skipping ads based on the start position as required.
|
||||
pendingContentPositionMs = player.getCurrentPosition();
|
||||
long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints());
|
||||
adPlaybackState = new AdPlaybackState(adGroupTimesUs);
|
||||
int adGroupIndexForPosition =
|
||||
getAdGroupIndexForPosition(adGroupTimesUs, C.msToUs(pendingContentPositionMs));
|
||||
if (adGroupIndexForPosition == 0) {
|
||||
podIndexOffset = 0;
|
||||
} else if (adGroupIndexForPosition == C.INDEX_UNSET) {
|
||||
pendingContentPositionMs = C.TIME_UNSET;
|
||||
// There is no preroll and midroll pod indices start at 1.
|
||||
podIndexOffset = -1;
|
||||
} else /* adGroupIndexForPosition > 0 */ {
|
||||
// Skip ad groups before the one at or immediately before the playback position.
|
||||
for (int i = 0; i < adGroupIndexForPosition; i++) {
|
||||
adPlaybackState.playedAdGroup(i);
|
||||
}
|
||||
// Play ads after the midpoint between the ad to play and the one before it, to avoid issues
|
||||
// with rounding one of the two ad times.
|
||||
long adGroupForPositionTimeUs = adGroupTimesUs[adGroupIndexForPosition];
|
||||
long adGroupBeforeTimeUs = adGroupTimesUs[adGroupIndexForPosition - 1];
|
||||
double midpointTimeUs = (adGroupForPositionTimeUs + adGroupBeforeTimeUs) / 2d;
|
||||
adsRenderingSettings.setPlayAdsAfterTime(midpointTimeUs / C.MICROS_PER_SECOND);
|
||||
|
||||
// We're removing one or more ads, which means that the earliest ad (if any) will be a
|
||||
// midroll/postroll. Midroll pod indices start at 1.
|
||||
podIndexOffset = adGroupIndexForPosition - 1;
|
||||
}
|
||||
|
||||
// Start ad playback.
|
||||
adsManager.init(adsRenderingSettings);
|
||||
updateAdPlaybackState();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Initialized with ads rendering settings: " + adsRenderingSettings);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeNotifyAdError() {
|
||||
if (eventListener != null && pendingAdErrorEvent != null) {
|
||||
IOException exception =
|
||||
new IOException("Ad error: " + pendingAdErrorEvent, pendingAdErrorEvent.getError());
|
||||
eventListener.onLoadError(exception);
|
||||
pendingAdErrorEvent = null;
|
||||
}
|
||||
pendingAdRequestContext = new Object();
|
||||
AdsRequest request = imaSdkFactory.createAdsRequest();
|
||||
request.setAdTagUrl(adTagUri.toString());
|
||||
request.setAdDisplayContainer(adDisplayContainer);
|
||||
request.setContentProgressProvider(this);
|
||||
request.setUserRequestContext(pendingAdRequestContext);
|
||||
adsLoader.requestAds(request);
|
||||
}
|
||||
|
||||
private void updateImaStateForPlayerState() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user