mirror of
https://github.com/samsonjs/media.git
synced 2026-04-02 10:45:51 +00:00
Move IMA SDK callbacks into inner class
The release() method was added in the recent IMA API changes for preloading and now 'collides' with the ExoPlayer AdsLoader release method. This led to all ads completing being treated as a call to completely release the ads loader, which meant that the ad playback state was not updated on resuming after all ads had completed, which in turn led to playback getting stuck buffering on returning from the background after all ads played. Move the IMA callbacks into an inner class to avoid this. Issue: #7508 PiperOrigin-RevId: 316834561
This commit is contained in:
parent
f85098a88f
commit
28695d9ab5
3 changed files with 373 additions and 327 deletions
|
|
@ -215,6 +215,9 @@
|
|||
* Add option to skip ads before the start position.
|
||||
* Catch unexpected errors in `stopAd` to avoid a crash
|
||||
([#7492](https://github.com/google/ExoPlayer/issues/7492)).
|
||||
* Fix a bug that caused playback to be stuck buffering on resuming from
|
||||
the background after all ads had played to the end
|
||||
([#7508](https://github.com/google/ExoPlayer/issues/7508)).
|
||||
* Demo app: Retain previous position in list of samples.
|
||||
* Add Guava dependency.
|
||||
|
||||
|
|
|
|||
|
|
@ -86,14 +86,7 @@ import java.util.Set;
|
|||
* For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} and {@link
|
||||
* AdViewProvider#getAdOverlayViews()}.
|
||||
*/
|
||||
public final class ImaAdsLoader
|
||||
implements Player.EventListener,
|
||||
AdsLoader,
|
||||
VideoAdPlayer,
|
||||
ContentProgressProvider,
|
||||
AdErrorListener,
|
||||
AdsLoadedListener,
|
||||
AdEventListener {
|
||||
public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
||||
|
||||
static {
|
||||
ExoPlayerLibraryInfo.registerModule("goog.exo.ima");
|
||||
|
|
@ -364,12 +357,13 @@ public final class ImaAdsLoader
|
|||
*/
|
||||
private static final int IMA_AD_STATE_NONE = 0;
|
||||
/**
|
||||
* The ad playback state when IMA has called {@link #playAd(AdMediaInfo)} and not {@link
|
||||
* #pauseAd(AdMediaInfo)}.
|
||||
* The ad playback state when IMA has called {@link ComponentListener#playAd(AdMediaInfo)} and not
|
||||
* {@link ComponentListener##pauseAd(AdMediaInfo)}.
|
||||
*/
|
||||
private static final int IMA_AD_STATE_PLAYING = 1;
|
||||
/**
|
||||
* The ad playback state when IMA has called {@link #pauseAd(AdMediaInfo)} while playing an ad.
|
||||
* The ad playback state when IMA has called {@link ComponentListener#pauseAd(AdMediaInfo)} while
|
||||
* playing an ad.
|
||||
*/
|
||||
private static final int IMA_AD_STATE_PAUSED = 2;
|
||||
|
||||
|
|
@ -386,7 +380,8 @@ public final class ImaAdsLoader
|
|||
private final ImaFactory imaFactory;
|
||||
private final Timeline.Period period;
|
||||
private final Handler handler;
|
||||
private final List<VideoAdPlayerCallback> adCallbacks;
|
||||
private final ComponentListener componentListener;
|
||||
private final List<VideoAdPlayer.VideoAdPlayerCallback> adCallbacks;
|
||||
private final AdDisplayContainer adDisplayContainer;
|
||||
private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader;
|
||||
private final Runnable updateAdProgressRunnable;
|
||||
|
|
@ -400,7 +395,7 @@ public final class ImaAdsLoader
|
|||
@Nullable private Player player;
|
||||
private VideoProgressUpdate lastContentProgress;
|
||||
private VideoProgressUpdate lastAdProgress;
|
||||
private int lastVolumePercentage;
|
||||
private int lastVolumePercent;
|
||||
|
||||
@Nullable private AdsManager adsManager;
|
||||
private boolean isAdsManagerInitialized;
|
||||
|
|
@ -443,10 +438,10 @@ public final class ImaAdsLoader
|
|||
*/
|
||||
@Nullable private AdInfo pendingAdPrepareErrorAdInfo;
|
||||
/**
|
||||
* If a content period has finished but IMA has not yet called {@link #playAd(AdMediaInfo)},
|
||||
* stores the value of {@link SystemClock#elapsedRealtime()} when the content stopped playing.
|
||||
* This can be used to determine a fake, increasing content position. {@link C#TIME_UNSET}
|
||||
* otherwise.
|
||||
* If a content period has finished but IMA has not yet called {@link
|
||||
* ComponentListener#playAd(AdMediaInfo)}, stores the value of {@link
|
||||
* SystemClock#elapsedRealtime()} when the content stopped playing. This can be used to determine
|
||||
* a fake, increasing content position. {@link C#TIME_UNSET} otherwise.
|
||||
*/
|
||||
private long fakeContentProgressElapsedRealtimeMs;
|
||||
/**
|
||||
|
|
@ -456,7 +451,10 @@ public final class ImaAdsLoader
|
|||
private long fakeContentProgressOffsetMs;
|
||||
/** Stores the pending content position when a seek operation was intercepted to play an ad. */
|
||||
private long pendingContentPositionMs;
|
||||
/** Whether {@link #getContentProgress()} has sent {@link #pendingContentPositionMs} to IMA. */
|
||||
/**
|
||||
* Whether {@link ComponentListener#getContentProgress()} has sent {@link
|
||||
* #pendingContentPositionMs} to IMA.
|
||||
*/
|
||||
private boolean sentPendingContentPositionMs;
|
||||
/**
|
||||
* Stores the real time in milliseconds at which the player started buffering, possibly due to not
|
||||
|
|
@ -528,14 +526,15 @@ public final class ImaAdsLoader
|
|||
imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION);
|
||||
period = new Timeline.Period();
|
||||
handler = Util.createHandler(getImaLooper(), /* callback= */ null);
|
||||
componentListener = new ComponentListener();
|
||||
adCallbacks = new ArrayList<>(/* initialCapacity= */ 1);
|
||||
adDisplayContainer = imaFactory.createAdDisplayContainer();
|
||||
adDisplayContainer.setPlayer(/* videoAdPlayer= */ this);
|
||||
adDisplayContainer.setPlayer(/* videoAdPlayer= */ componentListener);
|
||||
adsLoader =
|
||||
imaFactory.createAdsLoader(
|
||||
context.getApplicationContext(), imaSdkSettings, adDisplayContainer);
|
||||
adsLoader.addAdErrorListener(/* adErrorListener= */ this);
|
||||
adsLoader.addAdsLoadedListener(/* adsLoadedListener= */ this);
|
||||
adsLoader.addAdErrorListener(componentListener);
|
||||
adsLoader.addAdsLoadedListener(componentListener);
|
||||
updateAdProgressRunnable = this::updateAdProgress;
|
||||
adInfoByAdMediaInfo = new HashMap<>();
|
||||
supportedMimeTypes = Collections.emptyList();
|
||||
|
|
@ -596,7 +595,7 @@ public final class ImaAdsLoader
|
|||
if (vastLoadTimeoutMs != TIMEOUT_UNSET) {
|
||||
request.setVastLoadTimeout(vastLoadTimeoutMs);
|
||||
}
|
||||
request.setContentProgressProvider(this);
|
||||
request.setContentProgressProvider(componentListener);
|
||||
pendingAdRequestContext = new Object();
|
||||
request.setUserRequestContext(pendingAdRequestContext);
|
||||
adsLoader.requestAds(request);
|
||||
|
|
@ -645,7 +644,7 @@ public final class ImaAdsLoader
|
|||
player.addListener(this);
|
||||
boolean playWhenReady = player.getPlayWhenReady();
|
||||
this.eventListener = eventListener;
|
||||
lastVolumePercentage = 0;
|
||||
lastVolumePercent = 0;
|
||||
lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
|
||||
lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
|
||||
ViewGroup adViewGroup = adViewProvider.getAdViewGroup();
|
||||
|
|
@ -682,9 +681,9 @@ public final class ImaAdsLoader
|
|||
adPlaybackState.withAdResumePositionUs(
|
||||
playingAd ? C.msToUs(player.getCurrentPosition()) : 0);
|
||||
}
|
||||
lastVolumePercentage = getVolume();
|
||||
lastVolumePercent = getPlayerVolumePercent();
|
||||
lastAdProgress = getAdVideoProgressUpdate();
|
||||
lastContentProgress = getContentProgress();
|
||||
lastContentProgress = getContentVideoProgressUpdate();
|
||||
adDisplayContainer.unregisterAllVideoControlsOverlays();
|
||||
player.removeListener(this);
|
||||
this.player = null;
|
||||
|
|
@ -695,8 +694,8 @@ public final class ImaAdsLoader
|
|||
public void release() {
|
||||
pendingAdRequestContext = null;
|
||||
destroyAdsManager();
|
||||
adsLoader.removeAdsLoadedListener(/* adsLoadedListener= */ this);
|
||||
adsLoader.removeAdErrorListener(/* adErrorListener= */ this);
|
||||
adsLoader.removeAdsLoadedListener(componentListener);
|
||||
adsLoader.removeAdErrorListener(componentListener);
|
||||
imaPausedContent = false;
|
||||
imaAdState = IMA_AD_STATE_NONE;
|
||||
imaAdMediaInfo = null;
|
||||
|
|
@ -704,7 +703,7 @@ public final class ImaAdsLoader
|
|||
imaAdInfo = null;
|
||||
pendingAdLoadError = null;
|
||||
adPlaybackState = AdPlaybackState.NONE;
|
||||
hasAdPlaybackState = false;
|
||||
hasAdPlaybackState = true;
|
||||
updateAdPlaybackState();
|
||||
}
|
||||
|
||||
|
|
@ -720,275 +719,6 @@ public final class ImaAdsLoader
|
|||
}
|
||||
}
|
||||
|
||||
// com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener implementation.
|
||||
|
||||
@Override
|
||||
public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) {
|
||||
AdsManager adsManager = adsManagerLoadedEvent.getAdsManager();
|
||||
if (!Util.areEqual(pendingAdRequestContext, adsManagerLoadedEvent.getUserRequestContext())) {
|
||||
adsManager.destroy();
|
||||
return;
|
||||
}
|
||||
pendingAdRequestContext = null;
|
||||
this.adsManager = adsManager;
|
||||
adsManager.addAdErrorListener(this);
|
||||
adsManager.addAdEventListener(this);
|
||||
if (adEventListener != null) {
|
||||
adsManager.addAdEventListener(adEventListener);
|
||||
}
|
||||
if (player != null) {
|
||||
// If a player is attached already, start playback immediately.
|
||||
try {
|
||||
adPlaybackState = AdPlaybackStateFactory.fromCuePoints(adsManager.getAdCuePoints());
|
||||
hasAdPlaybackState = true;
|
||||
updateAdPlaybackState();
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("onAdsManagerLoaded", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AdEvent.AdEventListener implementation.
|
||||
|
||||
@Override
|
||||
public void onAdEvent(AdEvent adEvent) {
|
||||
AdEventType adEventType = adEvent.getType();
|
||||
if (DEBUG && adEventType != AdEventType.AD_PROGRESS) {
|
||||
Log.d(TAG, "onAdEvent: " + adEventType);
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// Drop events after release.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
handleAdEvent(adEvent);
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("onAdEvent", e);
|
||||
}
|
||||
}
|
||||
|
||||
// AdErrorEvent.AdErrorListener implementation.
|
||||
|
||||
@Override
|
||||
public void onAdError(AdErrorEvent adErrorEvent) {
|
||||
AdError error = adErrorEvent.getError();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onAdError", error);
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// No ads were loaded, so allow playback to start without any ads.
|
||||
pendingAdRequestContext = null;
|
||||
adPlaybackState = AdPlaybackState.NONE;
|
||||
hasAdPlaybackState = true;
|
||||
updateAdPlaybackState();
|
||||
} else if (isAdGroupLoadError(error)) {
|
||||
try {
|
||||
handleAdGroupLoadError(error);
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("onAdError", e);
|
||||
}
|
||||
}
|
||||
if (pendingAdLoadError == null) {
|
||||
pendingAdLoadError = AdLoadException.createForAllAds(error);
|
||||
}
|
||||
maybeNotifyPendingAdLoadError();
|
||||
}
|
||||
|
||||
// ContentProgressProvider implementation.
|
||||
|
||||
@Override
|
||||
public VideoProgressUpdate getContentProgress() {
|
||||
VideoProgressUpdate videoProgressUpdate = getContentVideoProgressUpdate();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Content progress: " + videoProgressUpdate);
|
||||
}
|
||||
|
||||
if (waitingForPreloadElapsedRealtimeMs != C.TIME_UNSET) {
|
||||
// IMA is polling the player position but we are buffering for an ad to preload, so playback
|
||||
// may be stuck. Detect this case and signal an error if applicable.
|
||||
long stuckElapsedRealtimeMs =
|
||||
SystemClock.elapsedRealtime() - waitingForPreloadElapsedRealtimeMs;
|
||||
if (stuckElapsedRealtimeMs >= THRESHOLD_AD_PRELOAD_MS) {
|
||||
waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET;
|
||||
handleAdGroupLoadError(new IOException("Ad preloading timed out"));
|
||||
maybeNotifyPendingAdLoadError();
|
||||
}
|
||||
}
|
||||
|
||||
return videoProgressUpdate;
|
||||
}
|
||||
|
||||
// VideoAdPlayer implementation.
|
||||
|
||||
@Override
|
||||
public VideoProgressUpdate getAdProgress() {
|
||||
throw new IllegalStateException("Unexpected call to getAdProgress when using preloading");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVolume() {
|
||||
@Nullable Player player = this.player;
|
||||
if (player == null) {
|
||||
return lastVolumePercentage;
|
||||
}
|
||||
|
||||
@Nullable Player.AudioComponent audioComponent = player.getAudioComponent();
|
||||
if (audioComponent != null) {
|
||||
return (int) (audioComponent.getVolume() * 100);
|
||||
}
|
||||
|
||||
// Check for a selected track using an audio renderer.
|
||||
TrackSelectionArray trackSelections = player.getCurrentTrackSelections();
|
||||
for (int i = 0; i < player.getRendererCount() && i < trackSelections.length; i++) {
|
||||
if (player.getRendererType(i) == C.TRACK_TYPE_AUDIO && trackSelections.get(i) != null) {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAd(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) {
|
||||
try {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "loadAd " + getAdMediaInfoString(adMediaInfo) + ", ad pod " + adPodInfo);
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// Drop events after release.
|
||||
return;
|
||||
}
|
||||
int adGroupIndex = getAdGroupIndexForAdPod(adPodInfo);
|
||||
int adIndexInAdGroup = adPodInfo.getAdPosition() - 1;
|
||||
AdInfo adInfo = new AdInfo(adGroupIndex, adIndexInAdGroup);
|
||||
adInfoByAdMediaInfo.put(adMediaInfo, adInfo);
|
||||
if (adPlaybackState.isAdInErrorState(adGroupIndex, adIndexInAdGroup)) {
|
||||
// We have already marked this ad as having failed to load, so ignore the request. IMA will
|
||||
// timeout after its media load timeout.
|
||||
return;
|
||||
}
|
||||
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adInfo.adGroupIndex];
|
||||
if (adGroup.count == C.LENGTH_UNSET) {
|
||||
adPlaybackState =
|
||||
adPlaybackState.withAdCount(
|
||||
adInfo.adGroupIndex, Math.max(adPodInfo.getTotalAds(), adGroup.states.length));
|
||||
adGroup = adPlaybackState.adGroups[adInfo.adGroupIndex];
|
||||
}
|
||||
for (int i = 0; i < adIndexInAdGroup; i++) {
|
||||
// Any preceding ads that haven't loaded are not going to load.
|
||||
if (adGroup.states[i] == AdPlaybackState.AD_STATE_UNAVAILABLE) {
|
||||
adPlaybackState =
|
||||
adPlaybackState.withAdLoadError(
|
||||
/* adGroupIndex= */ adGroupIndex, /* adIndexInAdGroup= */ i);
|
||||
}
|
||||
}
|
||||
Uri adUri = Uri.parse(adMediaInfo.getUrl());
|
||||
adPlaybackState =
|
||||
adPlaybackState.withAdUri(adInfo.adGroupIndex, adInfo.adIndexInAdGroup, adUri);
|
||||
updateAdPlaybackState();
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("loadAd", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
|
||||
adCallbacks.add(videoAdPlayerCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
|
||||
adCallbacks.remove(videoAdPlayerCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playAd(AdMediaInfo adMediaInfo) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "playAd " + getAdMediaInfoString(adMediaInfo));
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// Drop events after release.
|
||||
return;
|
||||
}
|
||||
|
||||
if (imaAdState == IMA_AD_STATE_PLAYING) {
|
||||
// IMA does not always call stopAd before resuming content.
|
||||
// See [Internal: b/38354028].
|
||||
Log.w(TAG, "Unexpected playAd without stopAd");
|
||||
}
|
||||
|
||||
try {
|
||||
if (imaAdState == IMA_AD_STATE_NONE) {
|
||||
// IMA is requesting to play the ad, so stop faking the content position.
|
||||
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
|
||||
fakeContentProgressOffsetMs = C.TIME_UNSET;
|
||||
imaAdState = IMA_AD_STATE_PLAYING;
|
||||
imaAdMediaInfo = adMediaInfo;
|
||||
imaAdInfo = Assertions.checkNotNull(adInfoByAdMediaInfo.get(adMediaInfo));
|
||||
for (int i = 0; i < adCallbacks.size(); i++) {
|
||||
adCallbacks.get(i).onPlay(adMediaInfo);
|
||||
}
|
||||
if (pendingAdPrepareErrorAdInfo != null && pendingAdPrepareErrorAdInfo.equals(imaAdInfo)) {
|
||||
pendingAdPrepareErrorAdInfo = null;
|
||||
for (int i = 0; i < adCallbacks.size(); i++) {
|
||||
adCallbacks.get(i).onError(adMediaInfo);
|
||||
}
|
||||
}
|
||||
updateAdProgress();
|
||||
} else {
|
||||
imaAdState = IMA_AD_STATE_PLAYING;
|
||||
Assertions.checkState(adMediaInfo.equals(imaAdMediaInfo));
|
||||
for (int i = 0; i < adCallbacks.size(); i++) {
|
||||
adCallbacks.get(i).onResume(adMediaInfo);
|
||||
}
|
||||
}
|
||||
if (!Assertions.checkNotNull(player).getPlayWhenReady()) {
|
||||
Assertions.checkNotNull(adsManager).pause();
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("playAd", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAd(AdMediaInfo adMediaInfo) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "stopAd " + getAdMediaInfoString(adMediaInfo));
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// Drop event after release.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Assertions.checkNotNull(player);
|
||||
Assertions.checkState(imaAdState != IMA_AD_STATE_NONE);
|
||||
stopAdInternal();
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("stopAd", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseAd(AdMediaInfo adMediaInfo) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "pauseAd " + getAdMediaInfoString(adMediaInfo));
|
||||
}
|
||||
if (imaAdState == IMA_AD_STATE_NONE) {
|
||||
// This method is called after content is resumed.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Assertions.checkState(adMediaInfo.equals(imaAdMediaInfo));
|
||||
imaAdState = IMA_AD_STATE_PAUSED;
|
||||
for (int i = 0; i < adCallbacks.size(); i++) {
|
||||
adCallbacks.get(i).onPause(adMediaInfo);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("pauseAd", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Player.EventListener implementation.
|
||||
|
||||
@Override
|
||||
|
|
@ -1265,6 +995,27 @@ public final class ImaAdsLoader
|
|||
handler.removeCallbacks(updateAdProgressRunnable);
|
||||
}
|
||||
|
||||
private int getPlayerVolumePercent() {
|
||||
@Nullable Player player = this.player;
|
||||
if (player == null) {
|
||||
return lastVolumePercent;
|
||||
}
|
||||
|
||||
@Nullable Player.AudioComponent audioComponent = player.getAudioComponent();
|
||||
if (audioComponent != null) {
|
||||
return (int) (audioComponent.getVolume() * 100);
|
||||
}
|
||||
|
||||
// Check for a selected track using an audio renderer.
|
||||
TrackSelectionArray trackSelections = player.getCurrentTrackSelections();
|
||||
for (int i = 0; i < player.getRendererCount() && i < trackSelections.length; i++) {
|
||||
if (player.getRendererType(i) == C.TRACK_TYPE_AUDIO && trackSelections.get(i) != null) {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void handlePlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
|
||||
if (playingAd && imaAdState == IMA_AD_STATE_PLAYING) {
|
||||
if (!bufferingAd && playbackState == Player.STATE_BUFFERING) {
|
||||
|
|
@ -1581,8 +1332,8 @@ public final class ImaAdsLoader
|
|||
|
||||
private void destroyAdsManager() {
|
||||
if (adsManager != null) {
|
||||
adsManager.removeAdErrorListener(this);
|
||||
adsManager.removeAdEventListener(this);
|
||||
adsManager.removeAdErrorListener(componentListener);
|
||||
adsManager.removeAdEventListener(componentListener);
|
||||
if (adEventListener != null) {
|
||||
adsManager.removeAdEventListener(adEventListener);
|
||||
}
|
||||
|
|
@ -1607,6 +1358,272 @@ public final class ImaAdsLoader
|
|||
Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer);
|
||||
}
|
||||
|
||||
private final class ComponentListener
|
||||
implements VideoAdPlayer,
|
||||
ContentProgressProvider,
|
||||
AdErrorListener,
|
||||
AdsLoadedListener,
|
||||
AdEventListener {
|
||||
|
||||
// com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener implementation.
|
||||
|
||||
@Override
|
||||
public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) {
|
||||
AdsManager adsManager = adsManagerLoadedEvent.getAdsManager();
|
||||
if (!Util.areEqual(pendingAdRequestContext, adsManagerLoadedEvent.getUserRequestContext())) {
|
||||
adsManager.destroy();
|
||||
return;
|
||||
}
|
||||
pendingAdRequestContext = null;
|
||||
ImaAdsLoader.this.adsManager = adsManager;
|
||||
adsManager.addAdErrorListener(this);
|
||||
adsManager.addAdEventListener(this);
|
||||
if (adEventListener != null) {
|
||||
adsManager.addAdEventListener(adEventListener);
|
||||
}
|
||||
if (player != null) {
|
||||
// If a player is attached already, start playback immediately.
|
||||
try {
|
||||
adPlaybackState = AdPlaybackStateFactory.fromCuePoints(adsManager.getAdCuePoints());
|
||||
hasAdPlaybackState = true;
|
||||
updateAdPlaybackState();
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("onAdsManagerLoaded", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AdEvent.AdEventListener implementation.
|
||||
|
||||
@Override
|
||||
public void onAdEvent(AdEvent adEvent) {
|
||||
AdEventType adEventType = adEvent.getType();
|
||||
if (DEBUG && adEventType != AdEventType.AD_PROGRESS) {
|
||||
Log.d(TAG, "onAdEvent: " + adEventType);
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// Drop events after release.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
handleAdEvent(adEvent);
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("onAdEvent", e);
|
||||
}
|
||||
}
|
||||
|
||||
// AdErrorEvent.AdErrorListener implementation.
|
||||
|
||||
@Override
|
||||
public void onAdError(AdErrorEvent adErrorEvent) {
|
||||
AdError error = adErrorEvent.getError();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onAdError", error);
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// No ads were loaded, so allow playback to start without any ads.
|
||||
pendingAdRequestContext = null;
|
||||
adPlaybackState = AdPlaybackState.NONE;
|
||||
hasAdPlaybackState = true;
|
||||
updateAdPlaybackState();
|
||||
} else if (isAdGroupLoadError(error)) {
|
||||
try {
|
||||
handleAdGroupLoadError(error);
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("onAdError", e);
|
||||
}
|
||||
}
|
||||
if (pendingAdLoadError == null) {
|
||||
pendingAdLoadError = AdLoadException.createForAllAds(error);
|
||||
}
|
||||
maybeNotifyPendingAdLoadError();
|
||||
}
|
||||
|
||||
// ContentProgressProvider implementation.
|
||||
|
||||
@Override
|
||||
public VideoProgressUpdate getContentProgress() {
|
||||
VideoProgressUpdate videoProgressUpdate = getContentVideoProgressUpdate();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Content progress: " + videoProgressUpdate);
|
||||
}
|
||||
|
||||
if (waitingForPreloadElapsedRealtimeMs != C.TIME_UNSET) {
|
||||
// IMA is polling the player position but we are buffering for an ad to preload, so playback
|
||||
// may be stuck. Detect this case and signal an error if applicable.
|
||||
long stuckElapsedRealtimeMs =
|
||||
SystemClock.elapsedRealtime() - waitingForPreloadElapsedRealtimeMs;
|
||||
if (stuckElapsedRealtimeMs >= THRESHOLD_AD_PRELOAD_MS) {
|
||||
waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET;
|
||||
handleAdGroupLoadError(new IOException("Ad preloading timed out"));
|
||||
maybeNotifyPendingAdLoadError();
|
||||
}
|
||||
}
|
||||
|
||||
return videoProgressUpdate;
|
||||
}
|
||||
|
||||
// VideoAdPlayer implementation.
|
||||
|
||||
@Override
|
||||
public VideoProgressUpdate getAdProgress() {
|
||||
throw new IllegalStateException("Unexpected call to getAdProgress when using preloading");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVolume() {
|
||||
return getPlayerVolumePercent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAd(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) {
|
||||
try {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "loadAd " + getAdMediaInfoString(adMediaInfo) + ", ad pod " + adPodInfo);
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// Drop events after release.
|
||||
return;
|
||||
}
|
||||
int adGroupIndex = getAdGroupIndexForAdPod(adPodInfo);
|
||||
int adIndexInAdGroup = adPodInfo.getAdPosition() - 1;
|
||||
AdInfo adInfo = new AdInfo(adGroupIndex, adIndexInAdGroup);
|
||||
adInfoByAdMediaInfo.put(adMediaInfo, adInfo);
|
||||
if (adPlaybackState.isAdInErrorState(adGroupIndex, adIndexInAdGroup)) {
|
||||
// We have already marked this ad as having failed to load, so ignore the request. IMA
|
||||
// will timeout after its media load timeout.
|
||||
return;
|
||||
}
|
||||
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adInfo.adGroupIndex];
|
||||
if (adGroup.count == C.LENGTH_UNSET) {
|
||||
adPlaybackState =
|
||||
adPlaybackState.withAdCount(
|
||||
adInfo.adGroupIndex, Math.max(adPodInfo.getTotalAds(), adGroup.states.length));
|
||||
adGroup = adPlaybackState.adGroups[adInfo.adGroupIndex];
|
||||
}
|
||||
for (int i = 0; i < adIndexInAdGroup; i++) {
|
||||
// Any preceding ads that haven't loaded are not going to load.
|
||||
if (adGroup.states[i] == AdPlaybackState.AD_STATE_UNAVAILABLE) {
|
||||
adPlaybackState =
|
||||
adPlaybackState.withAdLoadError(
|
||||
/* adGroupIndex= */ adGroupIndex, /* adIndexInAdGroup= */ i);
|
||||
}
|
||||
}
|
||||
Uri adUri = Uri.parse(adMediaInfo.getUrl());
|
||||
adPlaybackState =
|
||||
adPlaybackState.withAdUri(adInfo.adGroupIndex, adInfo.adIndexInAdGroup, adUri);
|
||||
updateAdPlaybackState();
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("loadAd", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
|
||||
adCallbacks.add(videoAdPlayerCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCallback(VideoAdPlayerCallback videoAdPlayerCallback) {
|
||||
adCallbacks.remove(videoAdPlayerCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playAd(AdMediaInfo adMediaInfo) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "playAd " + getAdMediaInfoString(adMediaInfo));
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// Drop events after release.
|
||||
return;
|
||||
}
|
||||
|
||||
if (imaAdState == IMA_AD_STATE_PLAYING) {
|
||||
// IMA does not always call stopAd before resuming content.
|
||||
// See [Internal: b/38354028].
|
||||
Log.w(TAG, "Unexpected playAd without stopAd");
|
||||
}
|
||||
|
||||
try {
|
||||
if (imaAdState == IMA_AD_STATE_NONE) {
|
||||
// IMA is requesting to play the ad, so stop faking the content position.
|
||||
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
|
||||
fakeContentProgressOffsetMs = C.TIME_UNSET;
|
||||
imaAdState = IMA_AD_STATE_PLAYING;
|
||||
imaAdMediaInfo = adMediaInfo;
|
||||
imaAdInfo = Assertions.checkNotNull(adInfoByAdMediaInfo.get(adMediaInfo));
|
||||
for (int i = 0; i < adCallbacks.size(); i++) {
|
||||
adCallbacks.get(i).onPlay(adMediaInfo);
|
||||
}
|
||||
if (pendingAdPrepareErrorAdInfo != null
|
||||
&& pendingAdPrepareErrorAdInfo.equals(imaAdInfo)) {
|
||||
pendingAdPrepareErrorAdInfo = null;
|
||||
for (int i = 0; i < adCallbacks.size(); i++) {
|
||||
adCallbacks.get(i).onError(adMediaInfo);
|
||||
}
|
||||
}
|
||||
updateAdProgress();
|
||||
} else {
|
||||
imaAdState = IMA_AD_STATE_PLAYING;
|
||||
Assertions.checkState(adMediaInfo.equals(imaAdMediaInfo));
|
||||
for (int i = 0; i < adCallbacks.size(); i++) {
|
||||
adCallbacks.get(i).onResume(adMediaInfo);
|
||||
}
|
||||
}
|
||||
if (!Assertions.checkNotNull(player).getPlayWhenReady()) {
|
||||
Assertions.checkNotNull(adsManager).pause();
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("playAd", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAd(AdMediaInfo adMediaInfo) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "stopAd " + getAdMediaInfoString(adMediaInfo));
|
||||
}
|
||||
if (adsManager == null) {
|
||||
// Drop event after release.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Assertions.checkNotNull(player);
|
||||
Assertions.checkState(imaAdState != IMA_AD_STATE_NONE);
|
||||
stopAdInternal();
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("stopAd", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseAd(AdMediaInfo adMediaInfo) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "pauseAd " + getAdMediaInfoString(adMediaInfo));
|
||||
}
|
||||
if (imaAdState == IMA_AD_STATE_NONE) {
|
||||
// This method is called after content is resumed.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Assertions.checkState(adMediaInfo.equals(imaAdMediaInfo));
|
||||
imaAdState = IMA_AD_STATE_PAUSED;
|
||||
for (int i = 0; i < adCallbacks.size(); i++) {
|
||||
adCallbacks.get(i).onPause(adMediaInfo);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
maybeNotifyInternalError("pauseAd", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider moving this into AdPlaybackState.
|
||||
private static final class AdInfo {
|
||||
public final int adGroupIndex;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings;
|
|||
import com.google.ads.interactivemedia.v3.api.AdsRequest;
|
||||
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
|
||||
import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo;
|
||||
import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider;
|
||||
import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
|
|
@ -114,6 +116,9 @@ public final class ImaAdsLoaderTest {
|
|||
private ViewGroup adViewGroup;
|
||||
private View adOverlayView;
|
||||
private AdsLoader.AdViewProvider adViewProvider;
|
||||
private AdEvent.AdEventListener adEventListener;
|
||||
private ContentProgressProvider contentProgressProvider;
|
||||
private VideoAdPlayer videoAdPlayer;
|
||||
private TestAdsLoaderListener adsLoaderListener;
|
||||
private FakePlayer fakeExoPlayer;
|
||||
private ImaAdsLoader imaAdsLoader;
|
||||
|
|
@ -192,6 +197,8 @@ public final class ImaAdsLoaderTest {
|
|||
@Test
|
||||
public void startAndCallbacksAfterRelease() {
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
// Request ads in order to get a reference to the ad event listener.
|
||||
imaAdsLoader.requestAds(adViewGroup);
|
||||
imaAdsLoader.release();
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
fakeExoPlayer.setPlayingContentPosition(/* position= */ 0);
|
||||
|
|
@ -202,16 +209,16 @@ public final class ImaAdsLoaderTest {
|
|||
// when using Robolectric and accessing VideoProgressUpdate.VIDEO_TIME_NOT_READY, due to the IMA
|
||||
// SDK being proguarded.
|
||||
imaAdsLoader.requestAds(adViewGroup);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||
imaAdsLoader.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||
imaAdsLoader.playAd(TEST_AD_MEDIA_INFO);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
imaAdsLoader.pauseAd(TEST_AD_MEDIA_INFO);
|
||||
imaAdsLoader.stopAd(TEST_AD_MEDIA_INFO);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||
videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||
videoAdPlayer.playAd(TEST_AD_MEDIA_INFO);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
videoAdPlayer.pauseAd(TEST_AD_MEDIA_INFO);
|
||||
videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
|
||||
imaAdsLoader.onPlayerError(ExoPlaybackException.createForSource(new IOException()));
|
||||
imaAdsLoader.onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||
imaAdsLoader.handlePrepareError(
|
||||
/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, new IOException());
|
||||
}
|
||||
|
|
@ -222,27 +229,27 @@ public final class ImaAdsLoaderTest {
|
|||
|
||||
// Load the preroll ad.
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||
imaAdsLoader.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.LOADED, mockPrerollSingleAd));
|
||||
videoAdPlayer.loadAd(TEST_AD_MEDIA_INFO, mockAdPodInfo);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, mockPrerollSingleAd));
|
||||
|
||||
// Play the preroll ad.
|
||||
imaAdsLoader.playAd(TEST_AD_MEDIA_INFO);
|
||||
videoAdPlayer.playAd(TEST_AD_MEDIA_INFO);
|
||||
fakeExoPlayer.setPlayingAdPosition(
|
||||
/* adGroupIndex= */ 0,
|
||||
/* adIndexInAdGroup= */ 0,
|
||||
/* position= */ 0,
|
||||
/* contentPosition= */ 0);
|
||||
fakeExoPlayer.setState(Player.STATE_READY, true);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.MIDPOINT, mockPrerollSingleAd));
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.THIRD_QUARTILE, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.MIDPOINT, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.THIRD_QUARTILE, mockPrerollSingleAd));
|
||||
|
||||
// Play the content.
|
||||
fakeExoPlayer.setPlayingContentPosition(0);
|
||||
imaAdsLoader.stopAd(TEST_AD_MEDIA_INFO);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||
videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||
|
||||
// Verify that the preroll ad has been marked as played.
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
|
|
@ -262,7 +269,7 @@ public final class ImaAdsLoaderTest {
|
|||
|
||||
// Simulate loading an empty postroll ad.
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
imaAdsLoader.onAdEvent(mockPostrollFetchErrorAdEvent);
|
||||
adEventListener.onAdEvent(mockPostrollFetchErrorAdEvent);
|
||||
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
|
|
@ -289,7 +296,7 @@ public final class ImaAdsLoaderTest {
|
|||
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
||||
// Advance before the timeout and simulating polling content progress.
|
||||
ShadowSystemClock.advanceBy(Duration.ofSeconds(1));
|
||||
imaAdsLoader.getContentProgress();
|
||||
contentProgressProvider.getContentProgress();
|
||||
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
|
|
@ -313,7 +320,7 @@ public final class ImaAdsLoaderTest {
|
|||
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
||||
// Advance past the timeout and simulate polling content progress.
|
||||
ShadowSystemClock.advanceBy(Duration.ofSeconds(5));
|
||||
imaAdsLoader.getContentProgress();
|
||||
contentProgressProvider.getContentProgress();
|
||||
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
|
|
@ -660,6 +667,8 @@ public final class ImaAdsLoaderTest {
|
|||
.thenAnswer(invocation -> userRequestContextCaptor.getValue());
|
||||
List<com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener> adsLoadedListeners =
|
||||
new ArrayList<>();
|
||||
// Deliberately don't handle removeAdsLoadedListener to allow testing behavior if the IMA SDK
|
||||
// invokes callbacks after release.
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
adsLoadedListeners.add(invocation.getArgument(0));
|
||||
|
|
@ -667,13 +676,6 @@ public final class ImaAdsLoaderTest {
|
|||
})
|
||||
.when(mockAdsLoader)
|
||||
.addAdsLoadedListener(any());
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
adsLoadedListeners.remove(invocation.getArgument(0));
|
||||
return null;
|
||||
})
|
||||
.when(mockAdsLoader)
|
||||
.removeAdsLoadedListener(any());
|
||||
when(mockAdsManagerLoadedEvent.getAdsManager()).thenReturn(mockAdsManager);
|
||||
when(mockAdsManagerLoadedEvent.getUserRequestContext())
|
||||
.thenAnswer(invocation -> mockAdsRequest.getUserRequestContext());
|
||||
|
|
@ -689,6 +691,30 @@ public final class ImaAdsLoaderTest {
|
|||
.when(mockAdsLoader)
|
||||
.requestAds(mockAdsRequest);
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
adEventListener = invocation.getArgument(0);
|
||||
return null;
|
||||
})
|
||||
.when(mockAdsManager)
|
||||
.addAdEventListener(any());
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
contentProgressProvider = invocation.getArgument(0);
|
||||
return null;
|
||||
})
|
||||
.when(mockAdsRequest)
|
||||
.setContentProgressProvider(any());
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
videoAdPlayer = invocation.getArgument(0);
|
||||
return null;
|
||||
})
|
||||
.when(mockAdDisplayContainer)
|
||||
.setPlayer(any());
|
||||
|
||||
when(mockImaFactory.createAdDisplayContainer()).thenReturn(mockAdDisplayContainer);
|
||||
when(mockImaFactory.createAdsRenderingSettings()).thenReturn(mockAdsRenderingSettings);
|
||||
when(mockImaFactory.createAdsRequest()).thenReturn(mockAdsRequest);
|
||||
|
|
|
|||
Loading…
Reference in a new issue