Add Util class for server-side inserted ads.

When working with SSAI ads, we need to easily convert positions between
the underlying stream and the media model that exposes ad groups. To
simplify this, we can add util methods (that are testable on their own).

In addition, we need an easy way to build AdPlaybackStates for SSAI
inserted ads. The metadata is obtained out-of-band and usually has the
ad group start and end times in the underlying stream only. Hence, we
also add a util method to add a new ad group based on this information.

PiperOrigin-RevId: 374369360
This commit is contained in:
tonihei 2021-05-18 09:57:22 +01:00 committed by Oliver Woodman
parent c7db9fb390
commit 378b3f6eb5
5 changed files with 991 additions and 34 deletions

View file

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.ads;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.common.collect.ObjectArrays.concat;
import static java.lang.Math.max;
import android.net.Uri;
@ -508,25 +507,48 @@ public final class AdPlaybackState implements Bundleable {
}
/**
* Returns an instance with the specified ad group times.
* Returns an instance with the specified ad group time.
*
* <p>If the number of ad groups differs, ad groups are either removed or empty ad groups are
* added.
*
* @param adGroupTimesUs The new ad group times, in microseconds.
* @param adGroupIndex The index of the ad group.
* @param adGroupTimeUs The new ad group time, in microseconds, or {@link C#TIME_END_OF_SOURCE} to
* indicate a postroll ad.
* @return The updated ad playback state.
*/
@CheckResult
public AdPlaybackState withAdGroupTimesUs(long... adGroupTimesUs) {
AdGroup[] adGroups =
adGroupTimesUs.length < adGroupCount
? Util.nullSafeArrayCopy(this.adGroups, adGroupTimesUs.length)
: adGroupTimesUs.length == adGroupCount
? this.adGroups
: concat(
this.adGroups,
createEmptyAdGroups(adGroupTimesUs.length - adGroupCount),
AdGroup.class);
public AdPlaybackState withAdGroupTimeUs(int adGroupIndex, long adGroupTimeUs) {
long[] adGroupTimesUs = Arrays.copyOf(this.adGroupTimesUs, this.adGroupCount);
adGroupTimesUs[adGroupIndex] = adGroupTimeUs;
return new AdPlaybackState(
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
}
/**
* Returns an instance with a new ad group.
*
* @param adGroupIndex The insertion index of the new group.
* @param adGroupTimeUs The ad group time, in microseconds, or {@link C#TIME_END_OF_SOURCE} to
* indicate a postroll ad.
* @return The updated ad playback state.
*/
@CheckResult
public AdPlaybackState withNewAdGroup(int adGroupIndex, long adGroupTimeUs) {
AdGroup newAdGroup = new AdGroup();
AdGroup[] adGroups = Util.nullSafeArrayAppend(this.adGroups, newAdGroup);
System.arraycopy(
/* src= */ adGroups,
/* srcPos= */ adGroupIndex,
/* dest= */ adGroups,
/* destPos= */ adGroupIndex + 1,
/* length= */ adGroupCount - adGroupIndex);
adGroups[adGroupIndex] = newAdGroup;
long[] adGroupTimesUs = Arrays.copyOf(this.adGroupTimesUs, adGroupCount + 1);
System.arraycopy(
/* src= */ adGroupTimesUs,
/* srcPos= */ adGroupIndex,
/* dest= */ adGroupTimesUs,
/* destPos= */ adGroupIndex + 1,
/* length= */ adGroupCount - adGroupIndex);
adGroupTimesUs[adGroupIndex] = adGroupTimeUs;
return new AdPlaybackState(
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
}
@ -606,6 +628,18 @@ public final class AdPlaybackState implements Bundleable {
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
}
/**
* Returns an instance with the specified ad durations, in microseconds, in the specified ad
* group.
*/
@CheckResult
public AdPlaybackState withAdDurationsUs(int adGroupIndex, long... adDurationsUs) {
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdDurationsUs(adDurationsUs);
return new AdPlaybackState(
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
}
/**
* Returns an instance with the specified ad resume position, in microseconds, relative to the
* start of the current ad.

View file

@ -73,35 +73,58 @@ public class AdPlaybackStateTest {
}
@Test
public void withAdGroupTimesUs_removingGroups_keepsRemainingGroups() {
AdPlaybackState state =
new AdPlaybackState(TEST_ADS_ID, new long[] {0, C.msToUs(10_000)})
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI);
public void withAdGroupTimeUs_updatesAdGroupTimeUs() {
AdPlaybackState state = new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0, 10_000);
state = state.withAdGroupTimesUs(new long[] {C.msToUs(3_000)});
state =
state
.withAdGroupTimeUs(/* adGroupIndex= */ 0, 3_000)
.withAdGroupTimeUs(/* adGroupIndex= */ 1, 6_000);
assertThat(state.adGroupCount).isEqualTo(1);
assertThat(state.adGroups[0].count).isEqualTo(2);
assertThat(state.adGroups[0].uris[1]).isSameInstanceAs(TEST_URI);
assertThat(state.adGroupCount).isEqualTo(2);
assertThat(state.adGroupTimesUs[0]).isEqualTo(3_000);
assertThat(state.adGroupTimesUs[1]).isEqualTo(6_000);
}
@Test
public void withAdGroupTimesUs_addingGroups_keepsExistingGroups() {
public void withNewAdGroup_addsGroupAndKeepsExistingGroups() {
AdPlaybackState state =
new AdPlaybackState(TEST_ADS_ID, new long[] {0, C.msToUs(10_000)})
new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 3_000, 6_000)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI)
.withSkippedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0);
state = state.withAdGroupTimesUs(new long[] {0, C.msToUs(3_000), C.msToUs(20_000)});
state =
state
.withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 1_000)
.withNewAdGroup(/* adGroupIndex= */ 2, /* adGroupTimeUs= */ 5_000)
.withNewAdGroup(/* adGroupIndex= */ 4, /* adGroupTimeUs= */ 8_000);
assertThat(state.adGroupCount).isEqualTo(3);
assertThat(state.adGroups[0].count).isEqualTo(2);
assertThat(state.adGroups[0].uris[1]).isSameInstanceAs(TEST_URI);
assertThat(state.adGroups[1].states[0]).isEqualTo(AdPlaybackState.AD_STATE_SKIPPED);
assertThat(state.adGroupCount).isEqualTo(5);
assertThat(state.adGroups[0].count).isEqualTo(C.INDEX_UNSET);
assertThat(state.adGroups[1].count).isEqualTo(2);
assertThat(state.adGroups[1].uris[1]).isSameInstanceAs(TEST_URI);
assertThat(state.adGroups[2].count).isEqualTo(C.INDEX_UNSET);
assertThat(state.adGroups[3].count).isEqualTo(1);
assertThat(state.adGroups[3].states[0]).isEqualTo(AdPlaybackState.AD_STATE_SKIPPED);
assertThat(state.adGroups[4].count).isEqualTo(C.INDEX_UNSET);
}
@Test
public void withAdDurationsUs_updatesAdDurations() {
AdPlaybackState state =
new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0, 10_000)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 2)
.withAdDurationsUs(new long[][] {new long[] {5_000, 6_000}, new long[] {7_000, 8_000}});
state = state.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ 1_000, 2_000);
assertThat(state.adGroups[0].durationsUs[0]).isEqualTo(5_000);
assertThat(state.adGroups[0].durationsUs[1]).isEqualTo(6_000);
assertThat(state.adGroups[1].durationsUs[0]).isEqualTo(1_000);
assertThat(state.adGroups[1].durationsUs[1]).isEqualTo(2_000);
}
@Test

