From 165fce4c64d547e01ef4e5f6fbb23ebc06354f2b Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 7 Feb 2018 02:44:23 -0800 Subject: [PATCH] Get the next ad index to play in MediaPeriodQueue The ad index in the ad group may need to skip over ads that failed to load, so it can't just be incremented any more. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=184812556 --- .../exoplayer2/ext/ima/ImaAdsLoader.java | 2 +- .../android/exoplayer2/MediaPeriodQueue.java | 41 ++++++++++++------- .../google/android/exoplayer2/Timeline.java | 24 ++++++++--- .../source/ads/AdPlaybackState.java | 24 ++++++++--- .../source/ads/AdPlaybackStateTest.java | 22 +++++++--- 5 files changed, 81 insertions(+), 32 deletions(-) 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