Commit 05f6d248 by andrewlewis Committed by kim-vde

Add support for ad playlists with `ImaAdsLoader`

Issue: #3750
PiperOrigin-RevId: 343878310
parent 689e89e5
...@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.ima; ...@@ -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.BITRATE_UNSET;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.TIMEOUT_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.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.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import static java.lang.Math.max; import static java.lang.Math.max;
...@@ -280,11 +280,11 @@ import java.util.Map; ...@@ -280,11 +280,11 @@ import java.util.Map;
} }
} }
/** Starts using the ads loader for playback. */ /**
public void start(Player player, AdViewProvider adViewProvider, EventListener eventListener) { * Starts passing events from this instance (including any pending ad playback state) and
this.player = player; * registers obstructions.
player.addListener(this); */
boolean playWhenReady = player.getPlayWhenReady(); public void start(AdViewProvider adViewProvider, EventListener eventListener) {
this.eventListener = eventListener; this.eventListener = eventListener;
lastVolumePercent = 0; lastVolumePercent = 0;
lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY; lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
...@@ -293,13 +293,9 @@ import java.util.Map; ...@@ -293,13 +293,9 @@ import java.util.Map;
if (!AdPlaybackState.NONE.equals(adPlaybackState)) { if (!AdPlaybackState.NONE.equals(adPlaybackState)) {
// Pass the ad playback state to the player, and resume ads if necessary. // Pass the ad playback state to the player, and resume ads if necessary.
eventListener.onAdPlaybackState(adPlaybackState); eventListener.onAdPlaybackState(adPlaybackState);
if (adsManager != null && imaPausedContent && playWhenReady) {
adsManager.resume();
}
} else if (adsManager != null) { } else if (adsManager != null) {
adPlaybackState = adPlaybackState =
new AdPlaybackState( new AdPlaybackState(adsId, getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints()));
adsId, ImaUtil.getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints()));
updateAdPlaybackState(); updateAdPlaybackState();
} }
for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) { for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) {
...@@ -311,14 +307,36 @@ import java.util.Map; ...@@ -311,14 +307,36 @@ import java.util.Map;
} }
} }
/** Stops using the ads loader for playback. */ /**
public void stop() { * Populates the ad playback state with loaded cue points, if available. Any preroll will be
@Nullable Player player = this.player; * paused immediately while waiting for this instance to be {@link #activate(Player) activated}.
if (player == null) { */
return; 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 =
adPlaybackState.withAdResumePositionUs( adPlaybackState.withAdResumePositionUs(
playingAd ? C.msToUs(player.getCurrentPosition()) : 0); playingAd ? C.msToUs(player.getCurrentPosition()) : 0);
...@@ -326,10 +344,15 @@ import java.util.Map; ...@@ -326,10 +344,15 @@ import java.util.Map;
lastVolumePercent = getPlayerVolumePercent(); lastVolumePercent = getPlayerVolumePercent();
lastAdProgress = getAdVideoProgressUpdate(); lastAdProgress = getAdVideoProgressUpdate();
lastContentProgress = getContentVideoProgressUpdate(); lastContentProgress = getContentVideoProgressUpdate();
adDisplayContainer.unregisterAllFriendlyObstructions();
player.removeListener(this); player.removeListener(this);
this.player = null; this.player = null;
}
/** Stops passing of events from this instance and unregisters obstructions. */
public void stop() {
eventListener = null; eventListener = null;
adDisplayContainer.unregisterAllFriendlyObstructions();
} }
/** Releases all resources used by the ad tag loader. */ /** Releases all resources used by the ad tag loader. */
...@@ -392,7 +415,6 @@ import java.util.Map; ...@@ -392,7 +415,6 @@ import java.util.Map;
// The player is being reset or contains no media. // The player is being reset or contains no media.
return; return;
} }
checkArgument(timeline.getPeriodCount() == 1);
this.timeline = timeline; this.timeline = timeline;
Player player = checkNotNull(this.player); Player player = checkNotNull(this.player);
long contentDurationUs = timeline.getPeriod(player.getCurrentPeriodIndex(), period).durationUs; long contentDurationUs = timeline.getPeriod(player.getCurrentPeriodIndex(), period).durationUs;
...@@ -592,14 +614,13 @@ import java.util.Map; ...@@ -592,14 +614,13 @@ import java.util.Map;
} }
private VideoProgressUpdate getContentVideoProgressUpdate() { private VideoProgressUpdate getContentVideoProgressUpdate() {
if (player == null) {
return lastContentProgress;
}
boolean hasContentDuration = contentDurationMs != C.TIME_UNSET; boolean hasContentDuration = contentDurationMs != C.TIME_UNSET;
long contentPositionMs; long contentPositionMs;
if (pendingContentPositionMs != C.TIME_UNSET) { if (pendingContentPositionMs != C.TIME_UNSET) {
sentPendingContentPositionMs = true; sentPendingContentPositionMs = true;
contentPositionMs = pendingContentPositionMs; contentPositionMs = pendingContentPositionMs;
} else if (player == null) {
return lastContentProgress;
} else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) { } else if (fakeContentProgressElapsedRealtimeMs != C.TIME_UNSET) {
long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs; long elapsedSinceEndMs = SystemClock.elapsedRealtime() - fakeContentProgressElapsedRealtimeMs;
contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs; contentPositionMs = fakeContentProgressOffsetMs + elapsedSinceEndMs;
...@@ -923,7 +944,8 @@ import java.util.Map; ...@@ -923,7 +944,8 @@ import java.util.Map;
adCallbacks.get(i).onResume(adMediaInfo); 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(); checkNotNull(adsManager).pause();
} }
} }
...@@ -941,7 +963,14 @@ import java.util.Map; ...@@ -941,7 +963,14 @@ import java.util.Map;
// to a different position, so drop the event. See also [Internal: b/159111848]. // to a different position, so drop the event. See also [Internal: b/159111848].
return; 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; imaAdState = IMA_AD_STATE_PAUSED;
for (int i = 0; i < adCallbacks.size(); i++) { for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onPause(adMediaInfo); adCallbacks.get(i).onPause(adMediaInfo);
...@@ -1157,9 +1186,13 @@ import java.util.Map; ...@@ -1157,9 +1186,13 @@ import java.util.Map;
throw new IllegalStateException("Failed to find cue point"); 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); @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( private static long getContentPeriodPositionMs(
...@@ -1226,16 +1259,12 @@ import java.util.Map; ...@@ -1226,16 +1259,12 @@ import java.util.Map;
if (configuration.applicationAdEventListener != null) { if (configuration.applicationAdEventListener != null) {
adsManager.addAdEventListener(configuration.applicationAdEventListener); adsManager.addAdEventListener(configuration.applicationAdEventListener);
} }
if (player != null) { try {
// If a player is attached already, start playback immediately. adPlaybackState =
try { new AdPlaybackState(adsId, getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints()));
adPlaybackState = updateAdPlaybackState();
new AdPlaybackState( } catch (RuntimeException e) {
adsId, ImaUtil.getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints())); maybeNotifyInternalError("onAdsManagerLoaded", e);
updateAdPlaybackState();
} catch (RuntimeException e) {
maybeNotifyInternalError("onAdsManagerLoaded", e);
}
} }
} }
......
...@@ -44,6 +44,7 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; ...@@ -44,6 +44,7 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Player; 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.MediaSourceFactory;
import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource;
...@@ -57,6 +58,7 @@ import java.util.ArrayList; ...@@ -57,6 +58,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
...@@ -371,12 +373,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader { ...@@ -371,12 +373,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
private final ImaUtil.Configuration configuration; private final ImaUtil.Configuration configuration;
private final Context context; private final Context context;
private final ImaUtil.ImaFactory imaFactory; 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; private boolean wasSetPlayerCalled;
@Nullable private Player nextPlayer; @Nullable private Player nextPlayer;
@Nullable private AdTagLoader adTagLoader;
private List<String> supportedMimeTypes; private List<String> supportedMimeTypes;
@Nullable private Player player; @Nullable private Player player;
@Nullable private AdTagLoader currentAdTagLoader;
private ImaAdsLoader( private ImaAdsLoader(
Context context, ImaUtil.Configuration configuration, ImaUtil.ImaFactory imaFactory) { Context context, ImaUtil.Configuration configuration, ImaUtil.ImaFactory imaFactory) {
...@@ -384,6 +390,10 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader { ...@@ -384,6 +390,10 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
this.configuration = configuration; this.configuration = configuration;
this.imaFactory = imaFactory; this.imaFactory = imaFactory;
supportedMimeTypes = ImmutableList.of(); 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 { ...@@ -394,7 +404,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
@SuppressWarnings("nullness:nullness.on.outer") @SuppressWarnings("nullness:nullness.on.outer")
@Nullable @Nullable
public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() { 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 { ...@@ -410,7 +420,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
*/ */
@Nullable @Nullable
public AdDisplayContainer getAdDisplayContainer() { 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 { ...@@ -427,8 +437,8 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
* null} if playing audio-only ads. * null} if playing audio-only ads.
*/ */
public void requestAds(DataSpec adTagDataSpec, Object adsId, @Nullable ViewGroup adViewGroup) { public void requestAds(DataSpec adTagDataSpec, Object adsId, @Nullable ViewGroup adViewGroup) {
if (adTagLoader == null) { if (!adTagLoaderByAdsId.containsKey(adsId)) {
adTagLoader = AdTagLoader adTagLoader =
new AdTagLoader( new AdTagLoader(
context, context,
configuration, configuration,
...@@ -437,6 +447,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader { ...@@ -437,6 +447,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
adTagDataSpec, adTagDataSpec,
adsId, adsId,
adViewGroup); adViewGroup);
adTagLoaderByAdsId.put(adsId, adTagLoader);
} }
} }
...@@ -448,8 +459,8 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader { ...@@ -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}. * IMA SDK provides the UI to skip ads in the ad view group passed via {@link AdViewProvider}.
*/ */
public void skipAd() { public void skipAd() {
if (adTagLoader != null) { if (currentAdTagLoader != null) {
adTagLoader.skipAd(); currentAdTagLoader.skipAd();
} }
} }
...@@ -494,37 +505,67 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader { ...@@ -494,37 +505,67 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
EventListener eventListener) { EventListener eventListener) {
checkState( checkState(
wasSetPlayerCalled, "Set player using adsLoader.setPlayer before preparing the player."); wasSetPlayerCalled, "Set player using adsLoader.setPlayer before preparing the player.");
player = nextPlayer; if (adTagLoaderByAdsMediaSource.isEmpty()) {
@Nullable Player player = this.player; player = nextPlayer;
if (player == null) { @Nullable Player player = this.player;
return; if (player == null) {
return;
}
player.addListener(this);
} }
@Nullable AdTagLoader adTagLoader = adTagLoaderByAdsId.get(adsId);
if (adTagLoader == null) { if (adTagLoader == null) {
requestAds(adTagDataSpec, adsId, adViewProvider.getAdViewGroup()); 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 @Override
public void stop(AdsMediaSource adsMediaSource) { public void stop(AdsMediaSource adsMediaSource) {
if (player != null && adTagLoader != null) { @Nullable AdTagLoader removedAdTagLoader = adTagLoaderByAdsMediaSource.remove(adsMediaSource);
adTagLoader.stop(); maybeUpdateCurrentAdTagLoader();
if (removedAdTagLoader != null) {
removedAdTagLoader.stop();
}
if (player != null && adTagLoaderByAdsMediaSource.isEmpty()) {
player.removeListener(this);
player = null;
} }
} }
@Override @Override
public void release() { 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(); adTagLoader.release();
} }
adTagLoaderByAdsId.clear();
} }
@Override @Override
public void handlePrepareComplete( public void handlePrepareComplete(
AdsMediaSource adsMediaSource, int adGroupIndex, int adIndexInAdGroup) { AdsMediaSource adsMediaSource, int adGroupIndex, int adIndexInAdGroup) {
if (adTagLoader != null) { if (player == null) {
adTagLoader.handlePrepareComplete(adGroupIndex, adIndexInAdGroup); return;
} }
checkNotNull(adTagLoaderByAdsMediaSource.get(adsMediaSource))
.handlePrepareComplete(adGroupIndex, adIndexInAdGroup);
} }
@Override @Override
...@@ -533,9 +574,112 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader { ...@@ -533,9 +574,112 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
int adGroupIndex, int adGroupIndex,
int adIndexInAdGroup, int adIndexInAdGroup,
IOException exception) { IOException exception) {
if (adTagLoader != null) { if (player == null) {
adTagLoader.handlePrepareError(adGroupIndex, adIndexInAdGroup, exception); 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));
} }
/** /**
......
...@@ -21,25 +21,30 @@ import com.google.android.exoplayer2.Player; ...@@ -21,25 +21,30 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.testutil.StubExoPlayer; import com.google.android.exoplayer2.testutil.StubExoPlayer;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 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. */ /** A fake player for testing content/ad playback. */
/* package */ final class FakePlayer extends StubExoPlayer { /* 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.Period period;
private final Timeline timeline;
private Timeline timeline;
@Player.State private int state; @Player.State private int state;
private boolean playWhenReady; private boolean playWhenReady;
private long position; private int periodIndex;
private long contentPosition; private long positionMs;
private long contentPositionMs;
private boolean isPlayingAd; private boolean isPlayingAd;
private int adGroupIndex; private int adGroupIndex;
private int adIndexInAdGroup; private int adIndexInAdGroup;
public FakePlayer() { public FakePlayer() {
listeners = new ArrayList<>(); listeners =
new ListenerSet<>(
Looper.getMainLooper(),
Player.Events::new,
(listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags));
period = new Timeline.Period(); period = new Timeline.Period();
state = Player.STATE_IDLE; state = Player.STATE_IDLE;
playWhenReady = true; playWhenReady = true;
...@@ -48,26 +53,27 @@ import java.util.ArrayList; ...@@ -48,26 +53,27 @@ import java.util.ArrayList;
/** Sets the timeline on this fake player, which notifies listeners with the changed timeline. */ /** Sets the timeline on this fake player, which notifies listeners with the changed timeline. */
public void updateTimeline(Timeline timeline, @TimelineChangeReason int reason) { public void updateTimeline(Timeline timeline, @TimelineChangeReason int reason) {
for (Player.EventListener listener : listeners) { this.timeline = timeline;
listener.onTimelineChanged(timeline, reason); 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 * 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. * 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; boolean notify = isPlayingAd;
isPlayingAd = false; isPlayingAd = false;
adGroupIndex = C.INDEX_UNSET; adGroupIndex = C.INDEX_UNSET;
adIndexInAdGroup = C.INDEX_UNSET; adIndexInAdGroup = C.INDEX_UNSET;
this.position = position; this.periodIndex = periodIndex;
contentPosition = position; this.positionMs = positionMs;
contentPositionMs = positionMs;
if (notify) { if (notify) {
for (Player.EventListener listener : listeners) { listeners.sendEvent(
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION); Player.EVENT_POSITION_DISCONTINUITY,
} listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION));
} }
} }
...@@ -77,17 +83,22 @@ import java.util.ArrayList; ...@@ -77,17 +83,22 @@ import java.util.ArrayList;
* position discontinuity. * position discontinuity.
*/ */
public void setPlayingAdPosition( 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; boolean notify = !isPlayingAd || this.adIndexInAdGroup != adIndexInAdGroup;
isPlayingAd = true; isPlayingAd = true;
this.periodIndex = periodIndex;
this.adGroupIndex = adGroupIndex; this.adGroupIndex = adGroupIndex;
this.adIndexInAdGroup = adIndexInAdGroup; this.adIndexInAdGroup = adIndexInAdGroup;
this.position = position; this.positionMs = positionMs;
this.contentPosition = contentPosition; this.contentPositionMs = contentPositionMs;
if (notify) { if (notify) {
for (Player.EventListener listener : listeners) { listeners.sendEvent(
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION); EVENT_POSITION_DISCONTINUITY,
} listener -> listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AD_INSERTION));
} }
} }
...@@ -99,16 +110,18 @@ import java.util.ArrayList; ...@@ -99,16 +110,18 @@ import java.util.ArrayList;
this.state = state; this.state = state;
this.playWhenReady = playWhenReady; this.playWhenReady = playWhenReady;
if (playbackStateChanged || playWhenReadyChanged) { if (playbackStateChanged || playWhenReadyChanged) {
for (Player.EventListener listener : listeners) { listeners.sendEvent(
listener.onPlayerStateChanged(playWhenReady, state); Player.EVENT_PLAYBACK_STATE_CHANGED,
if (playbackStateChanged) { listener -> {
listener.onPlaybackStateChanged(state); listener.onPlayerStateChanged(playWhenReady, state);
} if (playbackStateChanged) {
if (playWhenReadyChanged) { listener.onPlaybackStateChanged(state);
listener.onPlayWhenReadyChanged( }
playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); if (playWhenReadyChanged) {
} listener.onPlayWhenReadyChanged(
} playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
}
});
} }
} }
...@@ -146,6 +159,17 @@ import java.util.ArrayList; ...@@ -146,6 +159,17 @@ import java.util.ArrayList;
} }
@Override @Override
@RepeatMode
public int getRepeatMode() {
return REPEAT_MODE_OFF;
}
@Override
public boolean getShuffleModeEnabled() {
return false;
}
@Override
public int getRendererCount() { public int getRendererCount() {
return 0; return 0;
} }
...@@ -162,7 +186,7 @@ import java.util.ArrayList; ...@@ -162,7 +186,7 @@ import java.util.ArrayList;
@Override @Override
public int getCurrentPeriodIndex() { public int getCurrentPeriodIndex() {
return 0; return periodIndex;
} }
@Override @Override
...@@ -186,7 +210,7 @@ import java.util.ArrayList; ...@@ -186,7 +210,7 @@ import java.util.ArrayList;
@Override @Override
public long getCurrentPosition() { public long getCurrentPosition() {
return position; return positionMs;
} }
@Override @Override
...@@ -206,6 +230,6 @@ import java.util.ArrayList; ...@@ -206,6 +230,6 @@ import java.util.ArrayList;
@Override @Override
public long getContentPosition() { public long getContentPosition() {
return contentPosition; return contentPositionMs;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment