Add support for ad playlists with ImaAdsLoader

Issue: #3750
PiperOrigin-RevId: 343878310
This commit is contained in:
andrewlewis 2020-11-23 18:29:11 +00:00 committed by kim-vde
parent 689e89e5f3
commit 05f6d24821
4 changed files with 628 additions and 273 deletions

View File

@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.ima;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.BITRATE_UNSET;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.TIMEOUT_UNSET;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.getAdGroupTimesUsForCuePoints;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.getImaLooper;
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 java.lang.Math.max;
@ -280,11 +280,11 @@ import java.util.Map;
}
}
/** Starts using the ads loader for playback. */
public void start(Player player, AdViewProvider adViewProvider, EventListener eventListener) {
this.player = player;
player.addListener(this);
boolean playWhenReady = player.getPlayWhenReady();
/**
* Starts passing events from this instance (including any pending ad playback state) and
* registers obstructions.
*/
public void start(AdViewProvider adViewProvider, EventListener eventListener) {
this.eventListener = eventListener;
lastVolumePercent = 0;
lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
@ -293,13 +293,9 @@ import java.util.Map;
if (!AdPlaybackState.NONE.equals(adPlaybackState)) {
// Pass the ad playback state to the player, and resume ads if necessary.
eventListener.onAdPlaybackState(adPlaybackState);
if (adsManager != null && imaPausedContent && playWhenReady) {
adsManager.resume();
}
} else if (adsManager != null) {
adPlaybackState =
new AdPlaybackState(
adsId, ImaUtil.getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints()));
new AdPlaybackState(adsId, getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints()));
updateAdPlaybackState();
}
for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) {
@ -311,14 +307,36 @@ import java.util.Map;
}
}
/** Stops using the ads loader for playback. */
public void stop() {
@Nullable Player player = this.player;
if (player == null) {
return;
/**
* Populates the ad playback state with loaded cue points, if available. Any preroll will be
* paused immediately while waiting for this instance to be {@link #activate(Player) activated}.
*/
public void maybePreloadAds(long contentPositionMs, long contentDurationMs) {
maybeInitializeAdsManager(contentPositionMs, contentDurationMs);
}
/** Activates playback. */
public void activate(Player player) {
this.player = player;
player.addListener(this);
boolean playWhenReady = player.getPlayWhenReady();
onTimelineChanged(player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
if (!AdPlaybackState.NONE.equals(adPlaybackState)
&& adsManager != null
&& imaPausedContent
&& playWhenReady) {
adsManager.resume();
}
if (adsManager != null && imaPausedContent) {
adsManager.pause();
}
/** Deactivates playback. */
public void deactivate() {
Player player = checkNotNull(this.player);
if (!AdPlaybackState.NONE.equals(adPlaybackState) && imaPausedContent) {
if (adsManager != null) {
adsManager.pause();
}
adPlaybackState =
adPlaybackState.withAdResumePositionUs(
playingAd ? C.msToUs(player.getCurrentPosition()) : 0);
@ -326,10 +344,15 @@ import java.util.Map;
lastVolumePercent = getPlayerVolumePercent();
lastAdProgress = getAdVideoProgressUpdate();
lastContentProgress = getContentVideoProgressUpdate();
adDisplayContainer.unregisterAllFriendlyObstructions();
player.removeListener(this);
this.player = null;
}
/** Stops passing of events from this instance and unregisters obstructions. */
public void stop() {
eventListener = null;
adDisplayContainer.unregisterAllFriendlyObstructions();
}
/** Releases all resources used by the ad tag loader. */
@ -392,7 +415,6 @@ import java.util.Map;
// The player is being reset or contains no media.
return;
}
checkArgument(timeline.getPeriodCount() == 1);
this.timeline = timeline;
Player player = checkNotNull(this.player);
long contentDurationUs = timeline.getPeriod(player.getCurrentPeriodIndex(), period).durationUs;
@ -592,14 +614,13 @@ import java.util.Map;
}
private VideoProgressUpdate getContentVideoProgressUpdate() {
if (player == null) {
return lastContentProgress;
}
boolean hasContentDuration = contentDurationMs != C.TIME_UNSET;
long contentPositionMs;
if (pendingContentPositionMs != C.TIME_UNSET) {
sentPendingContentPositionMs = true;
contentPositionMs = pendingContentPositionMs;
} else if (player == null) {
return lastContentProgress;
} else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) {
long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs;
contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs;
@ -923,7 +944,8 @@ import java.util.Map;
adCallbacks.get(i).onResume(adMediaInfo);
}
}
if (!checkNotNull(player).getPlayWhenReady()) {
if (player == null || !player.getPlayWhenReady()) {
// Either this loader hasn't been activated yet, or the player is paused now.
checkNotNull(adsManager).pause();
}
}
@ -941,7 +963,14 @@ import java.util.Map;
// to a different position, so drop the event. See also [Internal: b/159111848].
return;
}
checkState(adMediaInfo.equals(imaAdMediaInfo));
if (configuration.debugModeEnabled && !adMediaInfo.equals(imaAdMediaInfo)) {
Log.w(
TAG,
"Unexpected pauseAd for "
+ getAdMediaInfoString(adMediaInfo)
+ ", expected "
+ getAdMediaInfoString(imaAdMediaInfo));
}
imaAdState = IMA_AD_STATE_PAUSED;
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onPause(adMediaInfo);
@ -1157,9 +1186,13 @@ import java.util.Map;
throw new IllegalStateException("Failed to find cue point");
}
private String getAdMediaInfoString(AdMediaInfo adMediaInfo) {
private String getAdMediaInfoString(@Nullable AdMediaInfo adMediaInfo) {
@Nullable AdInfo adInfo = adInfoByAdMediaInfo.get(adMediaInfo);
return "AdMediaInfo[" + adMediaInfo.getUrl() + (adInfo != null ? ", " + adInfo : "") + "]";
return "AdMediaInfo["
+ (adMediaInfo == null ? "null" : adMediaInfo.getUrl())
+ ", "
+ adInfo
+ "]";
}
private static long getContentPeriodPositionMs(
@ -1226,16 +1259,12 @@ import java.util.Map;
if (configuration.applicationAdEventListener != null) {
adsManager.addAdEventListener(configuration.applicationAdEventListener);
}
if (player != null) {
// If a player is attached already, start playback immediately.
try {
adPlaybackState =
new AdPlaybackState(
adsId, ImaUtil.getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints()));
updateAdPlaybackState();
} catch (RuntimeException e) {
maybeNotifyInternalError("onAdsManagerLoaded", e);
}
try {
adPlaybackState =
new AdPlaybackState(adsId, getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints()));
updateAdPlaybackState();
} catch (RuntimeException e) {
maybeNotifyInternalError("onAdsManagerLoaded", e);
}
}

