Support resuming content after ads

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=161775394
This commit is contained in:
andrewlewis 2017-07-13 01:10:51 -07:00 committed by Oliver Woodman
parent 4f2fae4fba
commit 6c74a31556
8 changed files with 101 additions and 35 deletions

View File

@ -406,7 +406,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
private void updateResumePosition() { private void updateResumePosition() {
resumeWindow = player.getCurrentWindowIndex(); resumeWindow = player.getCurrentWindowIndex();
resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getCurrentPosition()) resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getContentPosition())
: C.TIME_UNSET; : C.TIME_UNSET;
} }

View File

@ -536,6 +536,11 @@ public final class DynamicConcatenatingMediaSourceTest extends TestCase {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public long getContentPosition() {
throw new UnsupportedOperationException();
}
@Override @Override
public boolean handleMessage(Message msg) { public boolean handleMessage(Message msg) {
ExoPlayerMessage[] messages = (ExoPlayerMessage[]) msg.obj; ExoPlayerMessage[] messages = (ExoPlayerMessage[]) msg.obj;

View File

@ -563,4 +563,11 @@ public interface ExoPlayer {
*/ */
int getCurrentAdIndexInAdGroup(); int getCurrentAdIndexInAdGroup();
/**
* If {@link #isPlayingAd()} returns {@code true}, returns the content position that will be
* played once all ads in the ad group have finished playing, in milliseconds. If there is no ad
* playing, the returned position is the same as that returned by {@link #getCurrentPosition()}.
*/
long getContentPosition();
} }

View File

@ -367,6 +367,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
return pendingSeekAcks == 0 ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; return pendingSeekAcks == 0 ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET;
} }
@Override
public long getContentPosition() {
if (isPlayingAd()) {
timeline.getPeriod(playbackInfo.periodId.periodIndex, period);
return period.getPositionInWindowMs() + C.usToMs(playbackInfo.contentPositionUs);
} else {
return getCurrentPosition();
}
}
@Override @Override
public int getRendererCount() { public int getRendererCount() {
return renderers.length; return renderers.length;

View File

@ -54,6 +54,7 @@ import java.io.IOException;
public final MediaPeriodId periodId; public final MediaPeriodId periodId;
public final long startPositionUs; public final long startPositionUs;
public final long contentPositionUs;
public volatile long positionUs; public volatile long positionUs;
public volatile long bufferedPositionUs; public volatile long bufferedPositionUs;
@ -63,14 +64,20 @@ import java.io.IOException;
} }
public PlaybackInfo(MediaPeriodId periodId, long startPositionUs) { public PlaybackInfo(MediaPeriodId periodId, long startPositionUs) {
this(periodId, startPositionUs, C.TIME_UNSET);
}
public PlaybackInfo(MediaPeriodId periodId, long startPositionUs, long contentPositionUs) {
this.periodId = periodId; this.periodId = periodId;
this.startPositionUs = startPositionUs; this.startPositionUs = startPositionUs;
this.contentPositionUs = contentPositionUs;
positionUs = startPositionUs; positionUs = startPositionUs;
bufferedPositionUs = startPositionUs; bufferedPositionUs = startPositionUs;
} }
public PlaybackInfo copyWithPeriodId(MediaPeriodId periodId) { public PlaybackInfo copyWithPeriodIndex(int periodIndex) {
PlaybackInfo playbackInfo = new PlaybackInfo(periodId.periodIndex, startPositionUs); PlaybackInfo playbackInfo = new PlaybackInfo(periodId.copyWithPeriodIndex(periodIndex),
startPositionUs, contentPositionUs);
playbackInfo.positionUs = positionUs; playbackInfo.positionUs = positionUs;
playbackInfo.bufferedPositionUs = bufferedPositionUs; playbackInfo.bufferedPositionUs = bufferedPositionUs;
return playbackInfo; return playbackInfo;
@ -486,7 +493,7 @@ import java.io.IOException;
// position of the playing period to make sure none of the removed period is played. // position of the playing period to make sure none of the removed period is played.
MediaPeriodId periodId = playingPeriodHolder.info.id; MediaPeriodId periodId = playingPeriodHolder.info.id;
long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.positionUs); long newPositionUs = seekToPeriodPosition(periodId, playbackInfo.positionUs);
playbackInfo = new PlaybackInfo(periodId, newPositionUs); playbackInfo = new PlaybackInfo(periodId, newPositionUs, playbackInfo.contentPositionUs);
} }
} }
@ -663,11 +670,11 @@ import java.io.IOException;
boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET; boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
int periodIndex = periodPosition.first; int periodIndex = periodPosition.first;
long periodPositionUs = periodPosition.second; long periodPositionUs = periodPosition.second;
long contentPositionUs = periodPositionUs;
MediaPeriodId periodId = MediaPeriodId periodId =
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, periodPositionUs); mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, periodPositionUs);
if (periodId.isAd()) { if (periodId.isAd()) {
seekPositionAdjusted = true; seekPositionAdjusted = true;
// TODO: Resume content at periodPositionUs after the ad plays.
periodPositionUs = 0; periodPositionUs = 0;
} }
try { try {
@ -680,7 +687,7 @@ import java.io.IOException;
seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs; seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
periodPositionUs = newPeriodPositionUs; periodPositionUs = newPeriodPositionUs;
} finally { } finally {
playbackInfo = new PlaybackInfo(periodId, periodPositionUs); playbackInfo = new PlaybackInfo(periodId, periodPositionUs, contentPositionUs);
eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo) eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo)
.sendToTarget(); .sendToTarget();
} }
@ -985,16 +992,19 @@ import java.io.IOException;
long positionUs = periodPosition.second; long positionUs = periodPosition.second;
MediaPeriodId periodId = MediaPeriodId periodId =
mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs); mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex, positionUs);
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs); playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : positionUs, positionUs);
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) { } else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
if (timeline.isEmpty()) { if (timeline.isEmpty()) {
handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount); handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
return; return;
} }
Pair<Integer, Long> defaultPosition = getPeriodPosition(0, C.TIME_UNSET); Pair<Integer, Long> defaultPosition = getPeriodPosition(0, C.TIME_UNSET);
MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds( int periodIndex = defaultPosition.first;
defaultPosition.first, defaultPosition.second); long startPositionUs = defaultPosition.second;
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : defaultPosition.second); MediaPeriodId periodId = mediaPeriodInfoSequence.resolvePeriodPositionForAds(periodIndex,
startPositionUs);
playbackInfo = new PlaybackInfo(periodId, periodId.isAd() ? 0 : startPositionUs,
startPositionUs);
} }
} }
@ -1047,8 +1057,7 @@ import java.io.IOException;
// The current period is in the new timeline. Update the holder and playbackInfo. // The current period is in the new timeline. Update the holder and playbackInfo.
periodHolder = updatePeriodInfo(periodHolder, periodIndex); periodHolder = updatePeriodInfo(periodHolder, periodIndex);
if (periodIndex != playbackInfo.periodId.periodIndex) { if (periodIndex != playbackInfo.periodId.periodIndex) {
playbackInfo = playbackInfo = playbackInfo.copyWithPeriodIndex(periodIndex);
playbackInfo.copyWithPeriodId(playbackInfo.periodId.copyWithPeriodIndex(periodIndex));
} }
// If there are subsequent holders, update the index for each of them. If we find a holder // If there are subsequent holders, update the index for each of them. If we find a holder
@ -1070,7 +1079,8 @@ import java.io.IOException;
// position of the playing period to make sure none of the removed period is played. // position of the playing period to make sure none of the removed period is played.
long newPositionUs = long newPositionUs =
seekToPeriodPosition(playingPeriodHolder.info.id, playbackInfo.positionUs); seekToPeriodPosition(playingPeriodHolder.info.id, playbackInfo.positionUs);
playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, newPositionUs); playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, newPositionUs,
playbackInfo.contentPositionUs);
} else { } else {
// Update the loading period to be the last period that's still valid, and release all // Update the loading period to be the last period that's still valid, and release all
// subsequent periods. // subsequent periods.
@ -1223,7 +1233,7 @@ import java.io.IOException;
playingPeriodHolder.release(); playingPeriodHolder.release();
setPlayingPeriodHolder(playingPeriodHolder.next); setPlayingPeriodHolder(playingPeriodHolder.next);
playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id, playbackInfo = new PlaybackInfo(playingPeriodHolder.info.id,
playingPeriodHolder.info.startPositionUs); playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs);
updatePlaybackPositions(); updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
} }

