diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 493deed4ad..987a58d3f7 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -887,7 +887,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A private void stopAdInternal() { Assertions.checkState(imaAdState != IMA_AD_STATE_NONE); imaAdState = IMA_AD_STATE_NONE; - int adIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].nextAdIndexToPlay; + int adIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); // TODO: Handle the skipped event so the ad can be marked as skipped rather than played. adPlaybackState = adPlaybackState.withPlayedAd(adGroupIndex, adIndexInAdGroup).withAdResumePositionUs(0); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index 0c643ec120..4b6ef1807e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -372,7 +372,7 @@ import com.google.android.exoplayer2.util.Assertions; if (adGroupIndex == C.INDEX_UNSET) { return new MediaPeriodId(periodIndex); } else { - int adIndexInAdGroup = period.getNextAdIndexToPlay(adGroupIndex); + int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); return new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup); } } @@ -496,19 +496,20 @@ import com.google.android.exoplayer2.util.Assertions; MediaPeriodId currentPeriodId = mediaPeriodInfo.id; timeline.getPeriod(currentPeriodId.periodIndex, period); if (currentPeriodId.isAd()) { - int currentAdGroupIndex = currentPeriodId.adGroupIndex; - int adCountInCurrentAdGroup = period.getAdCountInAdGroup(currentAdGroupIndex); + int adGroupIndex = currentPeriodId.adGroupIndex; + int adCountInCurrentAdGroup = period.getAdCountInAdGroup(adGroupIndex); if (adCountInCurrentAdGroup == C.LENGTH_UNSET) { return null; } - int nextAdIndexInAdGroup = currentPeriodId.adIndexInAdGroup + 1; + int nextAdIndexInAdGroup = + period.getNextAdIndexToPlay(adGroupIndex, currentPeriodId.adIndexInAdGroup); if (nextAdIndexInAdGroup < adCountInCurrentAdGroup) { // Play the next ad in the ad group if it's available. - return !period.isAdAvailable(currentAdGroupIndex, nextAdIndexInAdGroup) + return !period.isAdAvailable(adGroupIndex, nextAdIndexInAdGroup) ? null : getMediaPeriodInfoForAd( currentPeriodId.periodIndex, - currentAdGroupIndex, + adGroupIndex, nextAdIndexInAdGroup, mediaPeriodInfo.contentPositionUs); } else { @@ -524,22 +525,32 @@ import com.google.android.exoplayer2.util.Assertions; return getMediaPeriodInfoForContent( currentPeriodId.periodIndex, mediaPeriodInfo.endPositionUs); } - return !period.isAdAvailable(nextAdGroupIndex, 0) + int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex); + return !period.isAdAvailable(nextAdGroupIndex, adIndexInAdGroup) ? null : getMediaPeriodInfoForAd( - currentPeriodId.periodIndex, nextAdGroupIndex, 0, mediaPeriodInfo.endPositionUs); + currentPeriodId.periodIndex, + nextAdGroupIndex, + adIndexInAdGroup, + mediaPeriodInfo.endPositionUs); } else { // Check if the postroll ad should be played. int adGroupCount = period.getAdGroupCount(); - if (adGroupCount == 0 - || period.getAdGroupTimeUs(adGroupCount - 1) != C.TIME_END_OF_SOURCE - || period.hasPlayedAdGroup(adGroupCount - 1) - || !period.isAdAvailable(adGroupCount - 1, 0)) { + if (adGroupCount == 0) { + return null; + } + int adGroupIndex = adGroupCount - 1; + if (period.getAdGroupTimeUs(adGroupIndex) != C.TIME_END_OF_SOURCE + || period.hasPlayedAdGroup(adGroupIndex)) { + return null; + } + int adIndexInAdGroup = period.getFirstAdIndexToPlay(adGroupIndex); + if (!period.isAdAvailable(adGroupIndex, adIndexInAdGroup)) { return null; } long contentDurationUs = period.getDurationUs(); return getMediaPeriodInfoForAd( - currentPeriodId.periodIndex, adGroupCount - 1, 0, contentDurationUs); + currentPeriodId.periodIndex, adGroupIndex, adIndexInAdGroup, contentDurationUs); } } @@ -587,7 +598,7 @@ import com.google.android.exoplayer2.util.Assertions; .getPeriod(id.periodIndex, period) .getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup); long startPositionUs = - adIndexInAdGroup == period.getNextAdIndexToPlay(adGroupIndex) + adIndexInAdGroup == period.getFirstAdIndexToPlay(adGroupIndex) ? period.getAdResumePositionUs() : 0; return new MediaPeriodInfo( @@ -636,7 +647,7 @@ import com.google.android.exoplayer2.util.Assertions; boolean isLastAd = isAd && id.adGroupIndex == lastAdGroupIndex && id.adIndexInAdGroup == postrollAdCount - 1; - return isLastAd || (!isAd && period.getNextAdIndexToPlay(lastAdGroupIndex) == postrollAdCount); + return isLastAd || (!isAd && period.getFirstAdIndexToPlay(lastAdGroupIndex) == postrollAdCount); } private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 26c2cc3e83..5906ffb8e7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -381,15 +381,29 @@ public abstract class Timeline { } /** - * Returns the index of the next ad to play in the specified ad group, or the number of ads in - * the ad group if the ad group does not have any ads remaining to play. + * Returns the index of the first ad in the specified ad group that should be played, or the + * number of ads in the ad group if no ads should be played. * * @param adGroupIndex The ad group index. + * @return The index of the first ad that should be played, or the number of ads in the ad group + * if no ads should be played. + */ + public int getFirstAdIndexToPlay(int adGroupIndex) { + return adPlaybackState.adGroups[adGroupIndex].getFirstAdIndexToPlay(); + } + + /** + * Returns the index of the next ad in the specified ad group that should be played after + * playing {@code adIndexInAdGroup}, or the number of ads in the ad group if no later ads should + * be played. + * + * @param adGroupIndex The ad group index. + * @param lastPlayedAdIndex The last played ad index in the ad group. * @return The index of the next ad that should be played, or the number of ads in the ad group * if the ad group does not have any ads remaining to play. */ - public int getNextAdIndexToPlay(int adGroupIndex) { - return adPlaybackState.adGroups[adGroupIndex].nextAdIndexToPlay; + public int getNextAdIndexToPlay(int adGroupIndex, int lastPlayedAdIndex) { + return adPlaybackState.adGroups[adGroupIndex].getNextAdIndexToPlay(lastPlayedAdIndex); } /** @@ -400,7 +414,7 @@ public abstract class Timeline { */ public boolean hasPlayedAdGroup(int adGroupIndex) { AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex]; - return adGroup.nextAdIndexToPlay == adGroup.count; + return adGroup.getFirstAdIndexToPlay() == adGroup.count; } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java index 7b06098d45..02bd67dbd8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdPlaybackState.java @@ -49,8 +49,6 @@ public final class AdPlaybackState { public final @AdState int[] states; /** The durations of each ad in the ad group, in microseconds. */ public final long[] durationsUs; - /** The index of the next ad that should be played, or {@link #count} if all ads were played. */ - public final int nextAdIndexToPlay; /** Creates a new ad group with an unspecified number of ads. */ public AdGroup() { @@ -67,14 +65,30 @@ public final class AdPlaybackState { this.states = states; this.uris = uris; this.durationsUs = durationsUs; - int nextAdIndexToPlay; - for (nextAdIndexToPlay = 0; nextAdIndexToPlay < states.length; nextAdIndexToPlay++) { + } + + /** + * Returns the index of the first ad in the ad group that should be played, or {@link #count} if + * no ads should be played. + */ + public int getFirstAdIndexToPlay() { + return getNextAdIndexToPlay(-1); + } + + /** + * Returns the index of the next ad in the ad group that should be played after playing {@code + * lastPlayedAdIndex}, or {@link #count} if no later ads should be played. + */ + public int getNextAdIndexToPlay(int lastPlayedAdIndex) { + int nextAdIndexToPlay = lastPlayedAdIndex + 1; + while (nextAdIndexToPlay < states.length) { if (states[nextAdIndexToPlay] == AD_STATE_UNAVAILABLE || states[nextAdIndexToPlay] == AD_STATE_AVAILABLE) { break; } + nextAdIndexToPlay++; } - this.nextAdIndexToPlay = nextAdIndexToPlay; + return nextAdIndexToPlay; } /** diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java index 95f492f17f..ca8bf5d393 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/ads/AdPlaybackStateTest.java @@ -70,29 +70,29 @@ public final class AdPlaybackStateTest { } @Test - public void testInitialNextAdIndexToPlay() { + public void testGetFirstAdIndexToPlayIsZero() { state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI); - assertThat(state.adGroups[0].nextAdIndexToPlay).isEqualTo(0); + assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(0); } @Test - public void testNextAdIndexToPlayWithPlayedAd() { + public void testGetFirstAdIndexToPlaySkipsPlayedAd() { state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI); state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); - assertThat(state.adGroups[0].nextAdIndexToPlay).isEqualTo(1); + assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(1); assertThat(state.adGroups[0].states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE); assertThat(state.adGroups[0].states[2]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE); } @Test - public void testNextAdIndexToPlaySkipsErrorAds() { + public void testGetFirstAdIndexToPlaySkipsErrorAds() { state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, TEST_URI); state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2, TEST_URI); @@ -100,7 +100,17 @@ public final class AdPlaybackStateTest { state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0); state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1); - assertThat(state.adGroups[0].nextAdIndexToPlay).isEqualTo(2); + assertThat(state.adGroups[0].getFirstAdIndexToPlay()).isEqualTo(2); + } + + @Test + public void testGetNextAdIndexToPlaySkipsErrorAds() { + state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 3); + state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI); + + state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1); + + assertThat(state.adGroups[0].getNextAdIndexToPlay(0)).isEqualTo(2); } @Test