Add helper method to split the AdPlaybackState

PiperOrigin-RevId: 416543473
This commit is contained in:
bachinger 2021-12-15 14:30:00 +00:00 committed by tonihei
parent 2f1e06da60
commit 47652c485b
2 changed files with 634 additions and 0 deletions

View File

@ -15,14 +15,19 @@
*/
package androidx.media3.exoplayer.ima;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import android.content.Context;
import android.os.Looper;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.media3.common.AdOverlayInfo;
import androidx.media3.common.AdPlaybackState;
import androidx.media3.common.AdViewProvider;
import androidx.media3.common.C;
import androidx.media3.common.Timeline;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSchemeDataSource;
import androidx.media3.datasource.DataSourceUtil;
@ -43,10 +48,13 @@ import com.google.ads.interactivemedia.v3.api.UiElement;
import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Utilities for working with IMA SDK and IMA extension data types. */
@ -255,5 +263,125 @@ import java.util.Set;
}
}
/**
* Splits an {@link AdPlaybackState} into a separate {@link AdPlaybackState} for each period of a
* content timeline. Ad group times are expected to not take previous ad duration into account and
* needs to be translated to the actual position in the {@code contentTimeline} by adding prior ad
* durations.
*
* <p>If a period is enclosed by an ad group, the period is considered an ad period and gets an ad
* playback state assigned with a single ad in a single ad group. The duration of the ad is set to
* the duration of the period. All other periods are considered content periods with an empty ad
* playback state without any ads.
*
* @param adPlaybackState The ad playback state to be split.
* @param contentTimeline The content timeline for each period of which to create an {@link
* AdPlaybackState}.
* @return A map of ad playback states for each period UID in the content timeline.
*/
public static ImmutableMap<Object, AdPlaybackState> splitAdPlaybackStateForPeriods(
AdPlaybackState adPlaybackState, Timeline contentTimeline) {
Timeline.Period period = new Timeline.Period();
if (contentTimeline.getPeriodCount() == 1) {
// A single period gets the entire ad playback state that may contain multiple ad groups.
return ImmutableMap.of(
checkNotNull(
contentTimeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).uid),
adPlaybackState);
}
int periodIndex = 0;
long totalElapsedContentDurationUs = 0;
Object adsId = checkNotNull(adPlaybackState.adsId);
AdPlaybackState contentOnlyAdPlaybackState = new AdPlaybackState(adsId);
Map<Object, AdPlaybackState> adPlaybackStates = new HashMap<>();
for (int i = adPlaybackState.removedAdGroupCount; i < adPlaybackState.adGroupCount; i++) {
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(/* adGroupIndex= */ i);
if (adGroup.timeUs == C.TIME_END_OF_SOURCE) {
checkState(i == adPlaybackState.adGroupCount - 1);
// The last ad group is a placeholder for a potential post roll. We can just stop here.
break;
}
// The ad group start timeUs is in content position. We need to add the ad
// duration before the ad group to translate the start time to the position in the period.
long adGroupDurationUs = getTotalDurationUs(adGroup.durationsUs);
long elapsedAdGroupAdDurationUs = 0;
for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) {
contentTimeline.getPeriod(j, period, /* setIds= */ true);
if (totalElapsedContentDurationUs < adGroup.timeUs) {
// Period starts before the ad group, so it is a content period.
adPlaybackStates.put(checkNotNull(period.uid), contentOnlyAdPlaybackState);
totalElapsedContentDurationUs += period.durationUs;
} else {
long periodStartUs = totalElapsedContentDurationUs + elapsedAdGroupAdDurationUs;
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs) {
// The period ends before the end of the ad group, so it is an ad period (Note: An ad
// reported by the IMA SDK may span multiple periods).
adPlaybackStates.put(
checkNotNull(period.uid),
splitAdGroupForPeriod(adsId, adGroup, periodStartUs, period.durationUs));
elapsedAdGroupAdDurationUs += period.durationUs;
} else {
// Period is after the current ad group. Continue with next ad group.
break;
}
}
// Increment the period index to the next unclassified period.
periodIndex++;
}
}
// The remaining periods end after the last ad group, so these are content periods.
for (int i = periodIndex; i < contentTimeline.getPeriodCount(); i++) {
contentTimeline.getPeriod(i, period, /* setIds= */ true);
adPlaybackStates.put(checkNotNull(period.uid), contentOnlyAdPlaybackState);
}
return ImmutableMap.copyOf(adPlaybackStates);
}
private static AdPlaybackState splitAdGroupForPeriod(
Object adsId, AdPlaybackState.AdGroup adGroup, long periodStartUs, long periodDurationUs) {
checkState(adGroup.timeUs <= periodStartUs);
AdPlaybackState adPlaybackState =
new AdPlaybackState(checkNotNull(adsId), /* adGroupTimesUs...= */ 0)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdDurationsUs(/* adGroupIndex= */ 0, periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
long periodEndUs = periodStartUs + periodDurationUs;
long adDurationsUs = 0;
for (int i = 0; i < adGroup.count; i++) {
adDurationsUs += adGroup.durationsUs[i];
if (periodEndUs == adGroup.timeUs + adDurationsUs) {
// Map the state of the global ad state to the period specific ad state.
switch (adGroup.states[i]) {
case AdPlaybackState.AD_STATE_PLAYED:
adPlaybackState =
adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
break;
case AdPlaybackState.AD_STATE_SKIPPED:
adPlaybackState =
adPlaybackState.withSkippedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
break;
case AdPlaybackState.AD_STATE_ERROR:
adPlaybackState =
adPlaybackState.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
break;
default:
// Do nothing.
break;
}
break;
}
}
return adPlaybackState;
}
private static long getTotalDurationUs(long[] durationsUs) {
long totalDurationUs = 0;
for (long adDurationUs : durationsUs) {
totalDurationUs += adDurationUs;
}
return totalDurationUs;
}
private ImaUtil() {}
}