View File

@ -44,6 +44,7 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
@ -57,6 +58,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@ -371,12 +373,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
private final ImaUtil.Configuration configuration;
private final Context context;
private final ImaUtil.ImaFactory imaFactory;
private final HashMap<Object, AdTagLoader> adTagLoaderByAdsId;
private final HashMap<AdsMediaSource, AdTagLoader> adTagLoaderByAdsMediaSource;
private final Timeline.Period period;
private final Timeline.Window window;
private boolean wasSetPlayerCalled;
@Nullable private Player nextPlayer;
@Nullable private AdTagLoader adTagLoader;
private List<String> supportedMimeTypes;
@Nullable private Player player;
@Nullable private AdTagLoader currentAdTagLoader;
private ImaAdsLoader(
Context context, ImaUtil.Configuration configuration, ImaUtil.ImaFactory imaFactory) {
@ -384,6 +390,10 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
this.configuration = configuration;
this.imaFactory = imaFactory;
supportedMimeTypes = ImmutableList.of();
adTagLoaderByAdsId = new HashMap<>();
adTagLoaderByAdsMediaSource = new HashMap<>();
period = new Timeline.Period();
window = new Timeline.Window();
}
/**
@ -394,7 +404,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
@SuppressWarnings("nullness:nullness.on.outer")
@Nullable
public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() {
return adTagLoader != null ? adTagLoader.getAdsLoader() : null;
return currentAdTagLoader != null ? currentAdTagLoader.getAdsLoader() : null;
}
/**
@ -410,7 +420,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
*/
@Nullable
public AdDisplayContainer getAdDisplayContainer() {
return adTagLoader != null ? adTagLoader.getAdDisplayContainer() : null;
return currentAdTagLoader != null ? currentAdTagLoader.getAdDisplayContainer() : null;
}
/**
@ -427,8 +437,8 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
* null} if playing audio-only ads.
*/
public void requestAds(DataSpec adTagDataSpec, Object adsId, @Nullable ViewGroup adViewGroup) {
if (adTagLoader == null) {
adTagLoader =
if (!adTagLoaderByAdsId.containsKey(adsId)) {
AdTagLoader adTagLoader =
new AdTagLoader(
context,
configuration,
@ -437,6 +447,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
adTagDataSpec,
adsId,
adViewGroup);
adTagLoaderByAdsId.put(adsId, adTagLoader);
}
}
@ -448,8 +459,8 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
* IMA SDK provides the UI to skip ads in the ad view group passed via {@link AdViewProvider}.
*/
public void skipAd() {
if (adTagLoader != null) {
adTagLoader.skipAd();
if (currentAdTagLoader != null) {
currentAdTagLoader.skipAd();
}
}
@ -494,37 +505,67 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
EventListener eventListener) {
checkState(
wasSetPlayerCalled, "Set player using adsLoader.setPlayer before preparing the player.");
player = nextPlayer;
@Nullable Player player = this.player;
if (player == null) {
return;
if (adTagLoaderByAdsMediaSource.isEmpty()) {
player = nextPlayer;
@Nullable Player player = this.player;
if (player == null) {
return;
}
player.addListener(this);
}
@Nullable AdTagLoader adTagLoader = adTagLoaderByAdsId.get(adsId);
if (adTagLoader == null) {
requestAds(adTagDataSpec, adsId, adViewProvider.getAdViewGroup());
adTagLoader = adTagLoaderByAdsId.get(adsId);
}
checkNotNull(adTagLoader).start(player, adViewProvider, eventListener);
adTagLoaderByAdsMediaSource.put(adsMediaSource, checkNotNull(adTagLoader));
checkNotNull(adTagLoader).start(adViewProvider, eventListener);
maybeUpdateCurrentAdTagLoader();
}
@Override
public void stop(AdsMediaSource adsMediaSource) {
if (player != null && adTagLoader != null) {
adTagLoader.stop();
@Nullable AdTagLoader removedAdTagLoader = adTagLoaderByAdsMediaSource.remove(adsMediaSource);
maybeUpdateCurrentAdTagLoader();
if (removedAdTagLoader != null) {
removedAdTagLoader.stop();
}
if (player != null && adTagLoaderByAdsMediaSource.isEmpty()) {
player.removeListener(this);
player = null;
}
}
@Override
public void release() {
if (adTagLoader != null) {
if (player != null) {
player.removeListener(this);
player = null;
maybeUpdateCurrentAdTagLoader();
}
nextPlayer = null;
for (AdTagLoader adTagLoader : adTagLoaderByAdsMediaSource.values()) {
adTagLoader.release();
}
adTagLoaderByAdsMediaSource.clear();
for (AdTagLoader adTagLoader : adTagLoaderByAdsId.values()) {
adTagLoader.release();
}
adTagLoaderByAdsId.clear();
}
@Override
public void handlePrepareComplete(
AdsMediaSource adsMediaSource, int adGroupIndex, int adIndexInAdGroup) {
if (adTagLoader != null) {
adTagLoader.handlePrepareComplete(adGroupIndex, adIndexInAdGroup);
if (player == null) {
return;
}
checkNotNull(adTagLoaderByAdsMediaSource.get(adsMediaSource))
.handlePrepareComplete(adGroupIndex, adIndexInAdGroup);
}
@Override
@ -533,9 +574,112 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
int adGroupIndex,
int adIndexInAdGroup,
IOException exception) {
if (adTagLoader != null) {
adTagLoader.handlePrepareError(adGroupIndex, adIndexInAdGroup, exception);
if (player == null) {
return;
}
checkNotNull(adTagLoaderByAdsMediaSource.get(adsMediaSource))
.handlePrepareError(adGroupIndex, adIndexInAdGroup, exception);
}
// Player.EventListener implementation.
@Override
public void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) {
if (timeline.isEmpty()) {
// The player is being reset or contains no media.
return;
}
maybeUpdateCurrentAdTagLoader();
maybePreloadNextPeriodAds();
}
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
maybeUpdateCurrentAdTagLoader();
maybePreloadNextPeriodAds();
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
maybePreloadNextPeriodAds();
}
@Override
public void onRepeatModeChanged(@Player.RepeatMode int repeatMode) {
maybePreloadNextPeriodAds();
}
// Internal methods.
private void maybeUpdateCurrentAdTagLoader() {
@Nullable AdTagLoader oldAdTagLoader = currentAdTagLoader;
@Nullable AdTagLoader newAdTagLoader = getCurrentAdTagLoader();
if (!Util.areEqual(oldAdTagLoader, newAdTagLoader)) {
if (oldAdTagLoader != null) {
oldAdTagLoader.deactivate();
}
currentAdTagLoader = newAdTagLoader;
if (newAdTagLoader != null) {
newAdTagLoader.activate(checkNotNull(player));
}
}
}
@Nullable
private AdTagLoader getCurrentAdTagLoader() {
@Nullable Player player = this.player;
if (player == null) {
return null;
}
Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) {
return null;
}
int periodIndex = player.getCurrentPeriodIndex();
@Nullable Object adsId = timeline.getPeriod(periodIndex, period).getAdsId();
if (adsId == null) {
return null;
}
@Nullable AdTagLoader adTagLoader = adTagLoaderByAdsId.get(adsId);
if (adTagLoader == null || !adTagLoaderByAdsMediaSource.containsValue(adTagLoader)) {
return null;
}
return adTagLoader;
}
private void maybePreloadNextPeriodAds() {
@Nullable Player player = this.player;
if (player == null) {
return;
}
Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) {
return;
}
int nextPeriodIndex =
timeline.getNextPeriodIndex(
player.getCurrentPeriodIndex(),
period,
window,
player.getRepeatMode(),
player.getShuffleModeEnabled());
if (nextPeriodIndex == C.INDEX_UNSET) {
return;
}
timeline.getPeriod(nextPeriodIndex, period);
@Nullable Object nextAdsId = period.getAdsId();
if (nextAdsId == null) {
return;
}
@Nullable AdTagLoader nextAdTagLoader = adTagLoaderByAdsId.get(nextAdsId);
if (nextAdTagLoader == null || nextAdTagLoader == currentAdTagLoader) {
return;
}
long periodPositionUs =
timeline.getPeriodPosition(
window, period, period.windowIndex, /* windowPositionUs= */ C.TIME_UNSET)
.second;
nextAdTagLoader.maybePreloadAds(C.usToMs(periodPositionUs), C.usToMs(period.durationUs));
}
/**

View File

@ -21,25 +21,30 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.testutil.StubExoPlayer;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import java.util.ArrayList;
import com.google.android.exoplayer2.util.ListenerSet;
/** A fake player for testing content/ad playback. */
/* package */ final class FakePlayer extends StubExoPlayer {
private final ArrayList<Player.EventListener> listeners;
private final ListenerSet<EventListener, Events> listeners;
private final Timeline.Period period;
private final Timeline timeline;
private Timeline timeline;
@Player.State private int state;
private boolean playWhenReady;
private long position;
private long contentPosition;
private int periodIndex;
private long positionMs;
private long contentPositionMs;
private boolean isPlayingAd;
private int adGroupIndex;
private int adIndexInAdGroup;
public FakePlayer() {
listeners = new ArrayList<>();
listeners =
new ListenerSet<>(
Looper.getMainLooper(),
Player.Events::new,
(listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags));
period = new Timeline.Period();
state = Player.STATE_IDLE;
playWhenReady = true;
@ -48,26 +53,27 @@ import java.util.ArrayList;
/** Sets the timeline on this fake player, which notifies listeners with the changed timeline. */
public void updateTimeline(Timeline timeline, @TimelineChangeReason int reason) {
for (Player.EventListener listener : listeners) {
listener.onTimelineChanged(timeline, reason);
}
this.timeline = timeline;
listeners.sendEvent(
Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(timeline, reason));
}
/**
* Sets the state of this player as if it were playing content at the given {@code position}. If
* an ad is currently playing, this will trigger a position discontinuity.
*/
public void setPlayingContentPosition(long position) {
public void setPlayingContentPosition(int periodIndex, long positionMs) {
boolean notify = isPlayingAd;
isPlayingAd = false;
adGroupIndex = C.INDEX_UNSET;
adIndexInAdGroup = C.INDEX_UNSET;
this.position = position;
contentPosition = position;
this.periodIndex = periodIndex;
this.positionMs = positionMs;
contentPositionMs = positionMs;
if (notify) {
for (Player.EventListener listener : listeners) {
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION);
}
listeners.sendEvent(
Player.EVENT_POSITION_DISCONTINUITY,
listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION));
}
}
@ -77,17 +83,22 @@ import java.util.ArrayList;
* position discontinuity.
*/
public void setPlayingAdPosition(
int adGroupIndex, int adIndexInAdGroup, long position, long contentPosition) {
int periodIndex,
int adGroupIndex,
int adIndexInAdGroup,
long positionMs,
long contentPositionMs) {
boolean notify = !isPlayingAd || this.adIndexInAdGroup != adIndexInAdGroup;
isPlayingAd = true;
this.periodIndex = periodIndex;
this.adGroupIndex = adGroupIndex;
this.adIndexInAdGroup = adIndexInAdGroup;
this.position = position;
this.contentPosition = contentPosition;
this.positionMs = positionMs;
this.contentPositionMs = contentPositionMs;
if (notify) {
for (Player.EventListener listener : listeners) {
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION);
}
listeners.sendEvent(
EVENT_POSITION_DISCONTINUITY,
listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION));
}
}
@ -99,16 +110,18 @@ import java.util.ArrayList;
this.state = state;
this.playWhenReady = playWhenReady;
if (playbackStateChanged || playWhenReadyChanged) {
for (Player.EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, state);
if (playbackStateChanged) {
listener.onPlaybackStateChanged(state);
}
if (playWhenReadyChanged) {
listener.onPlayWhenReadyChanged(
playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
}
}
listeners.sendEvent(
Player.EVENT_PLAYBACK_STATE_CHANGED,
listener -> {
listener.onPlayerStateChanged(playWhenReady, state);
if (playbackStateChanged) {
listener.onPlaybackStateChanged(state);
}
if (playWhenReadyChanged) {
listener.onPlayWhenReadyChanged(
playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
}
});
}
}
@ -145,6 +158,17 @@ import java.util.ArrayList;
return playWhenReady;
}
@Override
@RepeatMode
public int getRepeatMode() {
return REPEAT_MODE_OFF;
}
@Override
public boolean getShuffleModeEnabled() {
return false;
}
@Override
public int getRendererCount() {
return 0;
@ -162,7 +186,7 @@ import java.util.ArrayList;
@Override
public int getCurrentPeriodIndex() {
return 0;
return periodIndex;
}
@Override
@ -186,7 +210,7 @@ import java.util.ArrayList;
@Override
public long getCurrentPosition() {
return position;
return positionMs;
}
@Override
@ -206,6 +230,6 @@ import java.util.ArrayList;
@Override
public long getContentPosition() {
return contentPosition;
return contentPositionMs;
}
}

