Commit b82178ec by andrewlewis Committed by Oliver Woodman

Make ad state immutable and store state of each ad

Before this change, the ad playback state stored the number of played ads in
each ad group. There was no way to represent that an ad had failed to load (and
it wouldn't be possible just to increment the played ad count to signal a load
error because there might be an unplayed ad before the ad that failed to load).

Represent the state of each ad (unavailable, available, skipped, played, error)
in each ad group. In a later change the player will use this information to
update its loaded MediaPeriods in response to future ads failing to load.

Also make the AdPlaybackState immutable and remove copying/duplication of its
fields in the ad timeline and period.

Issue: #3584

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=183655308
parent 0c67a578
......@@ -48,6 +48,7 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.source.ads.AdPlaybackState.AdState;
import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
......@@ -393,7 +394,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
maybeNotifyAdError();
if (adPlaybackState != null) {
// Pass the ad playback state to the player, and resume ads if necessary.
eventListener.onAdPlaybackState(adPlaybackState.copy());
eventListener.onAdPlaybackState(adPlaybackState);
if (imaPausedContent && player.getPlayWhenReady()) {
adsManager.resume();
}
......@@ -409,7 +410,9 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
@Override
public void detachPlayer() {
if (adsManager != null && imaPausedContent) {
adPlaybackState.setAdResumePositionUs(playingAd ? C.msToUs(player.getCurrentPosition()) : 0);
adPlaybackState =
adPlaybackState.withAdResumePositionUs(
playingAd ? C.msToUs(player.getCurrentPosition()) : 0);
adsManager.pause();
}
lastAdProgress = getAdProgress();
......@@ -474,7 +477,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
if (DEBUG) {
Log.d(TAG, "Loaded ad " + adPosition + " of " + adCount + " in group " + adGroupIndex);
}
adPlaybackState.setAdCount(adGroupIndex, adCount);
adPlaybackState = adPlaybackState.withAdCount(adGroupIndex, adCount);
updateAdPlaybackState();
if (adGroupIndex != expectedAdGroupIndex) {
Log.w(
......@@ -589,8 +592,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
if (DEBUG) {
Log.d(TAG, "loadAd in ad group " + adGroupIndex);
}
adPlaybackState.addAdUri(adGroupIndex, Uri.parse(adUriString));
if (adPlaybackState.adsLoadedCounts[adGroupIndex] == adPlaybackState.adCounts[adGroupIndex]) {
int adIndexInAdGroup = getAdIndexInAdGroupToLoad(adGroupIndex);
adPlaybackState =
adPlaybackState.withAdUri(adGroupIndex, adIndexInAdGroup, Uri.parse(adUriString));
if (getAdIndexInAdGroupToLoad(adGroupIndex) == C.INDEX_UNSET) {
// Keep track of the expected ad group index to use as a fallback if the LOADED event is
// unexpectedly not triggered.
expectedAdGroupIndex++;
......@@ -693,7 +698,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
long contentDurationUs = timeline.getPeriod(0, period).durationUs;
contentDurationMs = C.usToMs(contentDurationUs);
if (contentDurationUs != C.TIME_UNSET) {
adPlaybackState.contentDurationUs = contentDurationUs;
adPlaybackState = adPlaybackState.withContentDurationUs(contentDurationUs);
}
updateImaStateForPlayerState();
}
......@@ -748,7 +753,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
if (sentContentComplete) {
for (int i = 0; i < adPlaybackState.adGroupCount; i++) {
if (adPlaybackState.adGroupTimesUs[i] != C.TIME_END_OF_SOURCE) {
adPlaybackState.playedAdGroup(i);
adPlaybackState = adPlaybackState.withSkippedAdGroup(i);
}
}
updateAdPlaybackState();
......@@ -790,7 +795,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
} else /* adGroupIndexForPosition > 0 */ {
// Skip ad groups before the one at or immediately before the playback position.
for (int i = 0; i < adGroupIndexForPosition; i++) {
adPlaybackState.playedAdGroup(i);
adPlaybackState = adPlaybackState.withSkippedAdGroup(i);
}
// Play ads after the midpoint between the ad to play and the one before it, to avoid issues
// with rounding one of the two ad times.
......@@ -859,7 +864,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
}
}
if (playingAd && adGroupIndex != C.INDEX_UNSET) {
adPlaybackState.playedAdGroup(adGroupIndex);
adPlaybackState = adPlaybackState.withSkippedAdGroup(adGroupIndex);
adGroupIndex = C.INDEX_UNSET;
updateAdPlaybackState();
}
......@@ -879,7 +884,10 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
private void stopAdInternal() {
Assertions.checkState(imaAdState != IMA_AD_STATE_NONE);
imaAdState = IMA_AD_STATE_NONE;
adPlaybackState.playedAd(adGroupIndex);
int adIndexInAdGroup = adPlaybackState.adGroups[adGroupIndex].nextAdIndexToPlay;
// TODO: Handle the skipped event so the ad can be marked as skipped rather than played.
adPlaybackState =
adPlaybackState.withPlayedAd(adGroupIndex, adIndexInAdGroup).withAdResumePositionUs(0);
updateAdPlaybackState();
if (!playingAd) {
adGroupIndex = C.INDEX_UNSET;
......@@ -901,7 +909,7 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
private void updateAdPlaybackState() {
// Ignore updates while detached. When a player is attached it will receive the latest state.
if (eventListener != null) {
eventListener.onAdPlaybackState(adPlaybackState.copy());
eventListener.onAdPlaybackState(adPlaybackState);
}
}
......@@ -946,4 +954,19 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
}
return adGroupTimesUs;
}
/**
* Returns the next ad index in the specified ad group to load, or {@link C#INDEX_UNSET} if all
* ads in the ad group have loaded.
*/
private int getAdIndexInAdGroupToLoad(int adGroupIndex) {
@AdState int[] states = adPlaybackState.adGroups[adGroupIndex].states;
int adIndexInAdGroup = 0;
// IMA loads ads in order.
while (adIndexInAdGroup < states.length
&& states[adIndexInAdGroup] != AdPlaybackState.AD_STATE_UNAVAILABLE) {
adIndexInAdGroup++;
}
return adIndexInAdGroup == states.length ? C.INDEX_UNSET : adIndexInAdGroup;
}
}
......@@ -326,7 +326,7 @@ import com.google.android.exoplayer2.util.Assertions;
if (adGroupIndex == C.INDEX_UNSET) {
return new MediaPeriodId(periodIndex);
} else {
int adIndexInAdGroup = period.getPlayedAdCount(adGroupIndex);
int adIndexInAdGroup = period.getNextAdIndexToPlay(adGroupIndex);
return new MediaPeriodId(periodIndex, adGroupIndex, adIndexInAdGroup);
}
}
......@@ -502,7 +502,7 @@ import com.google.android.exoplayer2.util.Assertions;
.getPeriod(id.periodIndex, period)
.getAdDurationUs(id.adGroupIndex, id.adIndexInAdGroup);
long startPositionUs =
adIndexInAdGroup == period.getPlayedAdCount(adGroupIndex)
adIndexInAdGroup == period.getNextAdIndexToPlay(adGroupIndex)
? period.getAdResumePositionUs()
: 0;
return new MediaPeriodInfo(
......@@ -547,7 +547,7 @@ import com.google.android.exoplayer2.util.Assertions;
boolean isLastAd =
isAd && id.adGroupIndex == lastAdGroupIndex && id.adIndexInAdGroup == postrollAdCount - 1;
return isLastAd || (!isAd && period.getPlayedAdCount(lastAdGroupIndex) == postrollAdCount);
return isLastAd || (!isAd && period.getNextAdIndexToPlay(lastAdGroupIndex) == postrollAdCount);
}
private boolean isLastInTimeline(MediaPeriodId id, boolean isLastMediaPeriodInPeriod) {
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2;
import android.util.Pair;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.util.Assertions;
/**
......@@ -278,12 +279,7 @@ public abstract class Timeline {
public long durationUs;
private long positionInWindowUs;
private long[] adGroupTimesUs;
private int[] adCounts;
private int[] adsLoadedCounts;
private int[] adsPlayedCounts;
private long[][] adDurationsUs;
private long adResumePositionUs;
private AdPlaybackState adPlaybackState;
/**
* Sets the data held by this period.
......@@ -300,8 +296,7 @@ public abstract class Timeline {
*/
public Period set(Object id, Object uid, int windowIndex, long durationUs,
long positionInWindowUs) {
return set(id, uid, windowIndex, durationUs, positionInWindowUs, null, null, null, null,
null, C.TIME_UNSET);
return set(id, uid, windowIndex, durationUs, positionInWindowUs, AdPlaybackState.NONE);
}
/**
......@@ -315,33 +310,23 @@ public abstract class Timeline {
* @param positionInWindowUs The position of the start of this period relative to the start of
* the window to which it belongs, in milliseconds. May be negative if the start of the
* period is not within the window.
* @param adGroupTimesUs The times of ad groups relative to the start of the period, in
* microseconds. A final element with the value {@link C#TIME_END_OF_SOURCE} indicates that
* the period has a postroll ad.
* @param adCounts The number of ads in each ad group. An element may be {@link C#LENGTH_UNSET}
* if the number of ads is not yet known.
* @param adsLoadedCounts The number of ads loaded so far in each ad group.
* @param adsPlayedCounts The number of ads played so far in each ad group.
* @param adDurationsUs The duration of each ad in each ad group, in microseconds. An element
* may be {@link C#TIME_UNSET} if the duration is not yet known.
* @param adResumePositionUs The position offset in the first unplayed ad at which to begin
* playback, in microseconds.
* @param adPlaybackState The state of the period's ads, or {@link AdPlaybackState#NONE} if
* there are no ads.
* @return This period, for convenience.
*/
public Period set(Object id, Object uid, int windowIndex, long durationUs,
long positionInWindowUs, long[] adGroupTimesUs, int[] adCounts, int[] adsLoadedCounts,
int[] adsPlayedCounts, long[][] adDurationsUs, long adResumePositionUs) {
public Period set(
Object id,
Object uid,
int windowIndex,
long durationUs,
long positionInWindowUs,
AdPlaybackState adPlaybackState) {
this.id = id;
this.uid = uid;
this.windowIndex = windowIndex;
this.durationUs = durationUs;
this.positionInWindowUs = positionInWindowUs;
this.adGroupTimesUs = adGroupTimesUs;
this.adCounts = adCounts;
this.adsLoadedCounts = adsLoadedCounts;
this.adsPlayedCounts = adsPlayedCounts;
this.adDurationsUs = adDurationsUs;
this.adResumePositionUs = adResumePositionUs;
this.adPlaybackState = adPlaybackState;
return this;
}
......@@ -381,7 +366,7 @@ public abstract class Timeline {
* Returns the number of ad groups in the period.
*/
public int getAdGroupCount() {
return adGroupTimesUs == null ? 0 : adGroupTimesUs.length;
return adPlaybackState.adGroupCount;
}
/**
......@@ -392,17 +377,19 @@ public abstract class Timeline {
* @return The time of the ad group at the index, in microseconds.
*/
public long getAdGroupTimeUs(int adGroupIndex) {
return adGroupTimesUs[adGroupIndex];
return adPlaybackState.adGroupTimesUs[adGroupIndex];
}
/**
* Returns the number of ads that have been played in the specified ad group in the period.
* 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.
*
* @param adGroupIndex The ad group index.
* @return The number of ads that have been played.
* @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 getPlayedAdCount(int adGroupIndex) {
return adsPlayedCounts[adGroupIndex];
public int getNextAdIndexToPlay(int adGroupIndex) {
return adPlaybackState.adGroups[adGroupIndex].nextAdIndexToPlay;
}
/**
......@@ -412,8 +399,8 @@ public abstract class Timeline {
* @return Whether the ad group at index {@code adGroupIndex} has been played.
*/
public boolean hasPlayedAdGroup(int adGroupIndex) {
return adCounts[adGroupIndex] != C.INDEX_UNSET
&& adsPlayedCounts[adGroupIndex] == adCounts[adGroupIndex];
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
return adGroup.nextAdIndexToPlay == adGroup.count;
}
/**
......@@ -425,6 +412,7 @@ public abstract class Timeline {
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
public int getAdGroupIndexForPositionUs(long positionUs) {
long[] adGroupTimesUs = adPlaybackState.adGroupTimesUs;
if (adGroupTimesUs == null) {
return C.INDEX_UNSET;
}
......@@ -446,6 +434,7 @@ public abstract class Timeline {
* @return The index of the ad group, or {@link C#INDEX_UNSET}.
*/
public int getAdGroupIndexAfterPositionUs(long positionUs) {
long[] adGroupTimesUs = adPlaybackState.adGroupTimesUs;
if (adGroupTimesUs == null) {
return C.INDEX_UNSET;
}
......@@ -467,7 +456,7 @@ public abstract class Timeline {
* @return The number of ads in the ad group, or {@link C#LENGTH_UNSET} if not yet known.
*/
public int getAdCountInAdGroup(int adGroupIndex) {
return adCounts[adGroupIndex];
return adPlaybackState.adGroups[adGroupIndex].count;
}
/**
......@@ -478,7 +467,9 @@ public abstract class Timeline {
* @return Whether the URL for the specified ad is known.
*/
public boolean isAdAvailable(int adGroupIndex, int adIndexInAdGroup) {
return adIndexInAdGroup < adsLoadedCounts[adGroupIndex];
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
return adGroup.count != C.LENGTH_UNSET
&& adGroup.states[adIndexInAdGroup] != AdPlaybackState.AD_STATE_UNAVAILABLE;
}
/**
......@@ -490,10 +481,7 @@ public abstract class Timeline {
* @return The duration of the ad, or {@link C#TIME_UNSET} if not yet known.
*/
public long getAdDurationUs(int adGroupIndex, int adIndexInAdGroup) {
if (adIndexInAdGroup >= adDurationsUs[adGroupIndex].length) {
return C.TIME_UNSET;
}
return adDurationsUs[adGroupIndex][adIndexInAdGroup];
return adPlaybackState.adGroups[adGroupIndex].durationsUs[adIndexInAdGroup];
}
/**
......@@ -501,7 +489,7 @@ public abstract class Timeline {
* microseconds.
*/
public long getAdResumePositionUs() {
return adResumePositionUs;
return adPlaybackState.adResumePositionUs;
}
}
......
......@@ -222,7 +222,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
int adGroupIndex = id.adGroupIndex;
int adIndexInAdGroup = id.adIndexInAdGroup;
if (adGroupMediaSources[adGroupIndex].length <= adIndexInAdGroup) {
Uri adUri = adPlaybackState.adUris[id.adGroupIndex][id.adIndexInAdGroup];
Uri adUri = adPlaybackState.adGroups[id.adGroupIndex].uris[id.adIndexInAdGroup];
MediaSource adMediaSource =
adMediaSourceFactory.createMediaSource(adUri, eventHandler, eventListener);
int oldAdCount = adGroupMediaSources[id.adGroupIndex].length;
......@@ -337,11 +337,11 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
private void maybeUpdateSourceInfo() {
if (adPlaybackState != null && contentTimeline != null) {
Timeline timeline = adPlaybackState.adGroupCount == 0 ? contentTimeline
: new SinglePeriodAdTimeline(contentTimeline, adPlaybackState.adGroupTimesUs,
adPlaybackState.adCounts, adPlaybackState.adsLoadedCounts,
adPlaybackState.adsPlayedCounts, adDurationsUs, adPlaybackState.adResumePositionUs,
adPlaybackState.contentDurationUs);
adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs);
Timeline timeline =
adPlaybackState.adGroupCount == 0
? contentTimeline
: new SinglePeriodAdTimeline(contentTimeline, adPlaybackState);
listener.onSourceInfoRefreshed(this, timeline, contentManifest);
}
}
......
......@@ -25,54 +25,32 @@ import com.google.android.exoplayer2.util.Assertions;
*/
/* package */ final class SinglePeriodAdTimeline extends ForwardingTimeline {
private final long[] adGroupTimesUs;
private final int[] adCounts;
private final int[] adsLoadedCounts;
private final int[] adsPlayedCounts;
private final long[][] adDurationsUs;
private final long adResumePositionUs;
private final long contentDurationUs;
private final AdPlaybackState adPlaybackState;
/**
* Creates a new timeline with a single period containing the specified ads.
* Creates a new timeline with a single period containing ads.
*
* @param contentTimeline The timeline of the content alongside which ads will be played. It must
* have one window and one period.
* @param adGroupTimesUs The times of ad groups relative to the start of the period, in
* microseconds. A final element with the value {@link C#TIME_END_OF_SOURCE} indicates that
* the period has a postroll ad.
* @param adCounts The number of ads in each ad group. An element may be {@link C#LENGTH_UNSET}
* if the number of ads is not yet known.
* @param adsLoadedCounts The number of ads loaded so far in each ad group.
* @param adsPlayedCounts The number of ads played so far in each ad group.
* @param adDurationsUs The duration of each ad in each ad group, in microseconds. An element
* may be {@link C#TIME_UNSET} if the duration is not yet known.
* @param adResumePositionUs The position offset in the earliest unplayed ad at which to begin
* playback, in microseconds.
* @param contentDurationUs The content duration in microseconds, if known. {@link C#TIME_UNSET}
* otherwise.
* @param adPlaybackState The state of the period's ads.
*/
public SinglePeriodAdTimeline(Timeline contentTimeline, long[] adGroupTimesUs, int[] adCounts,
int[] adsLoadedCounts, int[] adsPlayedCounts, long[][] adDurationsUs, long adResumePositionUs,
long contentDurationUs) {
public SinglePeriodAdTimeline(Timeline contentTimeline, AdPlaybackState adPlaybackState) {
super(contentTimeline);
Assertions.checkState(contentTimeline.getPeriodCount() == 1);
Assertions.checkState(contentTimeline.getWindowCount() == 1);
this.adGroupTimesUs = adGroupTimesUs;
this.adCounts = adCounts;
this.adsLoadedCounts = adsLoadedCounts;
this.adsPlayedCounts = adsPlayedCounts;
this.adDurationsUs = adDurationsUs;
this.adResumePositionUs = adResumePositionUs;
this.contentDurationUs = contentDurationUs;
this.adPlaybackState = adPlaybackState;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
timeline.getPeriod(periodIndex, period, setIds);
period.set(period.id, period.uid, period.windowIndex, period.durationUs,
period.getPositionInWindowUs(), adGroupTimesUs, adCounts, adsLoadedCounts, adsPlayedCounts,
adDurationsUs, adResumePositionUs);
period.set(
period.id,
period.uid,
period.windowIndex,
period.durationUs,
period.getPositionInWindowUs(),
adPlaybackState);
return period;
}
......@@ -81,7 +59,7 @@ import com.google.android.exoplayer2.util.Assertions;
long defaultPositionProjectionUs) {
window = super.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
if (window.durationUs == C.TIME_UNSET) {
window.durationUs = contentDurationUs;
window.durationUs = adPlaybackState.contentDurationUs;
}
return window;
}
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.source.ads;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.net.Uri;
import com.google.android.exoplayer2.C;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/** Unit test for {@link AdPlaybackState}. */
@RunWith(RobolectricTestRunner.class)
@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE)
public final class AdPlaybackStateTest {
private static final long[] TEST_AD_GROUP_TMES_US = new long[] {0, C.msToUs(10_000)};
private static final Uri TEST_URI = Uri.EMPTY;
private AdPlaybackState state;
@Before
public void setUp() {
state = new AdPlaybackState(TEST_AD_GROUP_TMES_US);
}
@Test
public void testSetAdCount() {
assertThat(state.adGroups[0].count).isEqualTo(C.LENGTH_UNSET);
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1);
assertThat(state.adGroups[0].count).isEqualTo(1);
}
@Test
public void testSetAdUriBeforeAdCount() {
state = state.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI);
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2);
assertThat(state.adGroups[0].uris[0]).isNull();
assertThat(state.adGroups[0].states[0]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
assertThat(state.adGroups[0].uris[1]).isSameAs(TEST_URI);
assertThat(state.adGroups[0].states[1]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE);
}
@Test
public void testSetAdErrorBeforeAdCount() {
state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2);
assertThat(state.adGroups[0].uris[0]).isNull();
assertThat(state.adGroups[0].states[0]).isEqualTo(AdPlaybackState.AD_STATE_ERROR);
assertThat(state.adGroups[0].states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
}
@Test
public void testInitialNextAdIndexToPlay() {
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);
}
@Test
public void testNextAdIndexToPlayWithPlayedAd() {
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].states[1]).isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
assertThat(state.adGroups[0].states[2]).isEqualTo(AdPlaybackState.AD_STATE_AVAILABLE);
}
@Test
public void testNextAdIndexToPlaySkipsErrorAds() {
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);
state = state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1);
assertThat(state.adGroups[0].nextAdIndexToPlay).isEqualTo(2);
}
@Test
public void testSetAdStateTwiceThrows() {
state = state.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1);
state = state.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
try {
state.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
fail();
} catch (Exception e) {
// Expected.
}
}
@Test
public void testSkipAllWithoutAdCount() {
state = state.withSkippedAdGroup(0);
state = state.withSkippedAdGroup(1);
assertThat(state.adGroups[0].count).isEqualTo(0);
assertThat(state.adGroups[1].count).isEqualTo(0);
}
}
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
......@@ -177,21 +178,21 @@ public final class FakeTimeline extends Timeline {
} else {
int adGroups = windowDefinition.adGroupsPerPeriodCount;
long[] adGroupTimesUs = new long[adGroups];
int[] adCounts = new int[adGroups];
int[] adLoadedAndPlayedCounts = new int[adGroups];
long[][] adDurationsUs = new long[adGroups][];
long adResumePositionUs = 0;
long adGroupOffset = adGroups > 1 ? periodDurationUs / (adGroups - 1) : 0;
for (int i = 0; i < adGroups; i++) {
adGroupTimesUs[i] = i * adGroupOffset;
adCounts[i] = windowDefinition.adsPerAdGroupCount;
adLoadedAndPlayedCounts[i] = 0;
adDurationsUs[i] = new long[adCounts[i]];
}
AdPlaybackState adPlaybackState = new AdPlaybackState(adGroupTimesUs);
long[][] adDurationsUs = new long[adGroups][];
for (int i = 0; i < adGroups; i++) {
int adCount = windowDefinition.adsPerAdGroupCount;
adPlaybackState = adPlaybackState.withAdCount(i, adCount);
adDurationsUs[i] = new long[adCount];
Arrays.fill(adDurationsUs[i], AD_DURATION_US);
}
return period.set(id, uid, windowIndex, periodDurationUs, positionInWindowUs, adGroupTimesUs,
adCounts, adLoadedAndPlayedCounts, adLoadedAndPlayedCounts, adDurationsUs,
adResumePositionUs);
adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs);
return period.set(
id, uid, windowIndex, periodDurationUs, positionInWindowUs, adPlaybackState);
}
}
......
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