View File

@ -0,0 +1,506 @@
/*
* Copyright (C) 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 androidx.media3.exoplayer.ima;
import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US;
import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
import static com.google.common.truth.Truth.assertThat;
import android.util.Pair;
import androidx.media3.common.AdPlaybackState;
import androidx.media3.common.C;
import androidx.media3.common.Timeline;
import androidx.media3.test.utils.FakeTimeline;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link ImaUtil}. */
@RunWith(AndroidJUnit4.class)
public class ImaUtilTest {
@Test
public void splitAdPlaybackStateForPeriods_emptyTimeline_emptyMapOfAdPlaybackStates() {
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "", 0, 20_000, C.TIME_END_OF_SOURCE);
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, Timeline.EMPTY);
assertThat(adPlaybackStates).isEmpty();
}
@Test
public void splitAdPlaybackStateForPeriods_singlePeriod_doesNotSplit() {
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "", 0, 20_000, C.TIME_END_OF_SOURCE);
FakeTimeline singlePeriodTimeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, singlePeriodTimeline);
assertThat(adPlaybackStates).hasSize(1);
assertThat(adPlaybackStates).containsEntry(new Pair<>(0L, 0), adPlaybackState);
}
@Test
public void splitAdPlaybackStateForPeriods_livePlaceholder_isIgnored() {
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "", C.TIME_END_OF_SOURCE)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline singlePeriodTimeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(/* periodCount= */ 3, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, singlePeriodTimeline);
assertThat(adPlaybackStates).hasSize(3);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 1)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 2)).adGroupCount).isEqualTo(0);
}
@Test
public void splitAdPlaybackStateForPeriods_noAds_splitToEmptyAdPlaybackStates() {
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ "adsId");
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(/* periodCount= */ 11, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(11);
for (AdPlaybackState periodAdPlaybackState : adPlaybackStates.values()) {
assertThat(periodAdPlaybackState.adsId).isEqualTo("adsId");
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(0);
}
}
@Test
public void splitAdPlaybackStateForPeriods_twoPrerollAds_splitToFirstTwoPeriods() {
int periodCount = 4;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId", /* adGroupTimesUs... */ 0)
.withAdCount(/* adGroupIndex= */ 0, 2)
.withAdDurationsUs(
/* adGroupIndex= */ 0,
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + periodDurationUs,
periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
for (int i = 0; i < 2; i++) {
Pair<Long, Integer> periodUid = new Pair<>(0L, i);
AdPlaybackState periodAdPlaybackState = adPlaybackStates.get(periodUid);
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(1);
assertThat(periodAdPlaybackState.adsId).isEqualTo("adsId");
assertThat(periodAdPlaybackState.getAdGroup(0).timeUs).isEqualTo(0);
assertThat(periodAdPlaybackState.getAdGroup(0).isServerSideInserted).isTrue();
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs).hasLength(1);
int adDurationUs = i == 0 ? 125_500_000 : 2_500_000;
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs[0]).isEqualTo(adDurationUs);
}
assertThat(adPlaybackStates.get(new Pair<>(0L, 2)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).adGroupCount).isEqualTo(0);
}
@Test
public void splitAdPlaybackStateForPeriods_onePrerollAdGroup_splitToFirstThreePeriods() {
int periodCount = 4;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId", /* adGroupTimesUs... */ 0)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAdDurationsUs(
/* adGroupIndex= */ 0,
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 3 * periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
for (int i = 0; i < 3; i++) {
Pair<Long, Integer> periodUid = new Pair<>(0L, i);
AdPlaybackState periodAdPlaybackState = adPlaybackStates.get(periodUid);
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs).hasLength(1);
int adDurationUs = i == 0 ? 125_500_000 : 2_500_000;
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs[0]).isEqualTo(adDurationUs);
}
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).adGroupCount).isEqualTo(0);
}
@Test
public void splitAdPlaybackStateForPeriods_twoMidrollAds_splitToMiddleTwoPeriods() {
int periodCount = 4;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(
/* adsId= */ "adsId", DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + periodDurationUs)
.withAdCount(/* adGroupIndex= */ 0, 2)
.withAdDurationsUs(/* adGroupIndex= */ 0, periodDurationUs, periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).adGroupCount).isEqualTo(0);
for (int i = 1; i < 3; i++) {
Pair<Long, Integer> periodUid = new Pair<>(0L, i);
AdPlaybackState periodAdPlaybackState = adPlaybackStates.get(periodUid);
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(1);
assertThat(periodAdPlaybackState.getAdGroup(0).timeUs).isEqualTo(0);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs).hasLength(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs[0]).isEqualTo(2_500_000);
}
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).adGroupCount).isEqualTo(0);
}
@Test
public void splitAdPlaybackStateForPeriods_oneMidrollAdGroupOneAd_adSpansTwoPeriods() {
int periodCount = 5;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(
/* adsId= */ "adsId", DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + periodDurationUs)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAdDurationsUs(/* adGroupIndex= */ 0, 2 * periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).adGroupCount).isEqualTo(0);
for (int i = 1; i < 3; i++) {
Pair<Long, Integer> periodUid = new Pair<>(0L, i);
AdPlaybackState periodAdPlaybackState = adPlaybackStates.get(periodUid);
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs).hasLength(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs[0]).isEqualTo(2_000_000);
}
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 4)).adGroupCount).isEqualTo(0);
}
@Test
public void splitAdPlaybackStateForPeriods_oneMidrollAdGroupTwoAds_eachAdSplitsToOnePeriod() {
int periodCount = 5;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(
/* adsId= */ "adsId", DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + periodDurationUs)
.withAdCount(/* adGroupIndex= */ 0, 2)
.withAdDurationsUs(/* adGroupIndex= */ 0, periodDurationUs, periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).adGroupCount).isEqualTo(0);
for (int i = 1; i < 3; i++) {
Pair<Long, Integer> periodUid = new Pair<>(0L, i);
AdPlaybackState periodAdPlaybackState = adPlaybackStates.get(periodUid);
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs).hasLength(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs[0]).isEqualTo(2_000_000);
}
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 4)).adGroupCount).isEqualTo(0);
}
@Test
public void splitAdPlaybackStateForPeriods_twoPostrollAds_splitToLastTwoPeriods() {
int periodCount = 4;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(
/* adsId= */ "adsId",
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 2 * periodDurationUs)
.withAdCount(/* adGroupIndex= */ 0, 2)
.withAdDurationsUs(/* adGroupIndex= */ 0, periodDurationUs, periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 1)).adGroupCount).isEqualTo(0);
for (int i = 2; i < periodCount; i++) {
Pair<Long, Integer> periodUid = new Pair<>(0L, i);
AdPlaybackState periodAdPlaybackState = adPlaybackStates.get(periodUid);
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs).hasLength(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs[0]).isEqualTo(2_500_000);
}
}
@Test
public void splitAdPlaybackStateForPeriods_onePostrollAdGroup_splitToLastThreePeriods() {
int periodCount = 7;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(
/* adsId= */ "adsId",
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 4 * periodDurationUs)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAdDurationsUs(/* adGroupIndex= */ 0, 3 * periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 1)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 2)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).adGroupCount).isEqualTo(0);
for (int i = 4; i < adPlaybackStates.size(); i++) {
Pair<Long, Integer> periodUid = new Pair<>(0L, i);
AdPlaybackState periodAdPlaybackState = adPlaybackStates.get(periodUid);
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs).hasLength(1);
assertThat(periodAdPlaybackState.getAdGroup(0).durationsUs[0]).isEqualTo(periodDurationUs);
}
}
@Test
public void splitAdPlaybackStateForPeriods_preMidAndPostrollAdGroup_splitCorrectly() {
int periodCount = 11;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId", 0, (2 * periodDurationUs), (5 * periodDurationUs))
.withAdCount(/* adGroupIndex= */ 0, 2)
.withAdDurationsUs(
/* adGroupIndex= */ 0,
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + periodDurationUs,
periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true)
.withAdCount(/* adGroupIndex= */ 1, 2)
.withAdDurationsUs(/* adGroupIndex= */ 1, periodDurationUs, periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 1, true)
.withAdCount(/* adGroupIndex= */ 2, 2)
.withAdDurationsUs(/* adGroupIndex= */ 2, periodDurationUs, periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 2, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).adGroupCount).isEqualTo(1);
assertThat(adPlaybackStates.get(new Pair<>(0L, 1)).adGroupCount).isEqualTo(1);
assertThat(adPlaybackStates.get(new Pair<>(0L, 2)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 4)).adGroupCount).isEqualTo(1);
assertThat(adPlaybackStates.get(new Pair<>(0L, 5)).adGroupCount).isEqualTo(1);
assertThat(adPlaybackStates.get(new Pair<>(0L, 6)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 7)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 8)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 9)).adGroupCount).isEqualTo(1);
assertThat(adPlaybackStates.get(new Pair<>(0L, 10)).adGroupCount).isEqualTo(1);
}
@Test
public void splitAdPlaybackStateForPeriods_midAndPostrollAdGroup_splitCorrectly() {
int periodCount = 9;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(
/* adsId= */ "adsId",
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + (2 * periodDurationUs),
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + (5 * periodDurationUs))
.withAdCount(/* adGroupIndex= */ 0, 2)
.withAdDurationsUs(/* adGroupIndex= */ 0, periodDurationUs, periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true)
.withAdCount(/* adGroupIndex= */ 1, 2)
.withAdDurationsUs(/* adGroupIndex= */ 1, periodDurationUs, periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 1, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 1)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 2)).adGroupCount).isEqualTo(1);
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).adGroupCount).isEqualTo(1);
assertThat(adPlaybackStates.get(new Pair<>(0L, 4)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 5)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 6)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 7)).adGroupCount).isEqualTo(1);
assertThat(adPlaybackStates.get(new Pair<>(0L, 8)).adGroupCount).isEqualTo(1);
}
@Test
public void splitAdPlaybackStateForPeriods_correctAdsIdInSplitPlaybackStates() {
int periodCount = 4;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(
/* adsId= */ "adsId",
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 2 * periodDurationUs)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAdDurationsUs(/* adGroupIndex= */ 0, 2 * periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
for (int i = 0; i < adPlaybackStates.size(); i++) {
assertThat(adPlaybackStates.get(new Pair<>(0L, i)).adsId).isEqualTo("adsId");
}
}
@Test
public void splitAdPlaybackStateForPeriods_correctAdPlaybackStates() {
int periodCount = 7;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId", 0)
.withAdCount(/* adGroupIndex= */ 0, periodCount)
.withAdDurationsUs(
/* adGroupIndex= */ 0, /* adDurationsUs...= */
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + periodDurationUs,
periodDurationUs,
periodDurationUs,
periodDurationUs,
periodDurationUs,
periodDurationUs,
periodDurationUs)
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
.withSkippedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1)
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_PLAYED);
assertThat(adPlaybackStates.get(new Pair<>(0L, 1)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_SKIPPED);
assertThat(adPlaybackStates.get(new Pair<>(0L, 2)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_ERROR);
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
}
@Test
public void splitAdPlaybackStateForPeriods_lateMidrollAdGroupStartTimeUs_adGroupIgnored() {
int periodCount = 4;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(
/* adsId= */ "adsId",
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + periodDurationUs + 1)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs...= */ periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
for (AdPlaybackState periodAdPlaybackState : adPlaybackStates.values()) {
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(0);
}
}
@Test
public void splitAdPlaybackStateForPeriods_earlyMidrollAdGroupStartTimeUs_adGroupIgnored() {
int periodCount = 4;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId", periodDurationUs - 1)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs...= */ periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
for (AdPlaybackState periodAdPlaybackState : adPlaybackStates.values()) {
assertThat(periodAdPlaybackState.adGroupCount).isEqualTo(0);
assertThat(periodAdPlaybackState.adsId).isEqualTo("adsId");
}
}
}