mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +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
95abd7d3cc
commit
1900c6c9d4
@ -524,6 +524,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;
|
||||
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.source.ads.AdPlaybackState;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||
import com.google.android.exoplayer2.testutil.TimelineAsserts;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@ -222,7 +223,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,
|
||||
@ -234,7 +235,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());
|
||||
|
@ -1170,7 +1170,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);
|
||||
@ -1484,7 +1484,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()) {
|
||||
@ -2346,7 +2346,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) {
|
||||
@ -2371,9 +2371,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.
|
||||
@ -2387,7 +2387,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return playbackInfo.copyWithNewPosition(
|
||||
mediaPeriodId,
|
||||
positionUs,
|
||||
contentPositionUs,
|
||||
requestedContentPositionUs,
|
||||
discontinuityStartPositionUs,
|
||||
getTotalBufferedDurationUs(),
|
||||
trackGroupArray,
|
||||
@ -2660,7 +2660,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 com.google.android.exoplayer2;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import android.os.Handler;
|
||||
@ -444,21 +445,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -479,8 +466,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);
|
||||
@ -491,6 +491,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
|
||||
@ -645,12 +694,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
|
||||
@ -670,20 +719,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);
|
||||
}
|
||||
|
||||
|
@ -113,6 +113,7 @@ import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
|
||||
import com.google.android.exoplayer2.source.ads.ServerSideAdInsertionMediaSource;
|
||||
import com.google.android.exoplayer2.testutil.Action;
|
||||
import com.google.android.exoplayer2.testutil.ActionSchedule;
|
||||
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable;
|
||||
@ -4974,6 +4975,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();
|
||||
@ -8026,7 +8457,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 =
|
||||
@ -8040,7 +8471,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 =
|
||||
@ -8054,7 +8485,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};
|
||||
@ -8412,7 +8843,7 @@ public final class ExoPlayerTest {
|
||||
/* durationUs= */ 10_000_000,
|
||||
/* defaultPositionUs= */ 0,
|
||||
/* windowOffsetInFirstPeriodUs= */ 0,
|
||||
AdPlaybackState.NONE,
|
||||
ImmutableList.of(AdPlaybackState.NONE),
|
||||
initialMediaItem);
|
||||
TimelineWindowDefinition secondWindow =
|
||||
new TimelineWindowDefinition(
|
||||
@ -8425,7 +8856,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);
|
||||
@ -9259,7 +9690,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(
|
||||
@ -9309,7 +9740,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(
|
||||
@ -9355,7 +9786,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(
|
||||
@ -9403,7 +9834,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(
|
||||
@ -9421,7 +9852,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(
|
||||
@ -9566,7 +9997,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(
|
||||
@ -9620,7 +10051,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(
|
||||
@ -9665,7 +10096,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(
|
||||
@ -9683,7 +10114,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(
|
||||
@ -9732,7 +10163,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(
|
||||
@ -9750,7 +10181,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(
|
||||
@ -9840,7 +10271,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 com.google.android.exoplayer2;
|
||||
|
||||
import static com.google.android.exoplayer2.testutil.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.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
|
||||
@ -473,8 +475,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,
|
||||
@ -734,6 +735,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)
|
||||
|
@ -25,6 +25,7 @@ import com.google.android.exoplayer2.source.ads.AdPlaybackState;
|
||||
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
|
||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/** Fake {@link MediaSourceFactory} that creates a {@link FakeMediaSource}. */
|
||||
// Implement and return deprecated type for backwards compatibility.
|
||||
@ -64,7 +65,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 com.google.android.exoplayer2.testutil;
|
||||
|
||||
import static com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US;
|
||||
import static com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
|
||||
import static com.google.android.exoplayer2.util.Util.sum;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.net.Uri;
|
||||
@ -26,7 +29,13 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.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. */
|
||||
public final class FakeTimeline extends Timeline {
|
||||
@ -50,7 +59,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.
|
||||
@ -177,10 +186,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.
|
||||
*
|
||||
@ -195,7 +235,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(
|
||||
@ -208,7 +248,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;
|
||||
@ -221,7 +261,7 @@ public final class FakeTimeline extends Timeline {
|
||||
this.durationUs = durationUs;
|
||||
this.defaultPositionUs = defaultPositionUs;
|
||||
this.windowOffsetInFirstPeriodUs = windowOffsetInFirstPeriodUs;
|
||||
this.adPlaybackState = adPlaybackState;
|
||||
this.adPlaybackStates = adPlaybackStates;
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,6 +306,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}.
|
||||
@ -361,6 +454,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,
|
||||
@ -374,7 +480,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);
|
||||
@ -394,11 +500,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) {
|
||||
@ -412,9 +522,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;
|
||||
}
|
||||
@ -440,6 +552,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++) {
|
||||
@ -447,4 +575,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 com.google.android.exoplayer2.testutil;
|
||||
|
||||
import static com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US;
|
||||
import static com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
|
||||
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