View File

@ -53,18 +53,15 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.ext.ima.ImaUtil.ImaFactory;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MaskingMediaSource.PlaceholderTimeline;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException;
import com.google.android.exoplayer2.source.ads.SinglePeriodAdTimeline;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
@ -133,12 +130,13 @@ public final class ImaAdsLoaderTest {
private ContentProgressProvider contentProgressProvider;
private VideoAdPlayer videoAdPlayer;
private TestAdsLoaderListener adsLoaderListener;
private FakePlayer fakeExoPlayer;
private FakePlayer fakePlayer;
private ImaAdsLoader imaAdsLoader;
@Before
public void setUp() {
setupMocks();
fakePlayer = new FakePlayer();
adViewGroup = new FrameLayout(getApplicationContext());
View adOverlayView = new View(getApplicationContext());
adViewProvider =
@ -166,19 +164,32 @@ public final class ImaAdsLoaderTest {
return ImmutableList.of();
}
};
imaAdsLoader =
new ImaAdsLoader.Builder(getApplicationContext())
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build();
imaAdsLoader.setPlayer(fakePlayer);
adsMediaSource =
new AdsMediaSource(
new FakeMediaSource(CONTENT_TIMELINE),
TEST_DATA_SPEC,
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adsLoaderListener = new TestAdsLoaderListener(getInitialTimelineWindowDefinition(TEST_ADS_ID));
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
}
@After
public void teardown() {
if (imaAdsLoader != null) {
imaAdsLoader.release();
}
imaAdsLoader.release();
}
@Test
public void builder_overridesPlayerType() {
public void loader_overridesCustomPlayerType() {
when(mockImaSdkSettings.getPlayerType()).thenReturn("test player type");
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -187,7 +198,6 @@ public final class ImaAdsLoaderTest {
@Test
public void start_setsAdUiViewGroup() {
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -198,7 +208,6 @@ public final class ImaAdsLoaderTest {
@Test
public void startForAudioOnlyAds_createsAudioOnlyAdDisplayContainer() {
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, audioAdsAdViewProvider, adsLoaderListener);
@ -210,8 +219,11 @@ public final class ImaAdsLoaderTest {
@Test
public void start_withPlaceholderContent_initializedAdsLoader() {
Timeline placeholderTimeline = new PlaceholderTimeline(MediaItem.fromUri(Uri.EMPTY));
setupPlayback(placeholderTimeline, PREROLL_CUE_POINTS_SECONDS);
adsLoaderListener =
new TestAdsLoaderListener(
getInitialTimelineWindowDefinition(TEST_ADS_ID, /* isPlaceholder= */ true));
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -221,11 +233,10 @@ public final class ImaAdsLoaderTest {
@Test
public void start_updatesAdPlaybackState() {
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
@ -233,7 +244,6 @@ public final class ImaAdsLoaderTest {
@Test
public void startAfterRelease() {
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.release();
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -241,14 +251,13 @@ 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(TEST_DATA_SPEC, TEST_ADS_ID, adViewGroup);
imaAdsLoader.release();
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
fakeExoPlayer.setPlayingContentPosition(/* position= */ 0);
fakeExoPlayer.setState(Player.STATE_READY, true);
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, /* positionMs= */ 0);
fakePlayer.setState(Player.STATE_READY, true);
// If callbacks are invoked there is no crash.
// Note: we can't currently call getContentProgress/getAdProgress as a VerifyError is thrown
@ -271,8 +280,6 @@ public final class ImaAdsLoaderTest {
@Test
public void playback_withPrerollAd_marksAdAsPlayed() {
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
// Load the preroll ad.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -282,24 +289,25 @@ public final class ImaAdsLoaderTest {
// Play the preroll ad.
videoAdPlayer.playAd(TEST_AD_MEDIA_INFO);
fakeExoPlayer.setPlayingAdPosition(
fakePlayer.setPlayingAdPosition(
/* periodIndex= */ 0,
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* position= */ 0,
/* contentPosition= */ 0);
fakeExoPlayer.setState(Player.STATE_READY, true);
fakePlayer.setState(Player.STATE_READY, true);
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);
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, /* positionMs= */ 0);
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)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -316,14 +324,14 @@ public final class ImaAdsLoaderTest {
when(mockMidrollFetchErrorAdEvent.getType()).thenReturn(AdEventType.AD_BREAK_FETCH_ERROR);
when(mockMidrollFetchErrorAdEvent.getAdData())
.thenReturn(ImmutableMap.of("adBreakTime", "20.5"));
setupPlayback(CONTENT_TIMELINE, ImmutableList.of(20.5f));
when(mockAdsManager.getAdCuePoints()).thenReturn(ImmutableList.of(20.5f));
// Simulate loading an empty midroll ad.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
adEventListener.onAdEvent(mockMidrollFetchErrorAdEvent);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 20_500_000)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -338,7 +346,7 @@ public final class ImaAdsLoaderTest {
when(mockMidrollFetchErrorAdEvent.getType()).thenReturn(AdEventType.AD_BREAK_FETCH_ERROR);
when(mockMidrollFetchErrorAdEvent.getAdData())
.thenReturn(ImmutableMap.of("adBreakTime", "5.5"));
setupPlayback(CONTENT_TIMELINE, ImmutableList.of(5.5f));
when(mockAdsManager.getAdCuePoints()).thenReturn(ImmutableList.of(5.5f));
// Simulate loading an empty midroll ad and advancing the player position.
imaAdsLoader.start(
@ -349,7 +357,7 @@ public final class ImaAdsLoaderTest {
playerPositionUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
long periodDurationUs =
CONTENT_TIMELINE.getPeriod(/* periodIndex= */ 0, new Period()).durationUs;
fakeExoPlayer.setPlayingContentPosition(C.usToMs(playerPositionUs));
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(playerPositionUs));
// Verify the content progress is updated to reflect the new player position.
assertThat(contentProgressProvider.getContentProgress())
@ -364,14 +372,14 @@ public final class ImaAdsLoaderTest {
when(mockPostrollFetchErrorAdEvent.getType()).thenReturn(AdEventType.AD_BREAK_FETCH_ERROR);
when(mockPostrollFetchErrorAdEvent.getAdData())
.thenReturn(ImmutableMap.of("adBreakTime", "-1"));
setupPlayback(CONTENT_TIMELINE, ImmutableList.of(-1f));
when(mockAdsManager.getAdCuePoints()).thenReturn(ImmutableList.of(-1f));
// Simulate loading an empty postroll ad.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
adEventListener.onAdEvent(mockPostrollFetchErrorAdEvent);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -388,18 +396,18 @@ public final class ImaAdsLoaderTest {
adGroupPositionInWindowUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
// Advance playback to just before the midroll and simulate buffering.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(adGroupPositionInWindowUs));
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs));
fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
// Advance before the timeout and simulating polling content progress.
ShadowSystemClock.advanceBy(Duration.ofSeconds(1));
contentProgressProvider.getContentProgress();
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
@ -413,18 +421,18 @@ public final class ImaAdsLoaderTest {
adGroupPositionInWindowUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
// Advance playback to just before the midroll and simulate buffering.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(adGroupPositionInWindowUs));
fakeExoPlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs));
fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
// Advance past the timeout and simulate polling content progress.
ShadowSystemClock.advanceBy(Duration.ofSeconds(5));
contentProgressProvider.getContentProgress();
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -440,14 +448,15 @@ public final class ImaAdsLoaderTest {
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) - 1_000);
fakePlayer.setPlayingContentPosition(
/* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs) - 1_000);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
verify(mockAdsRenderingSettings, never()).setPlayAdsAfterTime(anyDouble());
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
@ -460,9 +469,9 @@ public final class ImaAdsLoaderTest {
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs));
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs));
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -472,7 +481,7 @@ public final class ImaAdsLoaderTest {
assertThat(playAdsAfterTimeCaptor.getValue())
.isWithin(0.1)
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -486,9 +495,10 @@ public final class ImaAdsLoaderTest {
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) + 1_000);
fakePlayer.setPlayingContentPosition(
/* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs) + 1_000);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -498,7 +508,7 @@ public final class ImaAdsLoaderTest {
assertThat(playAdsAfterTimeCaptor.getValue())
.isWithin(0.1)
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -519,14 +529,15 @@ public final class ImaAdsLoaderTest {
ImmutableList.of(
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs) - 1_000);
fakePlayer.setPlayingContentPosition(
/* periodIndex= */ 0, C.usToMs(secondMidrollWindowTimeUs) - 1_000);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
verify(mockAdsRenderingSettings, never()).setPlayAdsAfterTime(anyDouble());
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
@ -546,9 +557,9 @@ public final class ImaAdsLoaderTest {
ImmutableList.of(
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(CONTENT_TIMELINE, cuePoints);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs));
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(secondMidrollWindowTimeUs));
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -558,7 +569,7 @@ public final class ImaAdsLoaderTest {
assertThat(playAdsAfterTimeCaptor.getValue())
.isWithin(0.1)
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -567,23 +578,30 @@ public final class ImaAdsLoaderTest {
@Test
public void resumePlaybackBeforeMidroll_withoutPlayAdBeforeStartPosition_skipsPreroll() {
imaAdsLoader =
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build();
imaAdsLoader.setPlayer(fakePlayer);
adsMediaSource =
new AdsMediaSource(
new FakeMediaSource(CONTENT_TIMELINE),
TEST_DATA_SPEC,
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
cuePoints,
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build(),
TEST_DATA_SPEC,
TEST_ADS_ID);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) - 1_000);
fakePlayer.setPlayingContentPosition(
/* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs) - 1_000);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -593,7 +611,7 @@ public final class ImaAdsLoaderTest {
assertThat(playAdsAfterTimeCaptor.getValue())
.isWithin(0.1d)
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withSkippedAdGroup(/* adGroupIndex= */ 0)
@ -602,23 +620,29 @@ public final class ImaAdsLoaderTest {
@Test
public void resumePlaybackAtMidroll_withoutPlayAdBeforeStartPosition_skipsPreroll() {
imaAdsLoader =
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build();
imaAdsLoader.setPlayer(fakePlayer);
adsMediaSource =
new AdsMediaSource(
new FakeMediaSource(CONTENT_TIMELINE),
TEST_DATA_SPEC,
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
cuePoints,
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build(),
TEST_DATA_SPEC,
TEST_ADS_ID);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs));
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs));
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -628,7 +652,7 @@ public final class ImaAdsLoaderTest {
assertThat(playAdsAfterTimeCaptor.getValue())
.isWithin(0.1d)
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -637,28 +661,35 @@ public final class ImaAdsLoaderTest {
@Test
public void resumePlaybackAfterMidroll_withoutPlayAdBeforeStartPosition_skipsMidroll() {
imaAdsLoader =
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build();
imaAdsLoader.setPlayer(fakePlayer);
adsMediaSource =
new AdsMediaSource(
new FakeMediaSource(CONTENT_TIMELINE),
TEST_DATA_SPEC,
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
long midrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long midrollPeriodTimeUs =
midrollWindowTimeUs + TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints =
ImmutableList.of(0f, (float) midrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
cuePoints,
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build(),
TEST_DATA_SPEC,
TEST_ADS_ID);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(midrollWindowTimeUs) + 1_000);
fakePlayer.setPlayingContentPosition(
/* periodIndex= */ 0, C.usToMs(midrollWindowTimeUs) + 1_000);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
verify(mockAdsManager).destroy();
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -669,6 +700,21 @@ public final class ImaAdsLoaderTest {
@Test
public void
resumePlaybackBeforeSecondMidroll_withoutPlayAdBeforeStartPosition_skipsFirstMidroll() {
imaAdsLoader =
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build();
imaAdsLoader.setPlayer(fakePlayer);
adsMediaSource =
new AdsMediaSource(
new FakeMediaSource(CONTENT_TIMELINE),
TEST_DATA_SPEC,
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
long firstMidrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long firstMidrollPeriodTimeUs =
firstMidrollWindowTimeUs
@ -681,18 +727,10 @@ public final class ImaAdsLoaderTest {
ImmutableList.of(
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
cuePoints,
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build(),
TEST_DATA_SPEC,
TEST_ADS_ID);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs) - 1_000);
fakePlayer.setPlayingContentPosition(
/* periodIndex= */ 0, C.usToMs(secondMidrollWindowTimeUs) - 1_000);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -702,7 +740,7 @@ public final class ImaAdsLoaderTest {
assertThat(playAdsAfterTimeCaptor.getValue())
.isWithin(0.1d)
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withSkippedAdGroup(/* adGroupIndex= */ 0)
@ -711,6 +749,21 @@ public final class ImaAdsLoaderTest {
@Test
public void resumePlaybackAtSecondMidroll_withoutPlayAdBeforeStartPosition_skipsFirstMidroll() {
imaAdsLoader =
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build();
imaAdsLoader.setPlayer(fakePlayer);
adsMediaSource =
new AdsMediaSource(
new FakeMediaSource(CONTENT_TIMELINE),
TEST_DATA_SPEC,
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
long firstMidrollWindowTimeUs = 2 * C.MICROS_PER_SECOND;
long firstMidrollPeriodTimeUs =
firstMidrollWindowTimeUs
@ -723,18 +776,9 @@ public final class ImaAdsLoaderTest {
ImmutableList.of(
(float) firstMidrollPeriodTimeUs / C.MICROS_PER_SECOND,
(float) secondMidrollPeriodTimeUs / C.MICROS_PER_SECOND);
setupPlayback(
CONTENT_TIMELINE,
cuePoints,
new ImaAdsLoader.Builder(getApplicationContext())
.setPlayAdBeforeStartPosition(false)
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build(),
TEST_DATA_SPEC,
TEST_ADS_ID);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakeExoPlayer.setPlayingContentPosition(C.usToMs(secondMidrollWindowTimeUs));
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(secondMidrollWindowTimeUs));
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -744,7 +788,7 @@ public final class ImaAdsLoaderTest {
assertThat(playAdsAfterTimeCaptor.getValue())
.isWithin(0.1d)
.of(expectedPlayAdsAfterTimeUs / C.MICROS_PER_SECOND);
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -762,16 +806,6 @@ public final class ImaAdsLoaderTest {
+ " </Ad>\n"
+ "</VAST>";
DataSpec adDataSpec = new DataSpec(Util.getDataUriForString("text/xml", adsResponse));
setupPlayback(
CONTENT_TIMELINE,
ImmutableList.of(0f),
new ImaAdsLoader.Builder(getApplicationContext())
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build(),
adDataSpec,
TEST_ADS_ID);
imaAdsLoader.start(adsMediaSource, adDataSpec, TEST_ADS_ID, adViewProvider, adsLoaderListener);
verify(mockAdsRequest).setAdsResponse(adsResponse);
@ -779,15 +813,6 @@ public final class ImaAdsLoaderTest {
@Test
public void requestAdTagWithUri_requestsWithAdTagUrl() throws Exception {
setupPlayback(
CONTENT_TIMELINE,
ImmutableList.of(0f),
new ImaAdsLoader.Builder(getApplicationContext())
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build(),
TEST_DATA_SPEC,
TEST_ADS_ID);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -796,7 +821,6 @@ public final class ImaAdsLoaderTest {
@Test
public void setsDefaultMimeTypes() throws Exception {
setupPlayback(CONTENT_TIMELINE, ImmutableList.of(0f));
imaAdsLoader.setSupportedContentTypes(C.TYPE_DASH, C.TYPE_OTHER);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -814,16 +838,23 @@ public final class ImaAdsLoaderTest {
@Test
public void buildWithAdMediaMimeTypes_setsMimeTypes() throws Exception {
setupPlayback(
CONTENT_TIMELINE,
ImmutableList.of(0f),
imaAdsLoader =
new ImaAdsLoader.Builder(getApplicationContext())
.setAdMediaMimeTypes(ImmutableList.of(MimeTypes.AUDIO_MPEG))
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.setAdMediaMimeTypes(ImmutableList.of(MimeTypes.AUDIO_MPEG))
.build(),
TEST_DATA_SPEC,
TEST_ADS_ID);
.build();
imaAdsLoader.setPlayer(fakePlayer);
adsMediaSource =
new AdsMediaSource(
new FakeMediaSource(CONTENT_TIMELINE),
TEST_DATA_SPEC,
TEST_ADS_ID,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.setSupportedContentTypes(C.TYPE_OTHER);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -833,7 +864,6 @@ public final class ImaAdsLoaderTest {
@Test
public void stop_unregistersAllVideoControlOverlays() {
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
imaAdsLoader.requestAds(TEST_DATA_SPEC, TEST_ADS_ID, adViewGroup);
@ -848,8 +878,9 @@ public final class ImaAdsLoaderTest {
public void loadAd_withLargeAdCuePoint_updatesAdPlaybackStateWithLoadedAd() {
// Use a large enough value to test correct truncating of large cue points.
float midrollTimeSecs = Float.MAX_VALUE;
ImmutableList<Float> cuePoints = ImmutableList.of(midrollTimeSecs);
setupPlayback(CONTENT_TIMELINE, cuePoints);
List<Float> cuePoints = ImmutableList.of(midrollTimeSecs);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
videoAdPlayer.loadAd(
@ -886,7 +917,7 @@ public final class ImaAdsLoaderTest {
}
});
assertThat(adsLoaderListener.adPlaybackState)
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
@ -895,37 +926,126 @@ public final class ImaAdsLoaderTest {
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}}));
}
private void setupPlayback(Timeline contentTimeline, List<Float> cuePoints) {
setupPlayback(
contentTimeline,
cuePoints,
new ImaAdsLoader.Builder(getApplicationContext())
.setImaFactory(mockImaFactory)
.setImaSdkSettings(mockImaSdkSettings)
.build(),
TEST_DATA_SPEC,
TEST_ADS_ID);
}
private void setupPlayback(
Timeline contentTimeline,
List<Float> cuePoints,
ImaAdsLoader imaAdsLoader,
DataSpec adTagDataSpec,
Object adsId) {
fakeExoPlayer = new FakePlayer();
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline);
adsMediaSource =
@Test
public void playbackWithTwoAdsMediaSources_preloadsSecondAdTag() {
Object secondAdsId = new Object();
AdsMediaSource secondAdsMediaSource =
new AdsMediaSource(
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
adTagDataSpec,
adsId,
new FakeMediaSource(CONTENT_TIMELINE),
TEST_DATA_SPEC,
secondAdsId,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
this.imaAdsLoader = imaAdsLoader;
imaAdsLoader.setPlayer(fakeExoPlayer);
adsLoaderListener =
new TestAdsLoaderListener(
getInitialTimelineWindowDefinition(TEST_ADS_ID),
getInitialTimelineWindowDefinition(secondAdsId));
// Load and play the preroll ad then content.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
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);
fakePlayer.setPlayingAdPosition(
/* periodIndex= */ 0,
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* position= */ 0,
/* contentPosition= */ 0);
fakePlayer.setState(Player.STATE_READY, true);
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));
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, /* positionMs= */ 0);
videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
// Simulate starting to buffer the second ads media source.
imaAdsLoader.start(
secondAdsMediaSource, TEST_DATA_SPEC, secondAdsId, adViewProvider, adsLoaderListener);
// Verify that the preroll ad has been marked as played.
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI)
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
.withAdResumePositionUs(/* adResumePositionUs= */ 0));
// Verify that the second source's ad cue points have preloaded.
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 1))
.isEqualTo(new AdPlaybackState(secondAdsId, /* adGroupTimesUs...= */ 0));
}
@Test
public void playbackWithTwoAdsMediaSources_preloadsSecondAdTagWithBackgroundResume() {
Object secondAdsId = new Object();
AdsMediaSource secondAdsMediaSource =
new AdsMediaSource(
new FakeMediaSource(CONTENT_TIMELINE),
TEST_DATA_SPEC,
secondAdsId,
new DefaultMediaSourceFactory((Context) getApplicationContext()),
imaAdsLoader,
adViewProvider);
adsLoaderListener =
new TestAdsLoaderListener(
getInitialTimelineWindowDefinition(TEST_ADS_ID),
getInitialTimelineWindowDefinition(secondAdsId));
// Load and play the preroll ad then content.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
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);
fakePlayer.setPlayingAdPosition(
/* periodIndex= */ 0,
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
/* position= */ 0,
/* contentPosition= */ 0);
fakePlayer.setState(Player.STATE_READY, true);
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));
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, /* positionMs= */ 0);
videoAdPlayer.stopAd(TEST_AD_MEDIA_INFO);
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
// Simulate starting to buffer the second ads media source.
imaAdsLoader.start(
secondAdsMediaSource, TEST_DATA_SPEC, secondAdsId, adViewProvider, adsLoaderListener);
// Simulate backgrounding/resuming.
imaAdsLoader.stop(adsMediaSource);
imaAdsLoader.stop(secondAdsMediaSource);
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
imaAdsLoader.start(
secondAdsMediaSource, TEST_DATA_SPEC, secondAdsId, adViewProvider, adsLoaderListener);
// Verify that the preroll ad has been marked as played.
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0)
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI)
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
.withAdResumePositionUs(/* adResumePositionUs= */ 0));
// Verify that the second source's ad cue points have preloaded.
assertThat(adsLoaderListener.getAdPlaybackState(/* periodIndex= */ 1))
.isEqualTo(new AdPlaybackState(secondAdsId, /* adGroupTimesUs...= */ 0));
}
private void setupMocks() {
@ -1023,16 +1143,16 @@ public final class ImaAdsLoaderTest {
}
/** Ad loader event listener that forwards ad playback state to a fake player. */
private static final class TestAdsLoaderListener implements AdsLoader.EventListener {
private final class TestAdsLoaderListener implements AdsLoader.EventListener {
private final FakePlayer fakeExoPlayer;
private final Timeline contentTimeline;
private final TimelineWindowDefinition[] timelineWindowDefinitions;
public AdPlaybackState adPlaybackState;
public TestAdsLoaderListener(TimelineWindowDefinition... timelineWindowDefinitions) {
this.timelineWindowDefinitions = timelineWindowDefinitions;
}
public TestAdsLoaderListener(FakePlayer fakeExoPlayer, Timeline contentTimeline) {
this.fakeExoPlayer = fakeExoPlayer;
this.contentTimeline = contentTimeline;
public AdPlaybackState getAdPlaybackState(int periodIndex) {
return timelineWindowDefinitions[periodIndex].adPlaybackState;
}
@Override
@ -1043,10 +1163,28 @@ public final class ImaAdsLoaderTest {
Arrays.fill(adDurationsUs[adGroupIndex], TEST_AD_DURATION_US);
}
adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs);
this.adPlaybackState = adPlaybackState;
fakeExoPlayer.updateTimeline(
new SinglePeriodAdTimeline(contentTimeline, adPlaybackState),
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
// Update the timeline window definition(s) to reflect the new ad playback state.
for (int i = 0; i < timelineWindowDefinitions.length; i++) {
TimelineWindowDefinition timelineWindowDefinition = timelineWindowDefinitions[i];
if (!Util.areEqual(timelineWindowDefinition.adPlaybackState.adsId, adPlaybackState.adsId)) {
continue;
}
timelineWindowDefinitions[i] =
new TimelineWindowDefinition(
timelineWindowDefinition.periodCount,
timelineWindowDefinition.id,
timelineWindowDefinition.isSeekable,
timelineWindowDefinition.isDynamic,
timelineWindowDefinition.isLive,
timelineWindowDefinition.isPlaceholder,
timelineWindowDefinition.durationUs,
timelineWindowDefinition.defaultPositionUs,
timelineWindowDefinition.windowOffsetInFirstPeriodUs,
adPlaybackState);
}
fakePlayer.updateTimeline(
new FakeTimeline(timelineWindowDefinitions), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
}
@Override
@ -1064,4 +1202,24 @@ public final class ImaAdsLoaderTest {
// Do nothing.
}
}
private static TimelineWindowDefinition getInitialTimelineWindowDefinition(Object adsId) {
return getInitialTimelineWindowDefinition(adsId, /* isPlaceholder= */ false);
}
private static TimelineWindowDefinition getInitialTimelineWindowDefinition(
Object adsId, boolean isPlaceholder) {
return new TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ new Object(),
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false,
/* isPlaceholder= */ isPlaceholder,
/* durationUs= */ CONTENT_DURATION_US,
/* defaultPositionUs= */ 0,
/* windowOffsetInFirstPeriodUs= */ TimelineWindowDefinition
.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
new AdPlaybackState(adsId));
}
}