View File

@ -47,6 +47,11 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
* {@link C#TIME_END_OF_SOURCE} if the end position is the end of the media period. * {@link C#TIME_END_OF_SOURCE} if the end position is the end of the media period.
*/ */
public final long endPositionUs; public final long endPositionUs;
/**
* If this is an ad, the position to play in the next content media period. {@link C#TIME_UNSET}
* otherwise.
*/
public final long contentPositionUs;
/** /**
* The duration of the media to play within the media period, in microseconds, or * The duration of the media to play within the media period, in microseconds, or
* {@link C#TIME_UNSET} if not known. * {@link C#TIME_UNSET} if not known.
@ -64,10 +69,11 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
public final boolean isFinal; public final boolean isFinal;
private MediaPeriodInfo(MediaPeriodId id, long startPositionUs, long endPositionUs, private MediaPeriodInfo(MediaPeriodId id, long startPositionUs, long endPositionUs,
long durationUs, boolean isLastInTimelinePeriod, boolean isFinal) { long contentPositionUs, long durationUs, boolean isLastInTimelinePeriod, boolean isFinal) {
this.id = id; this.id = id;
this.startPositionUs = startPositionUs; this.startPositionUs = startPositionUs;
this.endPositionUs = endPositionUs; this.endPositionUs = endPositionUs;
this.contentPositionUs = contentPositionUs;
this.durationUs = durationUs; this.durationUs = durationUs;
this.isLastInTimelinePeriod = isLastInTimelinePeriod; this.isLastInTimelinePeriod = isLastInTimelinePeriod;
this.isFinal = isFinal; this.isFinal = isFinal;
@ -79,14 +85,14 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
*/ */
public MediaPeriodInfo copyWithPeriodIndex(int periodIndex) { public MediaPeriodInfo copyWithPeriodIndex(int periodIndex) {
return new MediaPeriodInfo(id.copyWithPeriodIndex(periodIndex), startPositionUs, return new MediaPeriodInfo(id.copyWithPeriodIndex(periodIndex), startPositionUs,
endPositionUs, durationUs, isLastInTimelinePeriod, isFinal); endPositionUs, contentPositionUs, durationUs, isLastInTimelinePeriod, isFinal);
} }
/** /**
* Returns a copy of this instance with the start position set to the specified value. * Returns a copy of this instance with the start position set to the specified value.
*/ */
public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) { public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) {
return new MediaPeriodInfo(id, startPositionUs, endPositionUs, durationUs, return new MediaPeriodInfo(id, startPositionUs, endPositionUs, contentPositionUs, durationUs,
isLastInTimelinePeriod, isFinal); isLastInTimelinePeriod, isFinal);
} }
@ -127,7 +133,8 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
* Returns the first {@link MediaPeriodInfo} to play, based on the specified playback position. * Returns the first {@link MediaPeriodInfo} to play, based on the specified playback position.
*/ */
public MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) { public MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) {
return getMediaPeriodInfo(playbackInfo.periodId, playbackInfo.startPositionUs); return getMediaPeriodInfo(playbackInfo.periodId, playbackInfo.contentPositionUs,
playbackInfo.startPositionUs);
} }
/** /**
@ -173,8 +180,8 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
} else { } else {
startPositionUs = 0; startPositionUs = 0;
} }
return getMediaPeriodInfo(resolvePeriodPositionForAds(nextPeriodIndex, startPositionUs), MediaPeriodId periodId = resolvePeriodPositionForAds(nextPeriodIndex, startPositionUs);
startPositionUs); return getMediaPeriodInfo(periodId, startPositionUs, startPositionUs);
} }
MediaPeriodId currentPeriodId = currentMediaPeriodInfo.id; MediaPeriodId currentPeriodId = currentMediaPeriodInfo.id;
@ -190,18 +197,23 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
// Play the next ad in the ad group if it's available. // Play the next ad in the ad group if it's available.
return !period.isAdAvailable(currentAdGroupIndex, nextAdIndexInAdGroup) ? null return !period.isAdAvailable(currentAdGroupIndex, nextAdIndexInAdGroup) ? null
: getMediaPeriodInfoForAd(currentPeriodId.periodIndex, currentAdGroupIndex, : getMediaPeriodInfoForAd(currentPeriodId.periodIndex, currentAdGroupIndex,
nextAdIndexInAdGroup); nextAdIndexInAdGroup, currentMediaPeriodInfo.contentPositionUs);
} else { } else {
// Play content from the ad group position. // Play content from the ad group position.
return getMediaPeriodInfo(new MediaPeriodId(currentPeriodId.periodIndex), int nextAdGroupIndex =
period.getAdGroupTimeUs(currentAdGroupIndex)); period.getAdGroupIndexAfterPositionUs(currentMediaPeriodInfo.contentPositionUs);
long endUs = nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE
: period.getAdGroupTimeUs(nextAdGroupIndex);
return getMediaPeriodInfoForContent(currentPeriodId.periodIndex,
currentMediaPeriodInfo.contentPositionUs, endUs);
} }
} else if (currentMediaPeriodInfo.endPositionUs != C.TIME_END_OF_SOURCE) { } else if (currentMediaPeriodInfo.endPositionUs != C.TIME_END_OF_SOURCE) {
// Play the next ad group if it's available. // Play the next ad group if it's available.
int nextAdGroupIndex = int nextAdGroupIndex =
period.getAdGroupIndexForPositionUs(currentMediaPeriodInfo.endPositionUs); period.getAdGroupIndexForPositionUs(currentMediaPeriodInfo.endPositionUs);
return !period.isAdAvailable(nextAdGroupIndex, 0) ? null return !period.isAdAvailable(nextAdGroupIndex, 0) ? null
: getMediaPeriodInfoForAd(currentPeriodId.periodIndex, nextAdGroupIndex, 0); : getMediaPeriodInfoForAd(currentPeriodId.periodIndex, nextAdGroupIndex, 0,
currentMediaPeriodInfo.endPositionUs);
} else { } else {
// Check if the postroll ad should be played. // Check if the postroll ad should be played.
int adGroupCount = period.getAdGroupCount(); int adGroupCount = period.getAdGroupCount();
@ -211,7 +223,8 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|| !period.isAdAvailable(adGroupCount - 1, 0)) { || !period.isAdAvailable(adGroupCount - 1, 0)) {
return null; return null;
} }
return getMediaPeriodInfoForAd(currentPeriodId.periodIndex, adGroupCount - 1, 0); return getMediaPeriodInfoForAd(currentPeriodId.periodIndex, adGroupCount - 1, 0,
currentMediaPeriodInfo.endPositionUs);
} }
} }
@ -223,8 +236,12 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
public MediaPeriodId resolvePeriodPositionForAds(int periodIndex, long positionUs) { public MediaPeriodId resolvePeriodPositionForAds(int periodIndex, long positionUs) {
timeline.getPeriod(periodIndex, period); timeline.getPeriod(periodIndex, period);
int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs); int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs);
return adGroupIndex == C.INDEX_UNSET ? new MediaPeriodId(periodIndex) if (adGroupIndex == C.INDEX_UNSET) {
: new MediaPeriodId(periodIndex, adGroupIndex, 0); return new MediaPeriodId(periodIndex);
} else {
int adIndexInAdGroup = period.getPlayedAdCount(adGroupIndex);
return new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup);
}
} }
/** /**
@ -255,17 +272,19 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
long durationUs = newId.isAd() long durationUs = newId.isAd()
? period.getAdDurationUs(newId.adGroupIndex, newId.adIndexInAdGroup) ? period.getAdDurationUs(newId.adGroupIndex, newId.adIndexInAdGroup)
: (endPositionUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endPositionUs); : (endPositionUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endPositionUs);
return new MediaPeriodInfo(newId, startPositionUs, endPositionUs, durationUs, isLastInPeriod, return new MediaPeriodInfo(newId, startPositionUs, endPositionUs, info.contentPositionUs,
isLastInTimeline); durationUs, isLastInPeriod, isLastInTimeline);
} }
private MediaPeriodInfo getMediaPeriodInfo(MediaPeriodId id, long startPositionUs) { private MediaPeriodInfo getMediaPeriodInfo(MediaPeriodId id, long contentPositionUs,
long startPositionUs) {
timeline.getPeriod(id.periodIndex, period); timeline.getPeriod(id.periodIndex, period);
if (id.isAd()) { if (id.isAd()) {
if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) { if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) {
return null; return null;
} }
return getMediaPeriodInfoForAd(id.periodIndex, id.adGroupIndex, id.adIndexInAdGroup); return getMediaPeriodInfoForAd(id.periodIndex, id.adGroupIndex, id.adIndexInAdGroup,
contentPositionUs);
} else { } else {
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs);
long endUs = nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE long endUs = nextAdGroupIndex == C.INDEX_UNSET ? C.TIME_END_OF_SOURCE
@ -275,14 +294,14 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
} }
private MediaPeriodInfo getMediaPeriodInfoForAd(int periodIndex, int adGroupIndex, private MediaPeriodInfo getMediaPeriodInfoForAd(int periodIndex, int adGroupIndex,
int adIndexInAdGroup) { int adIndexInAdGroup, long contentPositionUs) {
MediaPeriodId id = new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup); MediaPeriodId id = new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup);
boolean isLastInPeriod = isLastInPeriod(id, C.TIME_END_OF_SOURCE); boolean isLastInPeriod = isLastInPeriod(id, C.TIME_END_OF_SOURCE);
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
long durationUs = timeline.getPeriod(id.periodIndex, period) long durationUs = timeline.getPeriod(id.periodIndex, period)
.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup); .getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup);
return new MediaPeriodInfo(id, 0, C.TIME_END_OF_SOURCE, durationUs, isLastInPeriod, return new MediaPeriodInfo(id, 0, C.TIME_END_OF_SOURCE, contentPositionUs, durationUs,
isLastInTimeline); isLastInPeriod, isLastInTimeline);
} }
private MediaPeriodInfo getMediaPeriodInfoForContent(int periodIndex, long startPositionUs, private MediaPeriodInfo getMediaPeriodInfoForContent(int periodIndex, long startPositionUs,
@ -292,7 +311,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod); boolean isLastInTimeline = isLastInTimeline(id, isLastInPeriod);
timeline.getPeriod(id.periodIndex, period); timeline.getPeriod(id.periodIndex, period);
long durationUs = endUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endUs; long durationUs = endUs == C.TIME_END_OF_SOURCE ? period.getDurationUs() : endUs;
return new MediaPeriodInfo(id, startPositionUs, endUs, durationUs, isLastInPeriod, return new MediaPeriodInfo(id, startPositionUs, endUs, C.TIME_UNSET, durationUs, isLastInPeriod,
isLastInTimeline); isLastInTimeline);
} }

View File

@ -715,6 +715,11 @@ public class SimpleExoPlayer implements ExoPlayer {
return player.getCurrentAdIndexInAdGroup(); return player.getCurrentAdIndexInAdGroup();
} }
@Override
public long getContentPosition() {
return player.getContentPosition();
}
// Internal methods. // Internal methods.
private void removeSurfaceCallbacks() { private void removeSurfaceCallbacks() {

View File

@ -378,6 +378,16 @@ public abstract class Timeline {
return adGroupTimesUs[adGroupIndex]; return adGroupTimesUs[adGroupIndex];
} }
/**
* Returns the number of ads that have been played in the specified ad group in the period.
*
* @param adGroupIndex The ad group index.
* @return The number of ads that have been played.
*/
public int getPlayedAdCount(int adGroupIndex) {
return adsPlayedCounts[adGroupIndex];
}
/** /**
* Returns whether the ad group at index {@code adGroupIndex} has been played. * Returns whether the ad group at index {@code adGroupIndex} has been played.
* *