View file

@ -0,0 +1,315 @@
/*
* Copyright 2021 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 java.lang.Math.max;
import androidx.annotation.CheckResult;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaPeriodId;
import com.google.android.exoplayer2.util.Util;
/** A static utility class with methods to work with server-side inserted ads. */
public final class ServerSideInsertedAdsUtil {
private ServerSideInsertedAdsUtil() {}
/**
* Adds a new server-side inserted ad group to an {@link AdPlaybackState}.
*
* @param adPlaybackState The existing {@link AdPlaybackState}.
* @param fromPositionUs The position in the underlying server-side inserted ads stream at which
* the ad group starts, in microseconds.
* @param toPositionUs The position in the underlying server-side inserted ads stream at which the
* ad group ends, in microseconds.
* @param contentResumeOffsetUs The timestamp offset which should be added to the content stream
* when resuming playback after the ad group. An offset of 0 collapses the ad group to a
* single insertion point, an offset of {@code toPositionUs-fromPositionUs} keeps the original
* stream timestamps after the ad group.
* @return The updated {@link AdPlaybackState}.
*/
@CheckResult
public static AdPlaybackState addAdGroupToAdPlaybackState(
AdPlaybackState adPlaybackState,
long fromPositionUs,
long toPositionUs,
long contentResumeOffsetUs) {
long adGroupInsertionPositionUs =
getMediaPeriodPositionUsForContent(
fromPositionUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState);
int insertionIndex = 0;
while (insertionIndex < adPlaybackState.adGroupCount
&& adPlaybackState.adGroupTimesUs[insertionIndex] != C.TIME_END_OF_SOURCE
&& adPlaybackState.adGroupTimesUs[insertionIndex] <= adGroupInsertionPositionUs) {
insertionIndex++;
}
long adDurationUs = toPositionUs - fromPositionUs;
adPlaybackState =
adPlaybackState
.withNewAdGroup(insertionIndex, adGroupInsertionPositionUs)
.withIsServerSideInserted(insertionIndex, /* isServerSideInserted= */ true)
.withAdCount(insertionIndex, /* adCount= */ 1)
.withAdDurationsUs(insertionIndex, adDurationUs)
.withContentResumeOffsetUs(insertionIndex, contentResumeOffsetUs);
long followingAdGroupTimeUsOffset = -adDurationUs + contentResumeOffsetUs;
for (int i = insertionIndex + 1; i < adPlaybackState.adGroupCount; i++) {
if (adPlaybackState.adGroupTimesUs[i] != C.TIME_END_OF_SOURCE) {
adPlaybackState =
adPlaybackState.withAdGroupTimeUs(
/* adGroupIndex= */ i,
adPlaybackState.adGroupTimesUs[i] + followingAdGroupTimeUsOffset);
}
}
return adPlaybackState;
}
/**
* Returns the duration of the underlying server-side inserted ads stream for the current {@link
* Timeline.Period} in the {@link Player}.
*
* @param player The {@link Player}.
* @param adPlaybackState The {@link AdPlaybackState} defining the ad groups.
* @return The duration of the underlying server-side inserted ads stream, in microseconds, or
* {@link C#TIME_UNSET} if it can't be determined.
*/
public static long getStreamDurationUs(Player player, AdPlaybackState adPlaybackState) {
Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) {
return C.TIME_UNSET;
}
Timeline.Period period =
timeline.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period());
if (period.durationUs == C.TIME_UNSET) {
return C.TIME_UNSET;
}
return getStreamPositionUsForContent(
period.durationUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState);
}
/**
* Returns the position in the underlying server-side inserted ads stream for the current playback
* position in the {@link Player}.
*
* @param player The {@link Player}.
* @param adPlaybackState The {@link AdPlaybackState} defining the ad groups.
* @return The position in the underlying server-side inserted ads stream, in microseconds, or
* {@link C#TIME_UNSET} if it can't be determined.
*/
public static long getStreamPositionUs(Player player, AdPlaybackState adPlaybackState) {
Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) {
return C.TIME_UNSET;
}
Timeline.Period period =
timeline.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period());
if (!Util.areEqual(period.getAdsId(), adPlaybackState.adsId)) {
return C.TIME_UNSET;
}
if (player.isPlayingAd()) {
int adGroupIndex = player.getCurrentAdGroupIndex();
int adIndexInAdGroup = player.getCurrentAdIndexInAdGroup();
long adPositionUs = C.msToUs(player.getCurrentPosition());
return getStreamPositionUsForAd(
adPositionUs, adGroupIndex, adIndexInAdGroup, adPlaybackState);
}
long periodPositionUs = C.msToUs(player.getCurrentPosition()) - period.getPositionInWindowUs();
return getStreamPositionUsForContent(
periodPositionUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState);
}
/**
* Returns the position in the underlying server-side inserted ads stream for a position in a
* {@link MediaPeriod}.
*
* @param positionUs The position in the {@link MediaPeriod}, in microseconds.
* @param mediaPeriodId The {@link MediaPeriodId} of the {@link MediaPeriod}.
* @param adPlaybackState The {@link AdPlaybackState} defining the ad groups.
* @return The position in the underlying server-side inserted ads stream, in microseconds.
*/
public static long getStreamPositionUs(
long positionUs, MediaPeriodId mediaPeriodId, AdPlaybackState adPlaybackState) {
return mediaPeriodId.isAd()
? getStreamPositionUsForAd(
positionUs, mediaPeriodId.adGroupIndex, mediaPeriodId.adIndexInAdGroup, adPlaybackState)
: getStreamPositionUsForContent(
positionUs, mediaPeriodId.nextAdGroupIndex, adPlaybackState);
}
/**
* Returns the position in a {@link MediaPeriod} for a position in the underlying server-side
* inserted ads stream.
*
* @param positionUs The position in the underlying server-side inserted ads stream, in
* microseconds.
* @param mediaPeriodId The {@link MediaPeriodId} of the {@link MediaPeriod}.
* @param adPlaybackState The {@link AdPlaybackState} defining the ad groups.
* @return The position in the {@link MediaPeriod}, in microseconds.
*/
public static long getMediaPeriodPositionUs(
long positionUs, MediaPeriodId mediaPeriodId, AdPlaybackState adPlaybackState) {
return mediaPeriodId.isAd()
? getMediaPeriodPositionUsForAd(
positionUs, mediaPeriodId.adGroupIndex, mediaPeriodId.adIndexInAdGroup, adPlaybackState)
: getMediaPeriodPositionUsForContent(
positionUs, mediaPeriodId.nextAdGroupIndex, adPlaybackState);
}
/**
* Returns the position in the underlying server-side inserted ads stream for a position in an ad
* {@link MediaPeriod}.
*
* @param positionUs The position in the ad {@link MediaPeriod}, in microseconds.
* @param adGroupIndex The ad group index of the ad.
* @param adIndexInAdGroup The index of the ad in the ad group.
* @param adPlaybackState The {@link AdPlaybackState} defining the ad groups.
* @return The position in the underlying server-side inserted ads stream, in microseconds.
*/
public static long getStreamPositionUsForAd(
long positionUs, int adGroupIndex, int adIndexInAdGroup, AdPlaybackState adPlaybackState) {
positionUs += adPlaybackState.adGroupTimesUs[adGroupIndex];
for (int i = 0; i < adGroupIndex; i++) {
for (int j = 0; j < getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i); j++) {
positionUs += adPlaybackState.adGroups[i].durationsUs[j];
}
positionUs -= adPlaybackState.adGroups[i].contentResumeOffsetUs;
}
if (adIndexInAdGroup < getAdCountInGroup(adPlaybackState, adGroupIndex)) {
for (int i = 0; i < adIndexInAdGroup; i++) {
positionUs += adPlaybackState.adGroups[adGroupIndex].durationsUs[i];
}
}
return positionUs;
}
/**
* Returns the position in an ad {@link MediaPeriod} for a position in the underlying server-side
* inserted ads stream.
*
* @param positionUs The position in the underlying server-side inserted ads stream, in
* microseconds.
* @param adGroupIndex The ad group index of the ad.
* @param adIndexInAdGroup The index of the ad in the ad group.
* @param adPlaybackState The {@link AdPlaybackState} defining the ad groups.
* @return The position in the ad {@link MediaPeriod}, in microseconds.
*/
public static long getMediaPeriodPositionUsForAd(
long positionUs, int adGroupIndex, int adIndexInAdGroup, AdPlaybackState adPlaybackState) {
positionUs -= adPlaybackState.adGroupTimesUs[adGroupIndex];
for (int i = 0; i < adGroupIndex; i++) {
for (int j = 0; j < getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i); j++) {
positionUs -= adPlaybackState.adGroups[i].durationsUs[j];
}
positionUs += adPlaybackState.adGroups[i].contentResumeOffsetUs;
}
if (adIndexInAdGroup < getAdCountInGroup(adPlaybackState, adGroupIndex)) {
for (int i = 0; i < adIndexInAdGroup; i++) {
positionUs -= adPlaybackState.adGroups[adGroupIndex].durationsUs[i];
}
}
return positionUs;
}
/**
* Returns the position in the underlying server-side inserted ads stream for a position in a
* content {@link MediaPeriod}.
*
* @param positionUs The position in the content {@link MediaPeriod}, in microseconds.
* @param nextAdGroupIndex The next ad group index after the content, or {@link C#INDEX_UNSET} if
* there is no following ad group. Ad groups from this index are not used to adjust the
* position.
* @param adPlaybackState The {@link AdPlaybackState} defining the ad groups.
* @return The position in the underlying server-side inserted ads stream, in microseconds.
*/
public static long getStreamPositionUsForContent(
long positionUs, int nextAdGroupIndex, AdPlaybackState adPlaybackState) {
long totalAdDurationBeforePositionUs = 0;
if (nextAdGroupIndex == C.INDEX_UNSET) {
nextAdGroupIndex = adPlaybackState.adGroupCount;
}
for (int i = 0; i < nextAdGroupIndex; i++) {
if (adPlaybackState.adGroupTimesUs[i] == C.TIME_END_OF_SOURCE
|| adPlaybackState.adGroupTimesUs[i] > positionUs) {
break;
}
long adGroupStreamStartPositionUs =
adPlaybackState.adGroupTimesUs[i] + totalAdDurationBeforePositionUs;
for (int j = 0; j < getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i); j++) {
totalAdDurationBeforePositionUs += adPlaybackState.adGroups[i].durationsUs[j];
}
totalAdDurationBeforePositionUs -= adPlaybackState.adGroups[i].contentResumeOffsetUs;
long adGroupResumePositionUs =
adPlaybackState.adGroupTimesUs[i] + adPlaybackState.adGroups[i].contentResumeOffsetUs;
if (adGroupResumePositionUs > positionUs) {
// The position is inside the ad group.
return max(adGroupStreamStartPositionUs, positionUs + totalAdDurationBeforePositionUs);
}
}
return positionUs + totalAdDurationBeforePositionUs;
}
/**
* Returns the position in a content {@link MediaPeriod} for a position in the underlying
* server-side inserted ads stream.
*
* @param positionUs The position in the underlying server-side inserted ads stream, in
* microseconds.
* @param nextAdGroupIndex The next ad group index after the content, or {@link C#INDEX_UNSET} if
* there is no following ad group. Ad groups from this index are not used to adjust the
* position.
* @param adPlaybackState The {@link AdPlaybackState} defining the ad groups.
* @return The position in the content {@link MediaPeriod}, in microseconds.
*/
public static long getMediaPeriodPositionUsForContent(
long positionUs, int nextAdGroupIndex, AdPlaybackState adPlaybackState) {
long totalAdDurationBeforePositionUs = 0;
if (nextAdGroupIndex == C.INDEX_UNSET) {
nextAdGroupIndex = adPlaybackState.adGroupCount;
}
for (int i = 0; i < nextAdGroupIndex; i++) {
if (adPlaybackState.adGroupTimesUs[i] == C.TIME_END_OF_SOURCE
|| adPlaybackState.adGroupTimesUs[i] > positionUs - totalAdDurationBeforePositionUs) {
break;
}
for (int j = 0; j < getAdCountInGroup(adPlaybackState, /* adGroupIndex= */ i); j++) {
totalAdDurationBeforePositionUs += adPlaybackState.adGroups[i].durationsUs[j];
}
totalAdDurationBeforePositionUs -= adPlaybackState.adGroups[i].contentResumeOffsetUs;
long adGroupResumePositionUs =
adPlaybackState.adGroupTimesUs[i] + adPlaybackState.adGroups[i].contentResumeOffsetUs;
if (adGroupResumePositionUs > positionUs - totalAdDurationBeforePositionUs) {
// The position is inside the ad group.
return max(adPlaybackState.adGroupTimesUs[i], positionUs - totalAdDurationBeforePositionUs);
}
}
return positionUs - totalAdDurationBeforePositionUs;
}
/**
* Returns the number of ads in an ad group, treating an unknown number as zero ads.
*
* @param adPlaybackState The {@link AdPlaybackState}.
* @param adGroupIndex The index of the ad group.
* @return The number of ads in the ad group.
*/
public static int getAdCountInGroup(AdPlaybackState adPlaybackState, int adGroupIndex) {
return adPlaybackState.adGroups[adGroupIndex].count == C.LENGTH_UNSET
? 0
: adPlaybackState.adGroups[adGroupIndex].count;
}
}

