mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Allow the number of ad groups to grow with AdsMediaSource
This enables `AdsMediaSource` to be used with a live media source that has a growing `AdPlaybackState` to which ad groups can be appended. Before this change, `AdsMediaSource` asserted that the number of ad groups was kept the same, else an exception was thrown. After this change, the assertion checks the validity of the update and throws in case the update isn't considered valid. An update is valid if ad groups are appended to the existing `AdPlaybackState` or ads are appended to existing ad groups. Further the `adGroupIndex` and `timeUs`of an existing ad group can not be changed and once a media item is set for a given ad, that media item can't be changed either. PiperOrigin-RevId: 707244455
This commit is contained in:
parent
aa2ee8f702
commit
d4f4a2c1d4
@ -40,6 +40,9 @@
|
|||||||
* Disable use of asynchronous decryption in MediaCodec to avoid reported
|
* Disable use of asynchronous decryption in MediaCodec to avoid reported
|
||||||
codec timeout issues with this platform API
|
codec timeout issues with this platform API
|
||||||
([#1641](https://github.com/androidx/media/issues/1641)).
|
([#1641](https://github.com/androidx/media/issues/1641)).
|
||||||
|
* Change `AdsMediaSource` to allow the `AdPlaybackStates` to grow by
|
||||||
|
appending ad groups. Invalid modifications are detected and throw an
|
||||||
|
exception.
|
||||||
* Transformer:
|
* Transformer:
|
||||||
* Update parameters of `VideoFrameProcessor.registerInputStream` and
|
* Update parameters of `VideoFrameProcessor.registerInputStream` and
|
||||||
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
|
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
|
||||||
|
@ -25,6 +25,7 @@ import android.os.SystemClock;
|
|||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.AdPlaybackState;
|
import androidx.media3.common.AdPlaybackState;
|
||||||
|
import androidx.media3.common.AdPlaybackState.AdGroup;
|
||||||
import androidx.media3.common.AdViewProvider;
|
import androidx.media3.common.AdViewProvider;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
@ -54,6 +55,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link MediaSource} that inserts ads linearly into a provided content media source.
|
* A {@link MediaSource} that inserts ads linearly into a provided content media source.
|
||||||
@ -352,16 +354,61 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
|
|
||||||
private void onAdPlaybackState(AdPlaybackState adPlaybackState) {
|
private void onAdPlaybackState(AdPlaybackState adPlaybackState) {
|
||||||
if (this.adPlaybackState == null) {
|
if (this.adPlaybackState == null) {
|
||||||
adMediaSourceHolders = new AdMediaSourceHolder[adPlaybackState.adGroupCount][];
|
int playableAdGroupCount =
|
||||||
|
adPlaybackState.adGroupCount
|
||||||
|
- (adPlaybackState.endsWithLivePostrollPlaceHolder() ? 1 : 0);
|
||||||
|
adMediaSourceHolders = new AdMediaSourceHolder[playableAdGroupCount][];
|
||||||
Arrays.fill(adMediaSourceHolders, new AdMediaSourceHolder[0]);
|
Arrays.fill(adMediaSourceHolders, new AdMediaSourceHolder[0]);
|
||||||
} else {
|
} else {
|
||||||
checkState(adPlaybackState.adGroupCount == this.adPlaybackState.adGroupCount);
|
int adGroupInsertionCount =
|
||||||
|
checkValidAdPlaybackStateUpdate(this.adPlaybackState, adPlaybackState);
|
||||||
|
if (adGroupInsertionCount > 0) {
|
||||||
|
adMediaSourceHolders =
|
||||||
|
growAdMediaSourceHolderGrid(adMediaSourceHolders, adGroupInsertionCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.adPlaybackState = adPlaybackState;
|
this.adPlaybackState = adPlaybackState;
|
||||||
maybeUpdateAdMediaSources();
|
maybeUpdateAdMediaSources();
|
||||||
maybeUpdateSourceInfo();
|
maybeUpdateSourceInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int checkValidAdPlaybackStateUpdate(
|
||||||
|
AdPlaybackState oldAdPlaybackState, AdPlaybackState newAdPlaybackState) {
|
||||||
|
checkState(
|
||||||
|
oldAdPlaybackState.endsWithLivePostrollPlaceHolder()
|
||||||
|
== newAdPlaybackState.endsWithLivePostrollPlaceHolder());
|
||||||
|
int insertionCount = newAdPlaybackState.adGroupCount - oldAdPlaybackState.adGroupCount;
|
||||||
|
checkState(insertionCount >= 0);
|
||||||
|
for (int i = newAdPlaybackState.removedAdGroupCount; i < oldAdPlaybackState.adGroupCount; i++) {
|
||||||
|
AdGroup oldAdGroup = oldAdPlaybackState.getAdGroup(i);
|
||||||
|
if (oldAdGroup.isLivePostrollPlaceholder()) {
|
||||||
|
// Post-roll placeholder must be at the last index.
|
||||||
|
checkState(i == oldAdPlaybackState.adGroupCount - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
AdGroup newAdGroup = newAdPlaybackState.getAdGroup(i);
|
||||||
|
checkState(oldAdGroup.count <= newAdGroup.count);
|
||||||
|
checkState(oldAdGroup.timeUs == newAdGroup.timeUs);
|
||||||
|
for (int j = 0; j < oldAdGroup.count; j++) {
|
||||||
|
if (oldAdGroup.mediaItems[j] != null) {
|
||||||
|
checkState(oldAdGroup.mediaItems[j].equals(newAdGroup.mediaItems[j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return insertionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NullableType AdMediaSourceHolder[][] growAdMediaSourceHolderGrid(
|
||||||
|
@NullableType AdMediaSourceHolder[][] grid, int insertionCount) {
|
||||||
|
@NullableType
|
||||||
|
AdMediaSourceHolder[][] grownGrid = new AdMediaSourceHolder[grid.length + insertionCount][];
|
||||||
|
System.arraycopy(grid, 0, grownGrid, 0, grid.length);
|
||||||
|
for (int i = grid.length; i < grownGrid.length; i++) {
|
||||||
|
grownGrid[i] = new AdMediaSourceHolder[0];
|
||||||
|
}
|
||||||
|
return grownGrid;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes any {@link AdMediaSourceHolder AdMediaSourceHolders} where the ad media URI is
|
* Initializes any {@link AdMediaSourceHolder AdMediaSourceHolders} where the ad media URI is
|
||||||
* newly known.
|
* newly known.
|
||||||
@ -378,7 +425,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
@Nullable
|
@Nullable
|
||||||
AdMediaSourceHolder adMediaSourceHolder =
|
AdMediaSourceHolder adMediaSourceHolder =
|
||||||
this.adMediaSourceHolders[adGroupIndex][adIndexInAdGroup];
|
this.adMediaSourceHolders[adGroupIndex][adIndexInAdGroup];
|
||||||
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
|
AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
|
||||||
if (adMediaSourceHolder != null
|
if (adMediaSourceHolder != null
|
||||||
&& !adMediaSourceHolder.hasMediaSource()
|
&& !adMediaSourceHolder.hasMediaSource()
|
||||||
&& adIndexInAdGroup < adGroup.mediaItems.length) {
|
&& adIndexInAdGroup < adGroup.mediaItems.length) {
|
||||||
@ -409,8 +456,12 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull("adPlaybackState")
|
||||||
private long[][] getAdDurationsUs() {
|
private long[][] getAdDurationsUs() {
|
||||||
long[][] adDurationsUs = new long[adMediaSourceHolders.length][];
|
boolean hasPostRollPlaceholder =
|
||||||
|
checkNotNull(adPlaybackState).endsWithLivePostrollPlaceHolder();
|
||||||
|
int adGroupCount = adMediaSourceHolders.length + (hasPostRollPlaceholder ? 1 : 0);
|
||||||
|
long[][] adDurationsUs = new long[adGroupCount][];
|
||||||
for (int i = 0; i < adMediaSourceHolders.length; i++) {
|
for (int i = 0; i < adMediaSourceHolders.length; i++) {
|
||||||
adDurationsUs[i] = new long[adMediaSourceHolders[i].length];
|
adDurationsUs[i] = new long[adMediaSourceHolders[i].length];
|
||||||
for (int j = 0; j < adMediaSourceHolders[i].length; j++) {
|
for (int j = 0; j < adMediaSourceHolders[i].length; j++) {
|
||||||
@ -418,6 +469,10 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||||||
adDurationsUs[i][j] = holder == null ? C.TIME_UNSET : holder.getDurationUs();
|
adDurationsUs[i][j] = holder == null ? C.TIME_UNSET : holder.getDurationUs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (hasPostRollPlaceholder) {
|
||||||
|
// Set the pseudo-durations of the placeholder that is not represented by the holders.
|
||||||
|
adDurationsUs[adGroupCount - 1] = new long[0];
|
||||||
|
}
|
||||||
return adDurationsUs;
|
return adDurationsUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ package androidx.media3.exoplayer.source.ads;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
@ -100,7 +101,7 @@ public final class AdsMediaSourceTest {
|
|||||||
private static final Object CONTENT_PERIOD_UID =
|
private static final Object CONTENT_PERIOD_UID =
|
||||||
CONTENT_TIMELINE.getUidOfPeriod(/* periodIndex= */ 0);
|
CONTENT_TIMELINE.getUidOfPeriod(/* periodIndex= */ 0);
|
||||||
|
|
||||||
private static final AdPlaybackState AD_PLAYBACK_STATE =
|
private static final AdPlaybackState PREROLL_AD_PLAYBACK_STATE =
|
||||||
new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 0)
|
new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 0)
|
||||||
.withContentDurationUs(CONTENT_DURATION_US)
|
.withContentDurationUs(CONTENT_DURATION_US)
|
||||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
@ -121,6 +122,7 @@ public final class AdsMediaSourceTest {
|
|||||||
private FakeMediaSource prerollAdMediaSource;
|
private FakeMediaSource prerollAdMediaSource;
|
||||||
@Mock private MediaSourceCaller mockMediaSourceCaller;
|
@Mock private MediaSourceCaller mockMediaSourceCaller;
|
||||||
private AdsMediaSource adsMediaSource;
|
private AdsMediaSource adsMediaSource;
|
||||||
|
private EventListener adsLoaderEventListener;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@ -156,15 +158,17 @@ public final class AdsMediaSourceTest {
|
|||||||
eq(TEST_ADS_ID),
|
eq(TEST_ADS_ID),
|
||||||
eq(mockAdViewProvider),
|
eq(mockAdViewProvider),
|
||||||
eventListenerArgumentCaptor.capture());
|
eventListenerArgumentCaptor.capture());
|
||||||
|
adsLoaderEventListener = eventListenerArgumentCaptor.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
// Simulate loading a preroll ad.
|
private void setAdPlaybackState(AdPlaybackState adPlaybackState) {
|
||||||
AdsLoader.EventListener adsLoaderEventListener = eventListenerArgumentCaptor.getValue();
|
adsLoaderEventListener.onAdPlaybackState(adPlaybackState);
|
||||||
adsLoaderEventListener.onAdPlaybackState(AD_PLAYBACK_STATE);
|
|
||||||
shadowOf(Looper.getMainLooper()).idle();
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createPeriod_forPreroll_preparesChildAdMediaSourceAndRefreshesSourceInfo() {
|
public void createPeriod_forPreroll_preparesChildAdMediaSourceAndRefreshesSourceInfo() {
|
||||||
|
setAdPlaybackState(PREROLL_AD_PLAYBACK_STATE);
|
||||||
// This should be unused if we only create the preroll period.
|
// This should be unused if we only create the preroll period.
|
||||||
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
||||||
adsMediaSource.createPeriod(
|
adsMediaSource.createPeriod(
|
||||||
@ -181,12 +185,13 @@ public final class AdsMediaSourceTest {
|
|||||||
verify(mockMediaSourceCaller)
|
verify(mockMediaSourceCaller)
|
||||||
.onSourceInfoRefreshed(
|
.onSourceInfoRefreshed(
|
||||||
adsMediaSource,
|
adsMediaSource,
|
||||||
new SinglePeriodAdTimeline(PLACEHOLDER_CONTENT_TIMELINE, AD_PLAYBACK_STATE));
|
new SinglePeriodAdTimeline(PLACEHOLDER_CONTENT_TIMELINE, PREROLL_AD_PLAYBACK_STATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void
|
public void
|
||||||
createPeriod_forPreroll_preparesChildAdMediaSourceAndRefreshesSourceInfoWithAdMediaSourceInfo() {
|
createPeriod_forPreroll_preparesChildAdMediaSourceAndRefreshesSourceInfoWithAdMediaSourceInfo() {
|
||||||
|
setAdPlaybackState(PREROLL_AD_PLAYBACK_STATE);
|
||||||
// This should be unused if we only create the preroll period.
|
// This should be unused if we only create the preroll period.
|
||||||
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
||||||
adsMediaSource.createPeriod(
|
adsMediaSource.createPeriod(
|
||||||
@ -205,11 +210,13 @@ public final class AdsMediaSourceTest {
|
|||||||
adsMediaSource,
|
adsMediaSource,
|
||||||
new SinglePeriodAdTimeline(
|
new SinglePeriodAdTimeline(
|
||||||
PLACEHOLDER_CONTENT_TIMELINE,
|
PLACEHOLDER_CONTENT_TIMELINE,
|
||||||
AD_PLAYBACK_STATE.withAdDurationsUs(new long[][] {{PREROLL_AD_DURATION_US}})));
|
PREROLL_AD_PLAYBACK_STATE.withAdDurationsUs(
|
||||||
|
new long[][] {{PREROLL_AD_DURATION_US}})));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createPeriod_forPreroll_createsChildPrerollAdMediaPeriod() {
|
public void createPeriod_forPreroll_createsChildPrerollAdMediaPeriod() {
|
||||||
|
setAdPlaybackState(PREROLL_AD_PLAYBACK_STATE);
|
||||||
adsMediaSource.createPeriod(
|
adsMediaSource.createPeriod(
|
||||||
new MediaPeriodId(
|
new MediaPeriodId(
|
||||||
CONTENT_PERIOD_UID,
|
CONTENT_PERIOD_UID,
|
||||||
@ -227,6 +234,7 @@ public final class AdsMediaSourceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createPeriod_forContent_createsChildContentMediaPeriodAndLoadsContentTimeline() {
|
public void createPeriod_forContent_createsChildContentMediaPeriodAndLoadsContentTimeline() {
|
||||||
|
setAdPlaybackState(PREROLL_AD_PLAYBACK_STATE);
|
||||||
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
||||||
shadowOf(Looper.getMainLooper()).idle();
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
adsMediaSource.createPeriod(
|
adsMediaSource.createPeriod(
|
||||||
@ -241,11 +249,12 @@ public final class AdsMediaSourceTest {
|
|||||||
.onSourceInfoRefreshed(eq(adsMediaSource), adsTimelineCaptor.capture());
|
.onSourceInfoRefreshed(eq(adsMediaSource), adsTimelineCaptor.capture());
|
||||||
TestUtil.timelinesAreSame(
|
TestUtil.timelinesAreSame(
|
||||||
adsTimelineCaptor.getValue(),
|
adsTimelineCaptor.getValue(),
|
||||||
new SinglePeriodAdTimeline(CONTENT_TIMELINE, AD_PLAYBACK_STATE));
|
new SinglePeriodAdTimeline(CONTENT_TIMELINE, PREROLL_AD_PLAYBACK_STATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void releasePeriod_releasesChildMediaPeriodsAndSources() {
|
public void releasePeriod_releasesChildMediaPeriodsAndSources() {
|
||||||
|
setAdPlaybackState(PREROLL_AD_PLAYBACK_STATE);
|
||||||
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
contentMediaSource.setNewSourceInfo(CONTENT_TIMELINE);
|
||||||
MediaPeriod prerollAdMediaPeriod =
|
MediaPeriod prerollAdMediaPeriod =
|
||||||
adsMediaSource.createPeriod(
|
adsMediaSource.createPeriod(
|
||||||
@ -692,6 +701,239 @@ public final class AdsMediaSourceTest {
|
|||||||
.isEqualTo(133_000_000); // Overridden by AdsMediaSource with the actual source duration.
|
.isEqualTo(133_000_000); // Overridden by AdsMediaSource with the actual source duration.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAdPlaybackState_correctAdPlaybackStateInTimeline() {
|
||||||
|
ArgumentCaptor<Timeline> timelineCaptor = ArgumentCaptor.forClass(Timeline.class);
|
||||||
|
|
||||||
|
setAdPlaybackState(PREROLL_AD_PLAYBACK_STATE);
|
||||||
|
|
||||||
|
verify(mockMediaSourceCaller).onSourceInfoRefreshed(any(), timelineCaptor.capture());
|
||||||
|
assertThat(
|
||||||
|
timelineCaptor
|
||||||
|
.getValue()
|
||||||
|
.getPeriod(/* periodIndex= */ 0, new Timeline.Period())
|
||||||
|
.adPlaybackState)
|
||||||
|
.isEqualTo(PREROLL_AD_PLAYBACK_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAdPlaybackState_growingLiveAdPlaybackState_correctAdPlaybackStateInTimeline() {
|
||||||
|
AdPlaybackState initialLiveAdPlaybackState =
|
||||||
|
new AdPlaybackState("adsId")
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false);
|
||||||
|
AdPlaybackState singleAdInFirstAdGroup =
|
||||||
|
initialLiveAdPlaybackState
|
||||||
|
.withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 0L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
MediaItem.fromUri("https://example.com/ad0-0"));
|
||||||
|
AdPlaybackState twoAdsInFirstAdGroup =
|
||||||
|
singleAdInFirstAdGroup
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000L, 2_000L)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 3_000L)
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 1,
|
||||||
|
MediaItem.fromUri("https://example.com/ad0-1"));
|
||||||
|
AdPlaybackState singleAdInSecondAdGroup =
|
||||||
|
twoAdsInFirstAdGroup
|
||||||
|
.withNewAdGroup(/* adGroupIndex= */ 1, /* adGroupTimeUs= */ 10_000L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 1, 10_000L)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, 10_000L)
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 1,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
MediaItem.fromUri("https://example.com/ad1-0"));
|
||||||
|
AdPlaybackState twoAdsInSecondAdGroup =
|
||||||
|
singleAdInSecondAdGroup
|
||||||
|
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 2)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 1, 10_000L, 20_000L)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, 30_000L)
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 1,
|
||||||
|
/* adIndexInAdGroup= */ 1,
|
||||||
|
MediaItem.fromUri("https://example.com/ad1-1"));
|
||||||
|
ArgumentCaptor<Timeline> timelineCaptor = ArgumentCaptor.forClass(Timeline.class);
|
||||||
|
|
||||||
|
setAdPlaybackState(initialLiveAdPlaybackState);
|
||||||
|
setAdPlaybackState(singleAdInFirstAdGroup);
|
||||||
|
setAdPlaybackState(twoAdsInFirstAdGroup);
|
||||||
|
setAdPlaybackState(singleAdInSecondAdGroup);
|
||||||
|
setAdPlaybackState(twoAdsInSecondAdGroup);
|
||||||
|
|
||||||
|
verify(mockMediaSourceCaller, times(5)).onSourceInfoRefreshed(any(), timelineCaptor.capture());
|
||||||
|
assertThat(
|
||||||
|
timelineCaptor
|
||||||
|
.getAllValues()
|
||||||
|
.get(0)
|
||||||
|
.getPeriod(0, new Timeline.Period())
|
||||||
|
.adPlaybackState)
|
||||||
|
.isEqualTo(initialLiveAdPlaybackState);
|
||||||
|
assertThat(
|
||||||
|
timelineCaptor
|
||||||
|
.getAllValues()
|
||||||
|
.get(1)
|
||||||
|
.getPeriod(0, new Timeline.Period())
|
||||||
|
.adPlaybackState)
|
||||||
|
.isEqualTo(
|
||||||
|
singleAdInFirstAdGroup.withAdDurationsUs(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adDurationsUs...= */ C.TIME_UNSET)); // durations are overridden by ads source
|
||||||
|
assertThat(
|
||||||
|
timelineCaptor
|
||||||
|
.getAllValues()
|
||||||
|
.get(2)
|
||||||
|
.getPeriod(0, new Timeline.Period())
|
||||||
|
.adPlaybackState)
|
||||||
|
.isEqualTo(
|
||||||
|
twoAdsInFirstAdGroup.withAdDurationsUs(
|
||||||
|
/* adGroupIndex= */ 0, /* adDurationsUs...= */ C.TIME_UNSET, C.TIME_UNSET));
|
||||||
|
assertThat(
|
||||||
|
timelineCaptor
|
||||||
|
.getAllValues()
|
||||||
|
.get(3)
|
||||||
|
.getPeriod(0, new Timeline.Period())
|
||||||
|
.adPlaybackState)
|
||||||
|
.isEqualTo(
|
||||||
|
singleAdInSecondAdGroup
|
||||||
|
.withAdDurationsUs(
|
||||||
|
/* adGroupIndex= */ 0, /* adDurationsUs...= */ C.TIME_UNSET, C.TIME_UNSET)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ C.TIME_UNSET));
|
||||||
|
assertThat(
|
||||||
|
timelineCaptor
|
||||||
|
.getAllValues()
|
||||||
|
.get(4)
|
||||||
|
.getPeriod(0, new Timeline.Period())
|
||||||
|
.adPlaybackState)
|
||||||
|
.isEqualTo(
|
||||||
|
twoAdsInSecondAdGroup
|
||||||
|
.withAdDurationsUs(
|
||||||
|
/* adGroupIndex= */ 0, /* adDurationsUs...= */ C.TIME_UNSET, C.TIME_UNSET)
|
||||||
|
.withAdDurationsUs(
|
||||||
|
/* adGroupIndex= */ 1, /* adDurationsUs...= */ C.TIME_UNSET, C.TIME_UNSET));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
onAdPlaybackState_shrinkingAdPlaybackStateForLiveStream_throwsIllegalStateException() {
|
||||||
|
AdPlaybackState initialLiveAdPlaybackState =
|
||||||
|
new AdPlaybackState("adsId")
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
|
||||||
|
.withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 0L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
MediaItem.fromUri("https://example.com/ad0-0"));
|
||||||
|
setAdPlaybackState(initialLiveAdPlaybackState);
|
||||||
|
|
||||||
|
assertThrows(
|
||||||
|
IllegalStateException.class,
|
||||||
|
() ->
|
||||||
|
setAdPlaybackState(
|
||||||
|
new AdPlaybackState("adsId")
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAdPlaybackState_timeUsOfAdGroupChanged_throwsIllegalStateException() {
|
||||||
|
AdPlaybackState initialLiveAdPlaybackState =
|
||||||
|
new AdPlaybackState("adsId")
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
|
||||||
|
.withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 0L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
MediaItem.fromUri("https://example.com/ad0-0"));
|
||||||
|
setAdPlaybackState(initialLiveAdPlaybackState);
|
||||||
|
|
||||||
|
assertThrows(
|
||||||
|
IllegalStateException.class,
|
||||||
|
() ->
|
||||||
|
setAdPlaybackState(
|
||||||
|
initialLiveAdPlaybackState.withAdGroupTimeUs(
|
||||||
|
/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 1234L)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAdPlaybackState_mediaItemOfAdChanged_throwsIllegalStateException() {
|
||||||
|
AdPlaybackState initialLiveAdPlaybackState =
|
||||||
|
new AdPlaybackState("adsId")
|
||||||
|
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
|
||||||
|
.withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 0L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
MediaItem.fromUri("https://example.com/ad0-0"));
|
||||||
|
setAdPlaybackState(initialLiveAdPlaybackState);
|
||||||
|
|
||||||
|
assertThrows(
|
||||||
|
IllegalStateException.class,
|
||||||
|
() ->
|
||||||
|
setAdPlaybackState(
|
||||||
|
initialLiveAdPlaybackState.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
MediaItem.fromUri("https://example.com/ad0-1"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAdPlaybackState_postRollAdded_throwsIllegalStateException() {
|
||||||
|
AdPlaybackState withoutLivePostRollPlaceholder =
|
||||||
|
new AdPlaybackState("adsId")
|
||||||
|
.withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 0L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
MediaItem.fromUri("https://example.com/ad0-0"));
|
||||||
|
|
||||||
|
setAdPlaybackState(withoutLivePostRollPlaceholder);
|
||||||
|
|
||||||
|
assertThrows(
|
||||||
|
IllegalStateException.class,
|
||||||
|
() ->
|
||||||
|
setAdPlaybackState(
|
||||||
|
withoutLivePostRollPlaceholder.withLivePostrollPlaceholderAppended(
|
||||||
|
/* isServerSideInserted= */ false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAdPlaybackState_postRollRemoved_throwsIllegalStateException() {
|
||||||
|
AdPlaybackState withoutLivePostRollPlaceholder =
|
||||||
|
new AdPlaybackState("adsId")
|
||||||
|
.withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 0L)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, 1_000L)
|
||||||
|
.withAvailableAdMediaItem(
|
||||||
|
/* adGroupIndex= */ 0,
|
||||||
|
/* adIndexInAdGroup= */ 0,
|
||||||
|
MediaItem.fromUri("https://example.com/ad0-0"));
|
||||||
|
setAdPlaybackState(
|
||||||
|
withoutLivePostRollPlaceholder.withLivePostrollPlaceholderAppended(
|
||||||
|
/* isServerSideInserted= */ false));
|
||||||
|
|
||||||
|
assertThrows(
|
||||||
|
IllegalStateException.class, () -> setAdPlaybackState(withoutLivePostRollPlaceholder));
|
||||||
|
}
|
||||||
|
|
||||||
private static class NoOpAdsLoader implements AdsLoader {
|
private static class NoOpAdsLoader implements AdsLoader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
x
Reference in New Issue
Block a user