mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Resolve media period ids in multi-period windows
Ignorable ad periods are skipped to resolve the media period id with the ad playback state of the resulting period. In case of a change in the period position un-played ad periods are rolled forward to be played. PiperOrigin-RevId: 428011116
This commit is contained in:
parent
449a840f88
commit
0367fafa6c
@ -23,6 +23,7 @@ import androidx.media3.test.utils.FakeTimeline;
|
||||
import androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition;
|
||||
import androidx.media3.test.utils.TimelineAsserts;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@ -221,7 +222,7 @@ public class TimelineTest {
|
||||
/* durationUs= */ 2,
|
||||
/* defaultPositionUs= */ 22,
|
||||
/* windowOffsetInFirstPeriodUs= */ 222,
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder().setMediaId("mediaId2").build()),
|
||||
new TimelineWindowDefinition(
|
||||
/* periodCount= */ 3,
|
||||
@ -233,7 +234,7 @@ public class TimelineTest {
|
||||
/* durationUs= */ 3,
|
||||
/* defaultPositionUs= */ 33,
|
||||
/* windowOffsetInFirstPeriodUs= */ 333,
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder().setMediaId("mediaId3").build()));
|
||||
|
||||
Timeline restoredTimeline = Timeline.CREATOR.fromBundle(timeline.toBundle());
|
||||
|
@ -1178,7 +1178,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
requestedContentPositionUs =
|
||||
seekPosition.windowPositionUs == C.TIME_UNSET ? C.TIME_UNSET : resolvedContentPositionUs;
|
||||
periodId =
|
||||
queue.resolveMediaPeriodIdForAds(
|
||||
queue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
playbackInfo.timeline, periodUid, resolvedContentPositionUs);
|
||||
if (periodId.isAd()) {
|
||||
playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period);
|
||||
@ -1492,7 +1492,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
window, period, firstWindowIndex, /* windowPositionUs= */ C.TIME_UNSET);
|
||||
// Add ad metadata if any and propagate the window sequence number to new period id.
|
||||
MediaPeriodId firstPeriodId =
|
||||
queue.resolveMediaPeriodIdForAds(
|
||||
queue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, firstPeriodAndPositionUs.first, /* positionUs= */ 0);
|
||||
long positionUs = firstPeriodAndPositionUs.second;
|
||||
if (firstPeriodId.isAd()) {
|
||||
@ -2354,7 +2354,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private PlaybackInfo handlePositionDiscontinuity(
|
||||
MediaPeriodId mediaPeriodId,
|
||||
long positionUs,
|
||||
long contentPositionUs,
|
||||
long requestedContentPositionUs,
|
||||
long discontinuityStartPositionUs,
|
||||
boolean reportDiscontinuity,
|
||||
@DiscontinuityReason int discontinuityReason) {
|
||||
@ -2379,9 +2379,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
staticMetadata = extractMetadataFromTrackSelectionArray(trackSelectorResult.selections);
|
||||
// Ensure the media period queue requested content position matches the new playback info.
|
||||
if (playingPeriodHolder != null
|
||||
&& playingPeriodHolder.info.requestedContentPositionUs != contentPositionUs) {
|
||||
&& playingPeriodHolder.info.requestedContentPositionUs != requestedContentPositionUs) {
|
||||
playingPeriodHolder.info =
|
||||
playingPeriodHolder.info.copyWithRequestedContentPositionUs(contentPositionUs);
|
||||
playingPeriodHolder.info.copyWithRequestedContentPositionUs(requestedContentPositionUs);
|
||||
}
|
||||
} else if (!mediaPeriodId.equals(playbackInfo.periodId)) {
|
||||
// Reset previously kept track info if unprepared and the period changes.
|
||||
@ -2395,7 +2395,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return playbackInfo.copyWithNewPosition(
|
||||
mediaPeriodId,
|
||||
positionUs,
|
||||
contentPositionUs,
|
||||
requestedContentPositionUs,
|
||||
discontinuityStartPositionUs,
|
||||
getTotalBufferedDurationUs(),
|
||||
trackGroupArray,
|
||||
@ -2668,7 +2668,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
// Ensure ad insertion metadata is up to date.
|
||||
MediaPeriodId periodIdWithAds =
|
||||
queue.resolveMediaPeriodIdForAds(timeline, newPeriodUid, contentPositionForAdResolutionUs);
|
||||
queue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, newPeriodUid, contentPositionForAdResolutionUs);
|
||||
boolean earliestCuePointIsUnchangedOrLater =
|
||||
periodIdWithAds.nextAdGroupIndex == C.INDEX_UNSET
|
||||
|| (oldPeriodId.nextAdGroupIndex != C.INDEX_UNSET
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import android.os.Handler;
|
||||
@ -446,21 +447,7 @@ import com.google.common.collect.ImmutableList;
|
||||
Timeline timeline, Object periodUid, long positionUs) {
|
||||
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(timeline, periodUid);
|
||||
return resolveMediaPeriodIdForAds(
|
||||
timeline, periodUid, positionUs, windowSequenceNumber, period);
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private void notifyQueueUpdate() {
|
||||
ImmutableList.Builder<MediaPeriodId> builder = ImmutableList.builder();
|
||||
@Nullable MediaPeriodHolder period = playing;
|
||||
while (period != null) {
|
||||
builder.add(period.info.id);
|
||||
period = period.getNext();
|
||||
}
|
||||
@Nullable MediaPeriodId readingPeriodId = reading == null ? null : reading.info.id;
|
||||
analyticsCollectorHandler.post(
|
||||
() -> analyticsCollector.updateMediaPeriodQueueInfo(builder.build(), readingPeriodId));
|
||||
timeline, periodUid, positionUs, windowSequenceNumber, window, period);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -481,8 +468,21 @@ import com.google.common.collect.ImmutableList;
|
||||
Object periodUid,
|
||||
long positionUs,
|
||||
long windowSequenceNumber,
|
||||
Timeline.Window window,
|
||||
Timeline.Period period) {
|
||||
timeline.getPeriodByUid(periodUid, period);
|
||||
timeline.getWindow(period.windowIndex, window);
|
||||
int periodIndex = timeline.getIndexOfPeriod(periodUid);
|
||||
// Skip ignorable server side inserted ad periods.
|
||||
while ((period.durationUs == 0
|
||||
&& period.getAdGroupCount() > 0
|
||||
&& period.isServerSideInsertedAdGroup(period.getRemovedAdGroupCount())
|
||||
&& period.getAdGroupIndexForPositionUs(0) == C.INDEX_UNSET)
|
||||
&& periodIndex++ < window.lastPeriodIndex) {
|
||||
timeline.getPeriod(periodIndex, period, /* setIds= */ true);
|
||||
periodUid = checkNotNull(period.uid);
|
||||
}
|
||||
timeline.getPeriodByUid(periodUid, period);
|
||||
int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs);
|
||||
if (adGroupIndex == C.INDEX_UNSET) {
|
||||
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs);
|
||||
@ -493,6 +493,55 @@ import com.google.common.collect.ImmutableList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the specified timeline period and position to a {@link MediaPeriodId} that should be
|
||||
* played after a period position change, returning an identifier for an ad group if one needs to
|
||||
* be played before the specified position, or an identifier for a content media period if not.
|
||||
*
|
||||
* @param timeline The timeline the period is part of.
|
||||
* @param periodUid The uid of the timeline period to play.
|
||||
* @param positionUs The next content position in the period to play.
|
||||
* @return The identifier for the first media period to play, taking into account unplayed ads.
|
||||
*/
|
||||
public MediaPeriodId resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
Timeline timeline, Object periodUid, long positionUs) {
|
||||
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(timeline, periodUid);
|
||||
// Check for preceding ad periods in multi-period window.
|
||||
timeline.getPeriodByUid(periodUid, period);
|
||||
timeline.getWindow(period.windowIndex, window);
|
||||
Object periodUidToPlay = periodUid;
|
||||
boolean seenAdPeriod = false;
|
||||
for (int i = timeline.getIndexOfPeriod(periodUid); i >= window.firstPeriodIndex; i--) {
|
||||
timeline.getPeriod(/* periodIndex= */ i, period, /* setIds= */ true);
|
||||
boolean isAdPeriod = period.getAdGroupCount() > 0;
|
||||
seenAdPeriod |= isAdPeriod;
|
||||
if (period.getAdGroupIndexForPositionUs(period.durationUs) != C.INDEX_UNSET) {
|
||||
// Roll forward to preceding un-played ad period.
|
||||
periodUidToPlay = checkNotNull(period.uid);
|
||||
}
|
||||
if (seenAdPeriod && (!isAdPeriod || period.durationUs != 0)) {
|
||||
// Stop for any periods except un-played ads with no content.
|
||||
break;
|
||||
}
|
||||
}
|
||||
return resolveMediaPeriodIdForAds(
|
||||
timeline, periodUidToPlay, positionUs, windowSequenceNumber, window, period);
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
private void notifyQueueUpdate() {
|
||||
ImmutableList.Builder<MediaPeriodId> builder = ImmutableList.builder();
|
||||
@Nullable MediaPeriodHolder period = playing;
|
||||
while (period != null) {
|
||||
builder.add(period.info.id);
|
||||
period = period.getNext();
|
||||
}
|
||||
@Nullable MediaPeriodId readingPeriodId = reading == null ? null : reading.info.id;
|
||||
analyticsCollectorHandler.post(
|
||||
() -> analyticsCollector.updateMediaPeriodQueueInfo(builder.build(), readingPeriodId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the specified period uid to a corresponding window sequence number. Either by reusing
|
||||
* the window sequence number of an existing matching media period or by creating a new window
|
||||
@ -647,12 +696,12 @@ import com.google.common.collect.ImmutableList;
|
||||
// We can't create a next period yet.
|
||||
return null;
|
||||
}
|
||||
|
||||
long startPositionUs;
|
||||
long contentPositionUs;
|
||||
// We either start a new period in the same window or the first period in the next window.
|
||||
long startPositionUs = 0;
|
||||
long contentPositionUs = 0;
|
||||
int nextWindowIndex =
|
||||
timeline.getPeriod(nextPeriodIndex, period, /* setIds= */ true).windowIndex;
|
||||
Object nextPeriodUid = period.uid;
|
||||
Object nextPeriodUid = checkNotNull(period.uid);
|
||||
long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber;
|
||||
if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) {
|
||||
// We're starting to buffer a new window. When playback transitions to this window we'll
|
||||
@ -672,20 +721,32 @@ import com.google.common.collect.ImmutableList;
|
||||
}
|
||||
nextPeriodUid = defaultPositionUs.first;
|
||||
startPositionUs = defaultPositionUs.second;
|
||||
MediaPeriodHolder nextMediaPeriodHolder = mediaPeriodHolder.getNext();
|
||||
@Nullable MediaPeriodHolder nextMediaPeriodHolder = mediaPeriodHolder.getNext();
|
||||
if (nextMediaPeriodHolder != null && nextMediaPeriodHolder.uid.equals(nextPeriodUid)) {
|
||||
windowSequenceNumber = nextMediaPeriodHolder.info.id.windowSequenceNumber;
|
||||
} else {
|
||||
windowSequenceNumber = nextWindowSequenceNumber++;
|
||||
}
|
||||
} else {
|
||||
// We're starting to buffer a new period within the same window.
|
||||
startPositionUs = 0;
|
||||
contentPositionUs = 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
MediaPeriodId periodId =
|
||||
resolveMediaPeriodIdForAds(
|
||||
timeline, nextPeriodUid, startPositionUs, windowSequenceNumber, period);
|
||||
timeline, nextPeriodUid, startPositionUs, windowSequenceNumber, window, period);
|
||||
if (contentPositionUs != C.TIME_UNSET
|
||||
&& mediaPeriodInfo.requestedContentPositionUs != C.TIME_UNSET) {
|
||||
boolean isPrecedingPeriodAnAd =
|
||||
timeline.getPeriodByUid(mediaPeriodInfo.id.periodUid, period).getAdGroupCount() > 0
|
||||
&& period.isServerSideInsertedAdGroup(period.getRemovedAdGroupCount());
|
||||
// Handle the requested content position for period transitions within the same window.
|
||||
if (periodId.isAd() && isPrecedingPeriodAnAd) {
|
||||
// Propagate the requested position to the following ad period in the same window.
|
||||
contentPositionUs = mediaPeriodInfo.requestedContentPositionUs;
|
||||
} else if (isPrecedingPeriodAnAd) {
|
||||
// Use the requested content position of the preceding ad period as the start position.
|
||||
startPositionUs = mediaPeriodInfo.requestedContentPositionUs;
|
||||
}
|
||||
}
|
||||
return getMediaPeriodInfo(timeline, periodId, contentPositionUs, startPositionUs);
|
||||
}
|
||||
|
||||
|
@ -124,6 +124,7 @@ import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
||||
import androidx.media3.exoplayer.source.SinglePeriodTimeline;
|
||||
import androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.upstream.Allocation;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
@ -4984,6 +4985,436 @@ public final class ExoPlayerTest {
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekTo_beyondSSAIMidRolls_seekAdjustedAndRequestedContentPositionKept()
|
||||
throws Exception {
|
||||
ArgumentCaptor<PositionInfo> oldPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<PositionInfo> newPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<Integer> reasonArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
FakeTimeline adTimeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
"windowId",
|
||||
/* numberOfPlayedAds= */ 0,
|
||||
/* isAdPeriodFlags...= */ false,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
Listener listener = mock(Listener.class);
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
player.addListener(listener);
|
||||
AtomicReference<ServerSideAdInsertionMediaSource> sourceReference = new AtomicReference<>();
|
||||
sourceReference.set(
|
||||
new ServerSideAdInsertionMediaSource(
|
||||
new FakeMediaSource(adTimeline),
|
||||
contentTimeline -> {
|
||||
sourceReference
|
||||
.get()
|
||||
.setAdPlaybackStates(adTimeline.getAdPlaybackStates(/* windowIndex= */ 0));
|
||||
return true;
|
||||
}));
|
||||
player.setMediaSource(sourceReference.get());
|
||||
player.pause();
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
|
||||
player.seekTo(/* positionMs= */ 4000);
|
||||
player.play();
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
player.release();
|
||||
|
||||
verify(listener, times(6))
|
||||
.onPositionDiscontinuity(
|
||||
oldPositionArgumentCaptor.capture(),
|
||||
newPositionArgumentCaptor.capture(),
|
||||
reasonArgumentCaptor.capture());
|
||||
List<PositionInfo> oldPositions = oldPositionArgumentCaptor.getAllValues();
|
||||
List<PositionInfo> newPositions = newPositionArgumentCaptor.getAllValues();
|
||||
List<Integer> reasons = reasonArgumentCaptor.getAllValues();
|
||||
assertThat(reasons).containsExactly(1, 2, 0, 0, 0, 0).inOrder();
|
||||
// seek discontinuities
|
||||
assertThat(oldPositions.get(0).periodIndex).isEqualTo(0);
|
||||
assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(0).periodIndex).isEqualTo(3);
|
||||
assertThat(newPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(0).positionMs).isEqualTo(4000);
|
||||
// seek adjustment
|
||||
assertThat(oldPositions.get(1).periodIndex).isEqualTo(3);
|
||||
assertThat(oldPositions.get(1).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(oldPositions.get(1).positionMs).isEqualTo(4000);
|
||||
assertThat(newPositions.get(1).periodIndex).isEqualTo(1);
|
||||
assertThat(newPositions.get(1).adGroupIndex).isEqualTo(0);
|
||||
assertThat(newPositions.get(1).adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(newPositions.get(1).positionMs).isEqualTo(0);
|
||||
assertThat(newPositions.get(1).contentPositionMs).isEqualTo(4000);
|
||||
// auto transition from ad to end of period
|
||||
assertThat(oldPositions.get(2).periodIndex).isEqualTo(1);
|
||||
assertThat(oldPositions.get(2).adGroupIndex).isEqualTo(0);
|
||||
assertThat(oldPositions.get(2).adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(oldPositions.get(2).positionMs).isEqualTo(2500);
|
||||
assertThat(oldPositions.get(2).contentPositionMs).isEqualTo(4000);
|
||||
assertThat(newPositions.get(2).periodIndex).isEqualTo(1);
|
||||
assertThat(newPositions.get(2).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(2).positionMs).isEqualTo(2500);
|
||||
// auto transition to next ad period
|
||||
assertThat(oldPositions.get(3).periodIndex).isEqualTo(1);
|
||||
assertThat(oldPositions.get(3).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(3).periodIndex).isEqualTo(2);
|
||||
assertThat(newPositions.get(3).adGroupIndex).isEqualTo(0);
|
||||
assertThat(newPositions.get(3).adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(newPositions.get(3).contentPositionMs).isEqualTo(4000);
|
||||
// auto transition from ad to end of period
|
||||
assertThat(oldPositions.get(4).periodIndex).isEqualTo(2);
|
||||
assertThat(oldPositions.get(4).adGroupIndex).isEqualTo(0);
|
||||
assertThat(oldPositions.get(4).adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(newPositions.get(4).periodIndex).isEqualTo(2);
|
||||
assertThat(newPositions.get(4).adGroupIndex).isEqualTo(-1);
|
||||
// auto transition to final content period with seek position
|
||||
assertThat(oldPositions.get(5).periodIndex).isEqualTo(2);
|
||||
assertThat(oldPositions.get(5).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(5).periodIndex).isEqualTo(3);
|
||||
assertThat(newPositions.get(5).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(5).contentPositionMs).isEqualTo(4000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekTo_beyondSSAIMidRollsConsecutiveContentPeriods_seekAdjusted() throws Exception {
|
||||
ArgumentCaptor<PositionInfo> oldPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<PositionInfo> newPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<Integer> reasonArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
FakeTimeline adTimeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
"windowId",
|
||||
/* numberOfPlayedAds= */ 0,
|
||||
/* isAdPeriodFlags...= */ false,
|
||||
true,
|
||||
false,
|
||||
false);
|
||||
Listener listener = mock(Listener.class);
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
player.addListener(listener);
|
||||
AtomicReference<ServerSideAdInsertionMediaSource> sourceReference = new AtomicReference<>();
|
||||
sourceReference.set(
|
||||
new ServerSideAdInsertionMediaSource(
|
||||
new FakeMediaSource(adTimeline),
|
||||
contentTimeline -> {
|
||||
sourceReference
|
||||
.get()
|
||||
.setAdPlaybackStates(adTimeline.getAdPlaybackStates(/* windowIndex= */ 0));
|
||||
return true;
|
||||
}));
|
||||
player.setMediaSource(sourceReference.get());
|
||||
player.pause();
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
|
||||
player.seekTo(/* positionMs= */ 7000);
|
||||
player.play();
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
player.release();
|
||||
|
||||
verify(listener, times(5))
|
||||
.onPositionDiscontinuity(
|
||||
oldPositionArgumentCaptor.capture(),
|
||||
newPositionArgumentCaptor.capture(),
|
||||
reasonArgumentCaptor.capture());
|
||||
List<PositionInfo> oldPositions = oldPositionArgumentCaptor.getAllValues();
|
||||
List<PositionInfo> newPositions = newPositionArgumentCaptor.getAllValues();
|
||||
List<Integer> reasons = reasonArgumentCaptor.getAllValues();
|
||||
assertThat(reasons).containsExactly(1, 2, 0, 0, 0).inOrder();
|
||||
// seek
|
||||
assertThat(oldPositions.get(0).periodIndex).isEqualTo(0);
|
||||
assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(0).periodIndex).isEqualTo(3);
|
||||
assertThat(newPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(0).positionMs).isEqualTo(7000);
|
||||
// seek adjustment
|
||||
assertThat(oldPositions.get(1).periodIndex).isEqualTo(3);
|
||||
assertThat(oldPositions.get(1).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(oldPositions.get(1).positionMs).isEqualTo(7000);
|
||||
assertThat(newPositions.get(1).periodIndex).isEqualTo(1);
|
||||
assertThat(newPositions.get(1).adGroupIndex).isEqualTo(0);
|
||||
assertThat(newPositions.get(1).positionMs).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekTo_beforeSSAIMidRolls_requestedContentPositionNotPropagatedIntoAds()
|
||||
throws Exception {
|
||||
ArgumentCaptor<PositionInfo> oldPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<PositionInfo> newPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<Integer> reasonArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
FakeTimeline adTimeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
"windowId",
|
||||
/* numberOfPlayedAds= */ 0,
|
||||
/* isAdPeriodFlags...= */ false,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
Listener listener = mock(Listener.class);
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
player.addListener(listener);
|
||||
AtomicReference<ServerSideAdInsertionMediaSource> sourceReference = new AtomicReference<>();
|
||||
sourceReference.set(
|
||||
new ServerSideAdInsertionMediaSource(
|
||||
new FakeMediaSource(adTimeline),
|
||||
contentTimeline -> {
|
||||
sourceReference
|
||||
.get()
|
||||
.setAdPlaybackStates(adTimeline.getAdPlaybackStates(/* windowIndex= */ 0));
|
||||
return true;
|
||||
}));
|
||||
player.setMediaSource(sourceReference.get());
|
||||
player.pause();
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
player.play();
|
||||
|
||||
player.seekTo(1600);
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
player.release();
|
||||
|
||||
verify(listener, times(6))
|
||||
.onPositionDiscontinuity(
|
||||
oldPositionArgumentCaptor.capture(),
|
||||
newPositionArgumentCaptor.capture(),
|
||||
reasonArgumentCaptor.capture());
|
||||
List<PositionInfo> oldPositions = oldPositionArgumentCaptor.getAllValues();
|
||||
List<PositionInfo> newPositions = newPositionArgumentCaptor.getAllValues();
|
||||
List<Integer> reasons = reasonArgumentCaptor.getAllValues();
|
||||
assertThat(reasons).containsExactly(1, 0, 0, 0, 0, 0).inOrder();
|
||||
// seek discontinuity
|
||||
assertThat(oldPositions.get(0).periodIndex).isEqualTo(0);
|
||||
assertThat(newPositions.get(0).periodIndex).isEqualTo(0);
|
||||
assertThat(newPositions.get(0).positionMs).isEqualTo(1600);
|
||||
assertThat(newPositions.get(0).contentPositionMs).isEqualTo(1600);
|
||||
// auto discontinuities through ads has correct content position that is not the seek position.
|
||||
assertThat(newPositions.get(1).periodIndex).isEqualTo(1);
|
||||
assertThat(newPositions.get(1).adGroupIndex).isEqualTo(0);
|
||||
assertThat(newPositions.get(1).adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(newPositions.get(1).positionMs).isEqualTo(0);
|
||||
assertThat(newPositions.get(1).contentPositionMs).isEqualTo(2500);
|
||||
assertThat(newPositions.get(2).contentPositionMs).isEqualTo(2500);
|
||||
assertThat(newPositions.get(3).contentPositionMs).isEqualTo(2500);
|
||||
assertThat(newPositions.get(4).contentPositionMs).isEqualTo(2500);
|
||||
// Content resumes at expected position that is not the seek position.
|
||||
assertThat(newPositions.get(5).periodIndex).isEqualTo(3);
|
||||
assertThat(newPositions.get(5).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(5).positionMs).isEqualTo(2500);
|
||||
assertThat(newPositions.get(5).contentPositionMs).isEqualTo(2500);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekTo_toSAIMidRolls_playsMidRolls() throws Exception {
|
||||
ArgumentCaptor<PositionInfo> oldPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<PositionInfo> newPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<Integer> reasonArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
FakeTimeline adTimeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
"windowId",
|
||||
/* numberOfPlayedAds= */ 0,
|
||||
/* isAdPeriodFlags...= */ false,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
Listener listener = mock(Listener.class);
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
player.addListener(listener);
|
||||
AtomicReference<ServerSideAdInsertionMediaSource> sourceReference = new AtomicReference<>();
|
||||
sourceReference.set(
|
||||
new ServerSideAdInsertionMediaSource(
|
||||
new FakeMediaSource(adTimeline),
|
||||
contentTimeline -> {
|
||||
sourceReference
|
||||
.get()
|
||||
.setAdPlaybackStates(adTimeline.getAdPlaybackStates(/* windowIndex= */ 0));
|
||||
return true;
|
||||
}));
|
||||
player.setMediaSource(sourceReference.get());
|
||||
player.pause();
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
player.seekTo(2500);
|
||||
player.play();
|
||||
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
player.release();
|
||||
|
||||
verify(listener, times(6))
|
||||
.onPositionDiscontinuity(
|
||||
oldPositionArgumentCaptor.capture(),
|
||||
newPositionArgumentCaptor.capture(),
|
||||
reasonArgumentCaptor.capture());
|
||||
List<PositionInfo> oldPositions = oldPositionArgumentCaptor.getAllValues();
|
||||
List<PositionInfo> newPositions = newPositionArgumentCaptor.getAllValues();
|
||||
List<Integer> reasons = reasonArgumentCaptor.getAllValues();
|
||||
assertThat(reasons).containsExactly(1, 2, 0, 0, 0, 0).inOrder();
|
||||
// seek discontinuity
|
||||
assertThat(oldPositions.get(0).periodIndex).isEqualTo(0);
|
||||
assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(0).periodIndex).isEqualTo(1);
|
||||
assertThat(newPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
// seek adjustment discontinuity
|
||||
assertThat(oldPositions.get(1).periodIndex).isEqualTo(1);
|
||||
assertThat(oldPositions.get(1).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(1).periodIndex).isEqualTo(1);
|
||||
assertThat(newPositions.get(1).adGroupIndex).isEqualTo(0);
|
||||
// auto transition to last frame of first ad period
|
||||
assertThat(oldPositions.get(2).periodIndex).isEqualTo(1);
|
||||
assertThat(oldPositions.get(2).adGroupIndex).isEqualTo(0);
|
||||
assertThat(newPositions.get(2).periodIndex).isEqualTo(1);
|
||||
assertThat(newPositions.get(2).adGroupIndex).isEqualTo(-1);
|
||||
// auto transition to second ad period
|
||||
assertThat(oldPositions.get(3).periodIndex).isEqualTo(1);
|
||||
assertThat(oldPositions.get(3).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(3).periodIndex).isEqualTo(2);
|
||||
assertThat(newPositions.get(3).adGroupIndex).isEqualTo(0);
|
||||
// auto transition to last frame of second ad period
|
||||
assertThat(oldPositions.get(4).periodIndex).isEqualTo(2);
|
||||
assertThat(oldPositions.get(4).adGroupIndex).isEqualTo(0);
|
||||
assertThat(newPositions.get(4).periodIndex).isEqualTo(2);
|
||||
assertThat(newPositions.get(4).adGroupIndex).isEqualTo(-1);
|
||||
// auto transition to the final content period
|
||||
assertThat(oldPositions.get(5).periodIndex).isEqualTo(2);
|
||||
assertThat(oldPositions.get(5).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(5).periodIndex).isEqualTo(3);
|
||||
assertThat(newPositions.get(5).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(5).positionMs).isEqualTo(2500);
|
||||
assertThat(newPositions.get(5).contentPositionMs).isEqualTo(2500);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seekTo_toPlayedSAIMidRolls_requestedContentPositionNotPropagatedIntoAds()
|
||||
throws Exception {
|
||||
ArgumentCaptor<PositionInfo> oldPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<PositionInfo> newPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<Integer> reasonArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
FakeTimeline adTimeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
"windowId",
|
||||
/* numberOfPlayedAds= */ 2,
|
||||
/* isAdPeriodFlags...= */ false,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
Listener listener = mock(Listener.class);
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
player.addListener(listener);
|
||||
AtomicReference<ServerSideAdInsertionMediaSource> sourceReference = new AtomicReference<>();
|
||||
sourceReference.set(
|
||||
new ServerSideAdInsertionMediaSource(
|
||||
new FakeMediaSource(adTimeline),
|
||||
contentTimeline -> {
|
||||
sourceReference
|
||||
.get()
|
||||
.setAdPlaybackStates(adTimeline.getAdPlaybackStates(/* windowIndex= */ 0));
|
||||
return true;
|
||||
}));
|
||||
player.setMediaSource(sourceReference.get());
|
||||
player.pause();
|
||||
player.prepare();
|
||||
runUntilPlaybackState(player, Player.STATE_READY);
|
||||
player.seekTo(2500);
|
||||
player.play();
|
||||
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
player.release();
|
||||
|
||||
verify(listener, times(1))
|
||||
.onPositionDiscontinuity(
|
||||
oldPositionArgumentCaptor.capture(),
|
||||
newPositionArgumentCaptor.capture(),
|
||||
reasonArgumentCaptor.capture());
|
||||
List<PositionInfo> oldPositions = oldPositionArgumentCaptor.getAllValues();
|
||||
List<PositionInfo> newPositions = newPositionArgumentCaptor.getAllValues();
|
||||
List<Integer> reasons = reasonArgumentCaptor.getAllValues();
|
||||
assertThat(reasons).containsExactly(1).inOrder();
|
||||
// seek discontinuity
|
||||
assertThat(oldPositions.get(0).periodIndex).isEqualTo(0);
|
||||
assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
// TODO(bachinger): Incorrect masking. Skipped played prerolls not taken into account by masking
|
||||
assertThat(newPositions.get(0).periodIndex).isEqualTo(1);
|
||||
assertThat(newPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void play_playedSSAIPreMidPostRolls_skipsAllAds() throws Exception {
|
||||
ArgumentCaptor<PositionInfo> oldPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<PositionInfo> newPositionArgumentCaptor =
|
||||
ArgumentCaptor.forClass(PositionInfo.class);
|
||||
ArgumentCaptor<Integer> reasonArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
FakeTimeline adTimeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
"windowId",
|
||||
/* numberOfPlayedAds= */ Integer.MAX_VALUE,
|
||||
/* isAdPeriodFlags...= */ true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true);
|
||||
Listener listener = mock(Listener.class);
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
player.addListener(listener);
|
||||
AtomicReference<ServerSideAdInsertionMediaSource> sourceReference = new AtomicReference<>();
|
||||
sourceReference.set(
|
||||
new ServerSideAdInsertionMediaSource(
|
||||
new FakeMediaSource(adTimeline),
|
||||
contentTimeline -> {
|
||||
sourceReference
|
||||
.get()
|
||||
.setAdPlaybackStates(adTimeline.getAdPlaybackStates(/* windowIndex= */ 0));
|
||||
return true;
|
||||
}));
|
||||
player.setMediaSource(sourceReference.get());
|
||||
player.prepare();
|
||||
|
||||
player.play();
|
||||
runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||
player.release();
|
||||
|
||||
verify(listener, times(3))
|
||||
.onPositionDiscontinuity(
|
||||
oldPositionArgumentCaptor.capture(),
|
||||
newPositionArgumentCaptor.capture(),
|
||||
reasonArgumentCaptor.capture());
|
||||
List<PositionInfo> oldPositions = oldPositionArgumentCaptor.getAllValues();
|
||||
List<PositionInfo> newPositions = newPositionArgumentCaptor.getAllValues();
|
||||
List<Integer> reasons = reasonArgumentCaptor.getAllValues();
|
||||
assertThat(reasons).containsExactly(0, 0, 0).inOrder();
|
||||
// Auto discontinuity from the empty ad period to the first content period.
|
||||
assertThat(oldPositions.get(0).periodIndex).isEqualTo(0);
|
||||
assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(oldPositions.get(0).positionMs).isEqualTo(0);
|
||||
assertThat(newPositions.get(0).periodIndex).isEqualTo(1);
|
||||
assertThat(newPositions.get(0).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(0).positionMs).isEqualTo(0);
|
||||
// Auto discontinuity from the first content to the second content period.
|
||||
assertThat(oldPositions.get(1).periodIndex).isEqualTo(1);
|
||||
assertThat(oldPositions.get(1).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(1).periodIndex).isEqualTo(4);
|
||||
assertThat(newPositions.get(1).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(1).positionMs).isEqualTo(1250);
|
||||
// Auto discontinuity from the second content period to the last frame of the last postroll.
|
||||
assertThat(oldPositions.get(2).periodIndex).isEqualTo(4);
|
||||
assertThat(oldPositions.get(2).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(2).periodIndex).isEqualTo(7);
|
||||
assertThat(newPositions.get(2).adGroupIndex).isEqualTo(-1);
|
||||
assertThat(newPositions.get(2).positionMs).isEqualTo(2500);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void becomingNoisyIgnoredIfBecomingNoisyHandlingIsDisabled() throws Exception {
|
||||
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||
@ -8036,7 +8467,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs = */ 100_000,
|
||||
/* defaultPositionUs = */ 0,
|
||||
/* windowOffsetInFirstPeriodUs= */ 0,
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
MediaItem.fromUri("http://foo.bar/fake1"));
|
||||
FakeMediaSource fakeMediaSource1 = new FakeMediaSource(new FakeTimeline(window1));
|
||||
TimelineWindowDefinition window2 =
|
||||
@ -8050,7 +8481,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs = */ 100_000,
|
||||
/* defaultPositionUs = */ 0,
|
||||
/* windowOffsetInFirstPeriodUs= */ 0,
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
MediaItem.fromUri("http://foo.bar/fake2"));
|
||||
FakeMediaSource fakeMediaSource2 = new FakeMediaSource(new FakeTimeline(window2));
|
||||
TimelineWindowDefinition window3 =
|
||||
@ -8064,7 +8495,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs = */ 100_000,
|
||||
/* defaultPositionUs = */ 0,
|
||||
/* windowOffsetInFirstPeriodUs= */ 0,
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
MediaItem.fromUri("http://foo.bar/fake3"));
|
||||
FakeMediaSource fakeMediaSource3 = new FakeMediaSource(new FakeTimeline(window3));
|
||||
final Player[] playerHolder = {null};
|
||||
@ -8422,7 +8853,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 10_000_000,
|
||||
/* defaultPositionUs= */ 0,
|
||||
/* windowOffsetInFirstPeriodUs= */ 0,
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
initialMediaItem);
|
||||
TimelineWindowDefinition secondWindow =
|
||||
new TimelineWindowDefinition(
|
||||
@ -8435,7 +8866,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 10_000_000,
|
||||
/* defaultPositionUs= */ 0,
|
||||
/* windowOffsetInFirstPeriodUs= */ 0,
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
initialMediaItem.buildUpon().setTag(1).build());
|
||||
FakeTimeline timeline = new FakeTimeline(initialWindow);
|
||||
FakeTimeline newTimeline = new FakeTimeline(secondWindow);
|
||||
@ -9269,7 +9700,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9319,7 +9750,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9365,7 +9796,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9413,7 +9844,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9431,7 +9862,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs + 50_000),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9576,7 +10007,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 20 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9630,7 +10061,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9675,7 +10106,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9693,7 +10124,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9742,7 +10173,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9760,7 +10191,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder()
|
||||
.setUri(Uri.EMPTY)
|
||||
.setLiveConfiguration(
|
||||
@ -9850,7 +10281,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 8 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(windowStartUnixTimeMs),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
new MediaItem.Builder().setUri(Uri.EMPTY).build()));
|
||||
player.pause();
|
||||
player.setMediaSource(new FakeMediaSource(liveTimelineWithoutTargetLiveOffset));
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer;
|
||||
|
||||
import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
@ -22,6 +23,7 @@ import static org.robolectric.Shadows.shadowOf;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Pair;
|
||||
import androidx.media3.common.AdPlaybackState;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
@ -479,8 +481,7 @@ public final class MediaPeriodQueueTest {
|
||||
/* startPositionUs= */ 0,
|
||||
/* requestedContentPositionUs= */ C.TIME_UNSET,
|
||||
/* endPositionUs= */ C.TIME_UNSET,
|
||||
/* durationUs= */ CONTENT_DURATION_US
|
||||
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||
/* durationUs= */ CONTENT_DURATION_US + DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||
/* isFollowedByTransitionToSameStream= */ false,
|
||||
/* isLastInPeriod= */ true,
|
||||
/* isLastInWindow= */ false,
|
||||
@ -740,6 +741,320 @@ public final class MediaPeriodQueueTest {
|
||||
assertThat(getQueueLength()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_behindAdPositionInSinglePeriodTimeline_resolvesToAd() {
|
||||
long adPositionUs = DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 10_000;
|
||||
AdPlaybackState adPlaybackState = new AdPlaybackState("adsId", adPositionUs);
|
||||
adPlaybackState = adPlaybackState.withAdDurationsUs(/* adGroupIndex= */ 0, 5_000);
|
||||
Object windowUid = new Object();
|
||||
FakeTimeline timeline =
|
||||
new FakeTimeline(
|
||||
new TimelineWindowDefinition(
|
||||
/* periodCount= */ 1,
|
||||
/* id= */ windowUid,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US,
|
||||
adPlaybackState));
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, /* periodUid= */ new Pair<>(windowUid, 0), adPositionUs + 1);
|
||||
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowUid, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_toAdPositionInSinglePeriodTimeline_resolvesToAd() {
|
||||
long adPositionUs = DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 10_000;
|
||||
AdPlaybackState adPlaybackState = new AdPlaybackState("adsId", adPositionUs);
|
||||
adPlaybackState = adPlaybackState.withAdDurationsUs(/* adGroupIndex= */ 0, 5_000);
|
||||
Object windowUid = new Object();
|
||||
FakeTimeline timeline =
|
||||
new FakeTimeline(
|
||||
new TimelineWindowDefinition(
|
||||
/* periodCount= */ 1,
|
||||
/* id= */ windowUid,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US,
|
||||
adPlaybackState));
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, /* periodUid= */ new Pair<>(windowUid, 0), adPositionUs);
|
||||
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowUid, 0));
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_beforeAdPositionInSinglePeriodTimeline_seekNotAdjusted() {
|
||||
long adPositionUs = DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + 10_000;
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState("adsId", adPositionUs).withAdDurationsUs(/* adGroupIndex= */ 0, 5_000);
|
||||
Object windowUid = new Object();
|
||||
FakeTimeline timeline =
|
||||
new FakeTimeline(
|
||||
new TimelineWindowDefinition(
|
||||
/* periodCount= */ 1,
|
||||
/* id= */ windowUid,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US,
|
||||
adPlaybackState));
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowUid, 0), adPositionUs - 1);
|
||||
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowUid, 0));
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_behindAdInMultiPeriodTimeline_rollForward() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId,
|
||||
/* numberOfPlayedAds= */ 0,
|
||||
/* isAdPeriodFlags...= */ true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 1), /* positionUs= */ 1);
|
||||
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 0));
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
|
||||
mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 5), /* positionUs= */ 0);
|
||||
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 2));
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_behindAdInMultiPeriodAllAdsPlayed_seekNotAdjusted() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId,
|
||||
/* numberOfPlayedAds= */ 4,
|
||||
/* isAdPeriodFlags...= */ true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 1), /* positionUs= */ 11);
|
||||
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 1));
|
||||
|
||||
mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 5), /* positionUs= */ 33);
|
||||
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_behindAdInMultiPeriodFirstTwoAdsPlayed_rollForward() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId,
|
||||
/* numberOfPlayedAds= */ 2,
|
||||
/* isAdPeriodFlags...= */ true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 5), /* positionUs= */ 33);
|
||||
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_beforeAdInMultiPeriodTimeline_seekNotAdjusted() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId, /* numberOfPlayedAds= */ 0, /* isAdPeriodFlags...= */ false, true);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 0), /* positionUs= */ 33);
|
||||
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_toUnplayedAdInMultiPeriodTimeline_resolvedAsAd() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId, /* numberOfPlayedAds= */ 0, /* isAdPeriodFlags...= */ false, true, false);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 1), /* positionUs= */ 0);
|
||||
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_toPlayedAdInMultiPeriodTimeline_skipPlayedAd() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId, /* numberOfPlayedAds= */ 1, /* isAdPeriodFlags...= */ false, true, false);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 1), /* positionUs= */ 0);
|
||||
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_toStartOfWindowPlayedAdPreroll_skipsPlayedPrerolls() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId, /* numberOfPlayedAds= */ 2, /* isAdPeriodFlags...= */ true, true, false);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 0), /* positionUs= */ 0);
|
||||
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_toPlayedPostrolls_skipsAllButLastPostroll() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId,
|
||||
/* numberOfPlayedAds= */ 4,
|
||||
/* isAdPeriodFlags...= */ false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 1), /* positionUs= */ 0);
|
||||
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 4));
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(-1);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_consecutiveContentPeriods_rollForward() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId,
|
||||
/* numberOfPlayedAds= */ 0,
|
||||
/* isAdPeriodFlags...= */ true,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 3), /* positionUs= */ 10_000);
|
||||
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 0));
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.adIndexInAdGroup).isEqualTo(0);
|
||||
assertThat(mediaPeriodId.nextAdGroupIndex).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
resolveMediaPeriodIdForAdsAfterPeriodPositionChange_onlyConsecutiveContentPeriods_seekNotAdjusted() {
|
||||
Object windowId = new Object();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId,
|
||||
/* numberOfPlayedAds= */ 0,
|
||||
/* isAdPeriodFlags...= */ false,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
|
||||
MediaPeriodId mediaPeriodId =
|
||||
mediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
|
||||
timeline, new Pair<>(windowId, 3), /* positionUs= */ 10_000);
|
||||
|
||||
assertThat(mediaPeriodId.periodUid).isEqualTo(new Pair<>(windowId, 3));
|
||||
assertThat(mediaPeriodId.adGroupIndex).isEqualTo(-1);
|
||||
}
|
||||
|
||||
private void setupAdTimeline(long... adGroupTimesUs) {
|
||||
adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ new Object(), adGroupTimesUs)
|
||||
|
@ -526,6 +526,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||
adPlaybackState,
|
||||
/* fromPositionUs= */ secToUs(cuePoint.getStartTime()),
|
||||
/* contentResumeOffsetUs= */ 0,
|
||||
// TODO(b/192231683) Use getEndTimeMs()/getStartTimeMs() after jar target was removed
|
||||
/* adDurationsUs...= */ secToUs(cuePoint.getEndTime() - cuePoint.getStartTime()));
|
||||
}
|
||||
return adPlaybackState;
|
||||
|
@ -438,12 +438,13 @@ import java.util.Set;
|
||||
new AdPlaybackState(checkNotNull(adsId), /* adGroupTimesUs...= */ 0)
|
||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||
.withAdDurationsUs(/* adGroupIndex= */ 0, periodDurationUs)
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 0, true)
|
||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, adGroup.contentResumeOffsetUs);
|
||||
long periodEndUs = periodStartUs + periodDurationUs;
|
||||
long adDurationsUs = 0;
|
||||
for (int i = 0; i < adGroup.count; i++) {
|
||||
adDurationsUs += adGroup.durationsUs[i];
|
||||
if (periodEndUs == adGroup.timeUs + adDurationsUs) {
|
||||
if (periodEndUs <= adGroup.timeUs + adDurationsUs + 10_000) {
|
||||
// Map the state of the global ad state to the period specific ad state.
|
||||
switch (adGroup.states[i]) {
|
||||
case AdPlaybackState.AD_STATE_PLAYED:
|
||||
|
@ -1365,7 +1365,9 @@ public final class ImaAdsLoaderTest {
|
||||
}
|
||||
|
||||
private AdPlaybackState getAdPlaybackState(int periodIndex) {
|
||||
return timelineWindowDefinitions[periodIndex].adPlaybackState;
|
||||
int adPlaybackStateCount = timelineWindowDefinitions[periodIndex].adPlaybackStates.size();
|
||||
return timelineWindowDefinitions[periodIndex].adPlaybackStates.get(
|
||||
periodIndex % adPlaybackStateCount);
|
||||
}
|
||||
|
||||
private static AdEvent getAdEvent(AdEventType adEventType, @Nullable Ad ad) {
|
||||
@ -1408,7 +1410,11 @@ public final class ImaAdsLoaderTest {
|
||||
adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs);
|
||||
|
||||
TimelineWindowDefinition timelineWindowDefinition = timelineWindowDefinitions[periodIndex];
|
||||
assertThat(adPlaybackState.adsId).isEqualTo(timelineWindowDefinition.adPlaybackState.adsId);
|
||||
assertThat(adPlaybackState.adsId)
|
||||
.isEqualTo(
|
||||
timelineWindowDefinition.adPlaybackStates.get(
|
||||
periodIndex % timelineWindowDefinition.adPlaybackStates.size())
|
||||
.adsId);
|
||||
timelineWindowDefinitions[periodIndex] =
|
||||
new TimelineWindowDefinition(
|
||||
timelineWindowDefinition.periodCount,
|
||||
|
@ -458,6 +458,52 @@ public class ImaUtilTest {
|
||||
.isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splitAdPlaybackStateForPeriods_singleAdOfAdGroupSpansMultiplePeriods_correctState() {
|
||||
int periodCount = 8;
|
||||
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ "adsId", 0, periodDurationUs, 2 * periodDurationUs)
|
||||
.withAdCount(/* adGroupIndex= */ 0, 1)
|
||||
.withAdCount(/* adGroupIndex= */ 1, 1)
|
||||
.withAdCount(/* adGroupIndex= */ 2, 1)
|
||||
.withAdDurationsUs(
|
||||
/* adGroupIndex= */ 0, /* adDurationsUs...= */
|
||||
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + (2 * periodDurationUs))
|
||||
.withAdDurationsUs(
|
||||
/* adGroupIndex= */ 1, /* adDurationsUs...= */ (2 * periodDurationUs))
|
||||
.withAdDurationsUs(
|
||||
/* adGroupIndex= */ 2, /* adDurationsUs...= */ (2 * periodDurationUs))
|
||||
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
|
||||
.withPlayedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0)
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 0, true)
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 1, true)
|
||||
.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)).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_PLAYED);
|
||||
assertThat(adPlaybackStates.get(new Pair<>(0L, 2)).adGroupCount).isEqualTo(0);
|
||||
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).getAdGroup(/* adGroupIndex= */ 0).states[0])
|
||||
.isEqualTo(AdPlaybackState.AD_STATE_PLAYED);
|
||||
assertThat(adPlaybackStates.get(new Pair<>(0L, 4)).getAdGroup(/* adGroupIndex= */ 0).states[0])
|
||||
.isEqualTo(AdPlaybackState.AD_STATE_PLAYED);
|
||||
assertThat(adPlaybackStates.get(new Pair<>(0L, 5)).adGroupCount).isEqualTo(0);
|
||||
assertThat(adPlaybackStates.get(new Pair<>(0L, 6)).getAdGroup(/* adGroupIndex= */ 0).states[0])
|
||||
.isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
|
||||
assertThat(adPlaybackStates.get(new Pair<>(0L, 7)).getAdGroup(/* adGroupIndex= */ 0).states[0])
|
||||
.isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splitAdPlaybackStateForPeriods_lateMidrollAdGroupStartTimeUs_adGroupIgnored() {
|
||||
int periodCount = 4;
|
||||
|
@ -26,6 +26,7 @@ import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.MediaSourceFactory;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
import androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/** Fake {@link MediaSourceFactory} that creates a {@link FakeMediaSource}. */
|
||||
@UnstableApi
|
||||
@ -66,7 +67,7 @@ public final class FakeMediaSourceFactory implements MediaSourceFactory {
|
||||
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
|
||||
/* defaultPositionUs= */ 2 * C.MICROS_PER_SECOND,
|
||||
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(123456789),
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
mediaItem);
|
||||
return new FakeMediaSource(new FakeTimeline(timelineWindowDefinition));
|
||||
}
|
||||
|
@ -15,6 +15,9 @@
|
||||
*/
|
||||
package androidx.media3.test.utils;
|
||||
|
||||
import static androidx.media3.common.util.Util.sum;
|
||||
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 java.lang.Math.min;
|
||||
|
||||
import android.net.Uri;
|
||||
@ -27,7 +30,13 @@ import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Fake {@link Timeline} which can be setup to return custom {@link TimelineWindowDefinition}s. */
|
||||
@UnstableApi
|
||||
@ -52,7 +61,7 @@ public final class FakeTimeline extends Timeline {
|
||||
public final long durationUs;
|
||||
public final long defaultPositionUs;
|
||||
public final long windowOffsetInFirstPeriodUs;
|
||||
public final AdPlaybackState adPlaybackState;
|
||||
public final List<AdPlaybackState> adPlaybackStates;
|
||||
|
||||
/**
|
||||
* Creates a window definition that corresponds to a placeholder timeline using the given tag.
|
||||
@ -179,10 +188,41 @@ public final class FakeTimeline extends Timeline {
|
||||
durationUs,
|
||||
defaultPositionUs,
|
||||
windowOffsetInFirstPeriodUs,
|
||||
adPlaybackState,
|
||||
ImmutableList.of(adPlaybackState),
|
||||
FAKE_MEDIA_ITEM.buildUpon().setTag(id).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #TimelineWindowDefinition(int, Object, boolean, boolean, boolean,
|
||||
* boolean, long, long, long, List, MediaItem)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public TimelineWindowDefinition(
|
||||
int periodCount,
|
||||
Object id,
|
||||
boolean isSeekable,
|
||||
boolean isDynamic,
|
||||
boolean isLive,
|
||||
boolean isPlaceholder,
|
||||
long durationUs,
|
||||
long defaultPositionUs,
|
||||
long windowOffsetInFirstPeriodUs,
|
||||
AdPlaybackState adPlaybackState,
|
||||
MediaItem mediaItem) {
|
||||
this(
|
||||
periodCount,
|
||||
id,
|
||||
isSeekable,
|
||||
isDynamic,
|
||||
isLive,
|
||||
isPlaceholder,
|
||||
durationUs,
|
||||
defaultPositionUs,
|
||||
windowOffsetInFirstPeriodUs,
|
||||
ImmutableList.of(adPlaybackState),
|
||||
mediaItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a window definition with ad groups and a custom media item.
|
||||
*
|
||||
@ -197,7 +237,7 @@ public final class FakeTimeline extends Timeline {
|
||||
* @param defaultPositionUs The default position of the window in microseconds.
|
||||
* @param windowOffsetInFirstPeriodUs The offset of the window in the first period, in
|
||||
* microseconds.
|
||||
* @param adPlaybackState The ad playback state.
|
||||
* @param adPlaybackStates The ad playback states for the periods.
|
||||
* @param mediaItem The media item to include in the timeline.
|
||||
*/
|
||||
public TimelineWindowDefinition(
|
||||
@ -210,7 +250,7 @@ public final class FakeTimeline extends Timeline {
|
||||
long durationUs,
|
||||
long defaultPositionUs,
|
||||
long windowOffsetInFirstPeriodUs,
|
||||
AdPlaybackState adPlaybackState,
|
||||
List<AdPlaybackState> adPlaybackStates,
|
||||
MediaItem mediaItem) {
|
||||
Assertions.checkArgument(durationUs != C.TIME_UNSET || periodCount == 1);
|
||||
this.periodCount = periodCount;
|
||||
@ -223,7 +263,7 @@ public final class FakeTimeline extends Timeline {
|
||||
this.durationUs = durationUs;
|
||||
this.defaultPositionUs = defaultPositionUs;
|
||||
this.windowOffsetInFirstPeriodUs = windowOffsetInFirstPeriodUs;
|
||||
this.adPlaybackState = adPlaybackState;
|
||||
this.adPlaybackStates = adPlaybackStates;
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,6 +308,59 @@ public final class FakeTimeline extends Timeline {
|
||||
return adPlaybackState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-period timeline with ad and content periods specified by the flags passed as
|
||||
* var-arg arguments.
|
||||
*
|
||||
* <p>Period uid end up being a {@code new Pair<>(windowId, periodIndex)}.
|
||||
*
|
||||
* @param windowId The window ID.
|
||||
* @param numberOfPlayedAds The number of ads that should be marked as played.
|
||||
* @param isAdPeriodFlags A value of true indicates an ad period. A value of false indicated a
|
||||
* content period.
|
||||
* @return A timeline with a single window with as many periods as var-arg arguments.
|
||||
*/
|
||||
public static FakeTimeline createMultiPeriodAdTimeline(
|
||||
Object windowId, int numberOfPlayedAds, boolean... isAdPeriodFlags) {
|
||||
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / isAdPeriodFlags.length;
|
||||
AdPlaybackState firstAdPeriodState =
|
||||
new AdPlaybackState(/* adsId= */ "adsId", /* adGroupTimesUs... */ 0)
|
||||
.withAdCount(/* adGroupIndex= */ 0, 1)
|
||||
.withAdDurationsUs(
|
||||
/* adGroupIndex= */ 0, DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + periodDurationUs)
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
|
||||
AdPlaybackState commonAdPeriodState = firstAdPeriodState.withAdDurationsUs(0, periodDurationUs);
|
||||
AdPlaybackState contentPeriodState = new AdPlaybackState(/* adsId= */ "adsId");
|
||||
|
||||
List<AdPlaybackState> adPlaybackStates = new ArrayList<>();
|
||||
int playedAdsCounter = 0;
|
||||
for (boolean isAd : isAdPeriodFlags) {
|
||||
AdPlaybackState periodAdPlaybackState =
|
||||
isAd
|
||||
? (adPlaybackStates.isEmpty() ? firstAdPeriodState : commonAdPeriodState)
|
||||
: contentPeriodState;
|
||||
if (isAd && playedAdsCounter < numberOfPlayedAds) {
|
||||
periodAdPlaybackState =
|
||||
periodAdPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
|
||||
playedAdsCounter++;
|
||||
}
|
||||
adPlaybackStates.add(periodAdPlaybackState);
|
||||
}
|
||||
return new FakeTimeline(
|
||||
new FakeTimeline.TimelineWindowDefinition(
|
||||
isAdPeriodFlags.length,
|
||||
windowId,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* isPlaceholder= */ false,
|
||||
/* durationUs= */ DEFAULT_WINDOW_DURATION_US,
|
||||
/* defaultPositionUs= */ 0,
|
||||
/* windowOffsetInFirstPeriodUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
|
||||
/* adPlaybackStates= */ adPlaybackStates,
|
||||
MediaItem.EMPTY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fake timeline with one seekable, non-dynamic window with one period and a duration of
|
||||
* {@link TimelineWindowDefinition#DEFAULT_WINDOW_DURATION_US}.
|
||||
@ -363,6 +456,19 @@ public final class FakeTimeline extends Timeline {
|
||||
@Override
|
||||
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
|
||||
long windowDurationUs = 0;
|
||||
Period period = new Period();
|
||||
for (int i = periodOffsets[windowIndex]; i < periodOffsets[windowIndex + 1]; i++) {
|
||||
long periodDurationUs = getPeriod(/* periodIndex= */ i, period).durationUs;
|
||||
if (i == periodOffsets[windowIndex] && periodDurationUs != 0) {
|
||||
windowDurationUs -= windowDefinition.windowOffsetInFirstPeriodUs;
|
||||
}
|
||||
if (periodDurationUs == C.TIME_UNSET) {
|
||||
windowDurationUs = C.TIME_UNSET;
|
||||
break;
|
||||
}
|
||||
windowDurationUs += periodDurationUs;
|
||||
}
|
||||
window.set(
|
||||
/* uid= */ windowDefinition.id,
|
||||
windowDefinition.mediaItem,
|
||||
@ -376,7 +482,7 @@ public final class FakeTimeline extends Timeline {
|
||||
windowDefinition.isDynamic,
|
||||
windowDefinition.isLive ? windowDefinition.mediaItem.liveConfiguration : null,
|
||||
windowDefinition.defaultPositionUs,
|
||||
windowDefinition.durationUs,
|
||||
windowDurationUs,
|
||||
periodOffsets[windowIndex],
|
||||
periodOffsets[windowIndex + 1] - 1,
|
||||
windowDefinition.windowOffsetInFirstPeriodUs);
|
||||
@ -396,11 +502,15 @@ public final class FakeTimeline extends Timeline {
|
||||
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
|
||||
Object id = setIds ? windowPeriodIndex : null;
|
||||
Object uid = setIds ? Pair.create(windowDefinition.id, windowPeriodIndex) : null;
|
||||
AdPlaybackState adPlaybackState =
|
||||
windowDefinition.adPlaybackStates.get(
|
||||
periodIndex % windowDefinition.adPlaybackStates.size());
|
||||
// Arbitrarily set period duration by distributing window duration equally among all periods.
|
||||
long periodDurationUs =
|
||||
windowDefinition.durationUs == C.TIME_UNSET
|
||||
periodIndex == windowDefinition.periodCount - 1
|
||||
&& windowDefinition.durationUs == C.TIME_UNSET
|
||||
? C.TIME_UNSET
|
||||
: windowDefinition.durationUs / windowDefinition.periodCount;
|
||||
: (windowDefinition.durationUs / windowDefinition.periodCount);
|
||||
long positionInWindowUs;
|
||||
if (windowPeriodIndex == 0) {
|
||||
if (windowDefinition.durationUs != C.TIME_UNSET) {
|
||||
@ -414,9 +524,11 @@ public final class FakeTimeline extends Timeline {
|
||||
id,
|
||||
uid,
|
||||
windowIndex,
|
||||
periodDurationUs,
|
||||
periodDurationUs == C.TIME_UNSET
|
||||
? C.TIME_UNSET
|
||||
: periodDurationUs - getServerSideAdInsertionAdDurationUs(adPlaybackState),
|
||||
positionInWindowUs,
|
||||
windowDefinition.adPlaybackState,
|
||||
adPlaybackState,
|
||||
windowDefinition.isPlaceholder);
|
||||
return period;
|
||||
}
|
||||
@ -442,6 +554,22 @@ public final class FakeTimeline extends Timeline {
|
||||
return Pair.create(windowDefinition.id, windowPeriodIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of ad playback states keyed by the period UID.
|
||||
*
|
||||
* @param windowIndex The window index of the window to get the map of ad playback states from.
|
||||
* @return The map of {@link AdPlaybackState ad playback states}.
|
||||
*/
|
||||
public ImmutableMap<Object, AdPlaybackState> getAdPlaybackStates(int windowIndex) {
|
||||
Map<Object, AdPlaybackState> adPlaybackStateMap = new HashMap<>();
|
||||
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
|
||||
for (int i = 0; i < windowDefinition.adPlaybackStates.size(); i++) {
|
||||
adPlaybackStateMap.put(
|
||||
new Pair<>(windowDefinition.id, i), windowDefinition.adPlaybackStates.get(i));
|
||||
}
|
||||
return ImmutableMap.copyOf(adPlaybackStateMap);
|
||||
}
|
||||
|
||||
private static TimelineWindowDefinition[] createDefaultWindowDefinitions(int windowCount) {
|
||||
TimelineWindowDefinition[] windowDefinitions = new TimelineWindowDefinition[windowCount];
|
||||
for (int i = 0; i < windowCount; i++) {
|
||||
@ -449,4 +577,15 @@ public final class FakeTimeline extends Timeline {
|
||||
}
|
||||
return windowDefinitions;
|
||||
}
|
||||
|
||||
private static long getServerSideAdInsertionAdDurationUs(AdPlaybackState adPlaybackState) {
|
||||
long adDurationUs = 0;
|
||||
for (int i = 0; i < adPlaybackState.adGroupCount; i++) {
|
||||
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(i);
|
||||
if (adGroup.isServerSideInserted) {
|
||||
adDurationUs += sum(adGroup.durationsUs);
|
||||
}
|
||||
}
|
||||
return adDurationUs;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.test.utils;
|
||||
|
||||
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 androidx.media3.common.AdPlaybackState;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Tests for {@link FakeTimeline}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class FakeTimelineTest {
|
||||
|
||||
@Test
|
||||
public void createMultiPeriodAdTimeline_firstPeriodIsAd() {
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
Timeline.Period period = new Timeline.Period();
|
||||
Object windowId = new Object();
|
||||
int numberOfPlayedAds = 2;
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
windowId,
|
||||
numberOfPlayedAds,
|
||||
/* isAdPeriodFlags...= */ true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true);
|
||||
|
||||
assertThat(timeline.getWindowCount()).isEqualTo(1);
|
||||
assertThat(timeline.getPeriodCount()).isEqualTo(7);
|
||||
// Assert content periods and window duration.
|
||||
Timeline.Period contentPeriod1 = timeline.getPeriod(/* periodIndex= */ 1, period);
|
||||
Timeline.Period contentPeriod5 = timeline.getPeriod(/* periodIndex= */ 5, period);
|
||||
assertThat(contentPeriod1.durationUs).isEqualTo(DEFAULT_WINDOW_DURATION_US / 7);
|
||||
assertThat(contentPeriod5.durationUs).isEqualTo(DEFAULT_WINDOW_DURATION_US / 7);
|
||||
assertThat(contentPeriod1.getAdGroupCount()).isEqualTo(0);
|
||||
assertThat(contentPeriod5.getAdGroupCount()).isEqualTo(0);
|
||||
timeline.getWindow(/* windowIndex= */ 0, window);
|
||||
assertThat(window.uid).isEqualTo(windowId);
|
||||
assertThat(window.durationUs).isEqualTo(contentPeriod1.durationUs + contentPeriod5.durationUs);
|
||||
assertThat(window.positionInFirstPeriodUs).isEqualTo(DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US);
|
||||
// Assert ad periods.
|
||||
int[] adIndices = {0, 2, 3, 4, 6};
|
||||
int adCounter = 0;
|
||||
for (int periodIndex : adIndices) {
|
||||
Timeline.Period adPeriod = timeline.getPeriod(periodIndex, period);
|
||||
assertThat(adPeriod.isServerSideInsertedAdGroup(0)).isTrue();
|
||||
assertThat(adPeriod.getAdGroupCount()).isEqualTo(1);
|
||||
assertThat(adPeriod.durationUs).isEqualTo(0);
|
||||
if (adPeriod.getAdGroupCount() > 0) {
|
||||
if (adCounter < numberOfPlayedAds) {
|
||||
assertThat(adPeriod.getAdState(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
|
||||
.isEqualTo(AdPlaybackState.AD_STATE_PLAYED);
|
||||
} else {
|
||||
assertThat(adPeriod.getAdState(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
|
||||
.isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
|
||||
}
|
||||
adCounter++;
|
||||
}
|
||||
long expectedDurationUs =
|
||||
(DEFAULT_WINDOW_DURATION_US / 7)
|
||||
+ (periodIndex == 0 ? DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US : 0);
|
||||
assertThat(adPeriod.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
|
||||
.isEqualTo(expectedDurationUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMultiPeriodAdTimeline_firstPeriodIsContent_correctWindowDurationUs() {
|
||||
Timeline.Window window = new Timeline.Window();
|
||||
FakeTimeline timeline =
|
||||
FakeTimeline.createMultiPeriodAdTimeline(
|
||||
/* windowId= */ new Object(),
|
||||
/* numberOfPlayedAds= */ 0,
|
||||
/* isAdPeriodFlags...= */ false,
|
||||
true,
|
||||
true,
|
||||
false);
|
||||
|
||||
timeline.getWindow(/* windowIndex= */ 0, window);
|
||||
// Assert content periods and window duration.
|
||||
assertThat(window.durationUs).isEqualTo(DEFAULT_WINDOW_DURATION_US / 2);
|
||||
assertThat(window.positionInFirstPeriodUs).isEqualTo(DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user