View file

@ -10479,8 +10479,9 @@ public final class ExoPlayerTest {
/* durationUs= */ C.TIME_UNSET,
initialAdPlaybackState));
AdPlaybackState updatedAdPlaybackState =
initialAdPlaybackState.withAdGroupTimesUs(
/* adGroupTimesUs...= */ firstSampleTimeUs + 18 * C.MICROS_PER_SECOND);
initialAdPlaybackState.withAdGroupTimeUs(
/* adGroupIndex= */ 0,
/* adGroupTimeUs= */ firstSampleTimeUs + 18 * C.MICROS_PER_SECOND);
Timeline updatedTimeline =
new FakeTimeline(
new TimelineWindowDefinition(

View file

@ -0,0 +1,584 @@
/*
* Copyright 2021 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.android.exoplayer2.source.ads.ServerSideInsertedAdsUtil.addAdGroupToAdPlaybackState;
import static com.google.android.exoplayer2.source.ads.ServerSideInsertedAdsUtil.getAdCountInGroup;
import static com.google.android.exoplayer2.source.ads.ServerSideInsertedAdsUtil.getMediaPeriodPositionUsForAd;
import static com.google.android.exoplayer2.source.ads.ServerSideInsertedAdsUtil.getMediaPeriodPositionUsForContent;
import static com.google.android.exoplayer2.source.ads.ServerSideInsertedAdsUtil.getStreamPositionUsForAd;
import static com.google.android.exoplayer2.source.ads.ServerSideInsertedAdsUtil.getStreamPositionUsForContent;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link ServerSideInsertedAdsUtil}. */
@RunWith(AndroidJUnit4.class)
public final class ServerSideInsertedAdsUtilTest {
private static final Object ADS_ID = new Object();
@Test
public void addAdGroupToAdPlaybackState_insertsCorrectAdGroupData() {
AdPlaybackState state =
new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE);
// stream: 0-- content --4300-- ad1 --4500-- content
// content timeline: 0-4300 - [ad1] - 4700-end
state =
addAdGroupToAdPlaybackState(
state,
/* fromPositionUs= */ 4300,
/* toPositionUs= */ 4500,
/* contentResumeOffsetUs= */ 400);
assertThat(state)
.isEqualTo(
new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 4300, C.TIME_END_OF_SOURCE)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 400)
.withAdDurationsUs(new long[][] {new long[] {200}, new long[0]}));
// stream: 0-- content --2100-- ad1 --2400-- content --4300-- ad2 --4500-- content
// content timeline: 0-2100 - [ad1] - 2100-4000 - [ad2] - 4400-end
state =
addAdGroupToAdPlaybackState(
state,
/* fromPositionUs= */ 2100,
/* toPositionUs= */ 2400,
/* contentResumeOffsetUs= */ 0);
assertThat(state)
.isEqualTo(
new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 2100, 4000, C.TIME_END_OF_SOURCE)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true)
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 400)
.withAdDurationsUs(new long[][] {new long[] {300}, new long[] {200}, new long[0]}));
// stream: 0-- ad1 --100-- content --2100-- ad2 --2400-- content --4300-- ad3 --4500-- content
// content timeline: 0 - [ad1] - 50-2050 -[ad2] - 2050-3950 - [ad3] - 4350-end
state =
addAdGroupToAdPlaybackState(
state,
/* fromPositionUs= */ 0,
/* toPositionUs= */ 100,
/* contentResumeOffsetUs= */ 50);
assertThat(state)
.isEqualTo(
new AdPlaybackState(
ADS_ID, /* adGroupTimesUs...= */ 0, 2050, 3950, C.TIME_END_OF_SOURCE)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1)
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 50)
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 400)
.withAdDurationsUs(
new long[][] {
new long[] {100}, new long[] {300}, new long[] {200}, new long[0]
}));
// stream: 0-- ad1 --100-- c --2100-- ad2 --2400-- c --4300-- ad3 --4500-- c --5000-- ad4 --6000
// content timeline: 0 - [ad1] - 50-2050 -[ad2] - 2050-3950 - [ad3] - 4350-4850 - [ad4] - 4850
state =
addAdGroupToAdPlaybackState(
state,
/* fromPositionUs= */ 5000,
/* toPositionUs= */ 6000,
/* contentResumeOffsetUs= */ 0);
assertThat(state)
.isEqualTo(
new AdPlaybackState(
ADS_ID, /* adGroupTimesUs...= */ 0, 2050, 3950, 4850, C.TIME_END_OF_SOURCE)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 3, /* adCount= */ 1)
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 3, /* isServerSideInserted= */ true)
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 50)
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 400)
.withAdDurationsUs(
new long[][] {
new long[] {100},
new long[] {300},
new long[] {200},
new long[] {1000},
new long[0]
}));
}
@Test
public void getStreamPositionUsForAd_returnsCorrectPositions() {
// stream: 0-- ad1 --200-- content --2100-- ad2 --2300-- content --4300-- ad3 --4500-- content
// content timeline: 0 - [ad1] - 100-2000 -[ad2] - 2000-4000 - [ad3] - 4400-end
AdPlaybackState state =
new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 2000, 4000)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 3)
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 100)
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 0)
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 400)
.withAdDurationsUs(
new long[][] {
new long[] {150, 50},
new long[] {200},
new long[] {50, 50, 100}
});
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 0, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(0);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 100, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(100);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 200, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(200);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ -50, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(100);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 0, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(150);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 100, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(250);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ -50, /* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(2050);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 0, /* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(2100);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 200, /* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(2300);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 300, /* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(2400);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ -50, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(4250);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 0, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(4300);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 100, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(4400);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ -50, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(4300);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 0, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(4350);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 100, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(4450);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ -50, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 2, state))
.isEqualTo(4350);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 50, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 2, state))
.isEqualTo(4450);
assertThat(
getStreamPositionUsForAd(
/* positionUs= */ 150, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 2, state))
.isEqualTo(4550);
}
@Test
public void getMediaPeriodPositionUsForAd_returnsCorrectPositions() {
// stream: 0-- ad1 --200-- content --2100-- ad2 --2300-- content --4300-- ad3 --4500-- content
// content timeline: 0 - [ad1] - 100-2000 -[ad2] - 2000-4000 - [ad3] - 4400-end
AdPlaybackState state =
new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 2000, 4000)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 3)
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 100)
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 0)
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 400)
.withAdDurationsUs(
new long[][] {
new long[] {150, 50},
new long[] {200},
new long[] {50, 50, 100}
});
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 0, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(0);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 100, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(100);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 100, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(-50);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 200, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(200);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 200, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(50);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 300, /* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(150);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 2000, /* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(-100);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 2100, /* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(0);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 2300, /* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(200);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4300, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(0);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4300, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(-50);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4300, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 2, state))
.isEqualTo(-100);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4400, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, state))
.isEqualTo(100);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4400, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(50);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4400, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 2, state))
.isEqualTo(0);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4500, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(150);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4500, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 2, state))
.isEqualTo(100);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4700, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, state))
.isEqualTo(350);
assertThat(
getMediaPeriodPositionUsForAd(
/* positionUs= */ 4700, /* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 2, state))
.isEqualTo(300);
}
@Test
public void getStreamPositionUsForContent_returnsCorrectPositions() {
// stream: 0-- ad1 --200-- content --2100-- ad2 --2300-- content --4300-- ad3 --4500-- content
// content timeline: 0 - [ad1] - 100-2000 -[ad2] - 2000-4000 - [ad3] - 4400-end
AdPlaybackState state =
new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 2000, 4000)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 3)
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 100)
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 0)
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 400)
.withAdDurationsUs(
new long[][] {
new long[] {150, 50},
new long[] {200},
new long[] {50, 50, 100}
});
assertThat(getStreamPositionUsForContent(/* positionUs= */ 0, /* nextAdGroupIndex= */ 0, state))
.isEqualTo(0);
assertThat(getStreamPositionUsForContent(/* positionUs= */ 0, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(100);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 0, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(100);
assertThat(
getStreamPositionUsForContent(/* positionUs= */ 50, /* nextAdGroupIndex= */ 0, state))
.isEqualTo(50);
assertThat(
getStreamPositionUsForContent(/* positionUs= */ 50, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(150);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 50, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(150);
assertThat(
getStreamPositionUsForContent(/* positionUs= */ 100, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(200);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 100, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(200);
assertThat(
getStreamPositionUsForContent(/* positionUs= */ 1999, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(2099);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 1999, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(2099);
assertThat(
getStreamPositionUsForContent(/* positionUs= */ 2000, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(2100);
assertThat(
getStreamPositionUsForContent(/* positionUs= */ 2000, /* nextAdGroupIndex= */ 2, state))
.isEqualTo(2300);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 2000, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(2300);
assertThat(
getStreamPositionUsForContent(/* positionUs= */ 3999, /* nextAdGroupIndex= */ 2, state))
.isEqualTo(4299);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 3999, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(4299);
assertThat(
getStreamPositionUsForContent(/* positionUs= */ 4000, /* nextAdGroupIndex= */ 2, state))
.isEqualTo(4300);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 4000, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(4300);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 4200, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(4300);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 4300, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(4400);
assertThat(
getStreamPositionUsForContent(
/* positionUs= */ 4400, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(4500);
}
@Test
public void getMediaPeriodPositionUsForContent_returnsCorrectPositions() {
// stream: 0-- ad1 --200-- content --2100-- ad2 --2300-- content --4300-- ad3 --4500-- content
// content timeline: 0 - [ad1] - 100-2000 -[ad2] - 2000-4000 - [ad3] - 4400-end
AdPlaybackState state =
new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 2000, 4000)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 3)
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 100)
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 0)
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 400)
.withAdDurationsUs(
new long[][] {
new long[] {150, 50},
new long[] {200},
new long[] {50, 50, 100}
});
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 0, /* nextAdGroupIndex= */ 0, state))
.isEqualTo(0);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 0, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(0);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 0, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(0);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 100, /* nextAdGroupIndex= */ 0, state))
.isEqualTo(100);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 100, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(0);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 100, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(0);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 200, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(100);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 200, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(100);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 2099, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(1999);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 2099, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(1999);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 2100, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(2000);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 2100, /* nextAdGroupIndex= */ 2, state))
.isEqualTo(2000);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 2100, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(2000);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 2300, /* nextAdGroupIndex= */ 1, state))
.isEqualTo(2200);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 2300, /* nextAdGroupIndex= */ 2, state))
.isEqualTo(2000);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 2300, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(2000);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 4299, /* nextAdGroupIndex= */ 2, state))
.isEqualTo(3999);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 4299, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(3999);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 4300, /* nextAdGroupIndex= */ 2, state))
.isEqualTo(4000);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 4300, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(4200);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 4500, /* nextAdGroupIndex= */ 2, state))
.isEqualTo(4200);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 4500, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(4400);
assertThat(
getMediaPeriodPositionUsForContent(
/* positionUs= */ 4700, /* nextAdGroupIndex= */ C.INDEX_UNSET, state))
.isEqualTo(4600);
}
@Test
public void getAdCountInGroup_withUnsetCount_returnsZero() {
AdPlaybackState state = new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 2000);
assertThat(getAdCountInGroup(state, /* adGroupIndex= */ 0)).isEqualTo(0);
assertThat(getAdCountInGroup(state, /* adGroupIndex= */ 1)).isEqualTo(0);
}
@Test
public void getAdCountInGroup_withSetCount_returnsCount() {
AdPlaybackState state =
new AdPlaybackState(ADS_ID, /* adGroupTimesUs...= */ 0, 2000)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 4)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 6);
assertThat(getAdCountInGroup(state, /* adGroupIndex= */ 0)).isEqualTo(4);
assertThat(getAdCountInGroup(state, /* adGroupIndex= */ 1)).isEqualTo(6);
}
}