Skip played live ad media periods

This change makes sure that live ad periods that are played are
skip when attempted to be added to the queue. To make this work
the existing filter logic had to be take into account the content
resume offset that live periods use.

PiperOrigin-RevId: 518068138
This commit is contained in:
bachinger 2023-03-20 20:55:43 +00:00 committed by microkatz
parent fca9197e1c
commit 0a2a20d4fb
6 changed files with 109 additions and 26 deletions

View File

@ -441,7 +441,8 @@ public class TimelineTest {
/* nowUs= */ 60_000_000,
/* adSequencePattern= */ new boolean[] {false, true, true},
/* isContentTimeline= */ false,
/* populateAds= */ true);
/* populateAds= */ true,
/* playedAds= */ false);
assertThat(timeline.getPeriodCount()).isEqualTo(4);
assertThat(

View File

@ -474,13 +474,10 @@ import com.google.common.collect.ImmutableList;
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) {
for (int periodIndex = timeline.getIndexOfPeriod(periodUid);
isSkippableAdPeriod(period) && periodIndex <= window.lastPeriodIndex;
periodIndex++) {
timeline.getPeriod(periodIndex, period, /* setIds= */ true);
periodUid = checkNotNull(period.uid);
}
@ -495,6 +492,26 @@ import com.google.common.collect.ImmutableList;
}
}
private static boolean isSkippableAdPeriod(Timeline.Period period) {
int adGroupCount = period.getAdGroupCount();
if (adGroupCount == 0
|| (adGroupCount == 1 && period.isLivePostrollPlaceholder(/* adGroupIndex= */ 0))
|| !period.isServerSideInsertedAdGroup(period.getRemovedAdGroupCount())
|| period.getAdGroupIndexForPositionUs(0L) != C.INDEX_UNSET) {
return false;
}
if (period.durationUs == 0) {
return true;
}
long contentResumeOffsetUs = 0;
int lastIndexInclusive =
adGroupCount - (period.isLivePostrollPlaceholder(adGroupCount - 1) ? 2 : 1);
for (int i = 0; i <= lastIndexInclusive; i++) {
contentResumeOffsetUs += period.getContentResumeOffsetUs(/* adGroupIndex= */ i);
}
return period.durationUs <= contentResumeOffsetUs;
}
/**
* 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

View File

@ -438,7 +438,8 @@ public final class MediaPeriodQueueTest {
/* nowUs= */ 110_000_000,
new boolean[] {false, true, true},
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
setupTimeline(multiPeriodLiveTimeline);
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
/* periodUid= */ firstPeriodUid,
@ -504,7 +505,8 @@ public final class MediaPeriodQueueTest {
/* nowUs= */ 110_000_000,
new boolean[] {false, true, true},
/* isContentTimeline= */ false,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
setupTimeline(multiPeriodLiveTimeline);
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
/* periodUid= */ firstPeriodUid,
@ -569,7 +571,8 @@ public final class MediaPeriodQueueTest {
/* nowUs= */ 110_000_000,
new boolean[] {false, true, true},
/* isContentTimeline= */ false,
/* populateAds= */ true);
/* populateAds= */ true,
/* playedAds= */ false);
setupTimeline(multiPeriodLiveTimeline);
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
/* periodUid= */ firstPeriodUid,
@ -638,6 +641,47 @@ public final class MediaPeriodQueueTest {
assertThat(getNextMediaPeriodInfo()).isNull();
}
@Test
@SuppressWarnings("unchecked")
public void getNextMediaPeriodInfo_multiPeriodTimelineWithPlayedAdsAndWithPostRollPlaceHolder() {
long contentPeriodDurationUs = FakeMultiPeriodLiveTimeline.PERIOD_DURATION_US;
FakeMultiPeriodLiveTimeline multiPeriodLiveTimeline =
new FakeMultiPeriodLiveTimeline(
/* availabilityStartTimeUs= */ 0,
/* liveWindowDurationUs= */ 60_000_000,
/* nowUs= */ 110_000_000,
new boolean[] {false, true, true},
/* isContentTimeline= */ false,
/* populateAds= */ true,
/* playedAds= */ true);
setupTimeline(multiPeriodLiveTimeline);
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
/* periodUid= */ firstPeriodUid,
/* startPositionUs= */ 0,
/* requestedContentPositionUs= */ C.TIME_UNSET,
/* endPositionUs= */ C.TIME_UNSET,
/* durationUs= */ contentPeriodDurationUs,
/* isFollowedByTransitionToSameStream= */ false,
/* isLastInPeriod= */ false,
/* isLastInWindow= */ false,
/* isFinal= */ false,
/* nextAdGroupIndex= */ 0);
advance();
assertGetNextMediaPeriodInfoReturnsContentMediaPeriod(
new Pair<Object, Object>(((Pair<Object, Object>) firstPeriodUid).first, "uid-6[c]"),
/* startPositionUs= */ 0,
/* requestedContentPositionUs= */ 0,
/* endPositionUs= */ C.TIME_UNSET,
/* durationUs= */ C.TIME_UNSET, // Last period in stream.
/* isFollowedByTransitionToSameStream= */ false,
/* isLastInPeriod= */ false,
/* isLastInWindow= */ false,
/* isFinal= */ false,
/* nextAdGroupIndex= */ 0);
advance();
assertThat(getNextMediaPeriodInfo()).isNull();
}
@Test
public void getNextMediaPeriodInfo_withPostrollLoadError_returnsEmptyFinalMediaPeriodInfo() {
setupAdTimeline(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE);

View File

@ -566,7 +566,8 @@ public class ImaUtilTest {
/* nowUs= */ 150_000_000,
/* adSequencePattern= */ new boolean[] {false, true, true},
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
// Ad event received from SDK around 130s.
adPlaybackState =
addLiveAdBreak(
@ -855,7 +856,8 @@ public class ImaUtilTest {
/* nowUs= */ 59_999_999,
/* adSequencePattern= */ new boolean[] {false, true, true},
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
// Ad event received from SDK around 30s.
adPlaybackState =
addLiveAdBreak(
@ -1296,7 +1298,8 @@ public class ImaUtilTest {
/* nowUs= */ 150_000_000,
/* adSequencePattern= */ new boolean[] {false, true, true},
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
AdPlaybackState adPlaybackState =
new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
adPlaybackState =

View File

@ -57,6 +57,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
private final long liveWindowDurationUs;
private final boolean isContentTimeline;
private final boolean populateAds;
private final boolean playedAds;
private long nowUs;
private ImmutableList<PeriodData> periods;
@ -72,7 +73,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
* @param isContentTimeline Whether the timeline is a content timeline without {@link
* AdPlaybackState}s.
* @param populateAds Whether to populate ads like after the ad event has been received. This
* parameter is ignored if the timeline is a content timeline.
* @param playedAds Whether ads should be marked as played if populated.
*/
public FakeMultiPeriodLiveTimeline(
long availabilityStartTimeUs,
@ -80,7 +81,8 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
long nowUs,
boolean[] adSequencePattern,
boolean isContentTimeline,
boolean populateAds) {
boolean populateAds,
boolean playedAds) {
checkArgument(nowUs - liveWindowDurationUs >= availabilityStartTimeUs);
this.availabilityStartTimeUs = availabilityStartTimeUs;
this.liveWindowDurationUs = liveWindowDurationUs;
@ -88,6 +90,7 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
this.adSequencePattern = Arrays.copyOf(adSequencePattern, adSequencePattern.length);
this.isContentTimeline = isContentTimeline;
this.populateAds = populateAds;
this.playedAds = playedAds;
mediaItem = new MediaItem.Builder().build();
periods =
invalidate(
@ -96,7 +99,8 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
nowUs,
adSequencePattern,
isContentTimeline,
populateAds);
populateAds,
playedAds);
}
/** Calculates the total duration of the given ad period sequence. */
@ -118,7 +122,8 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
nowUs,
adSequencePattern,
isContentTimeline,
populateAds);
populateAds,
playedAds);
}
@Override
@ -188,7 +193,8 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
long now,
boolean[] adSequencePattern,
boolean isContentTimeline,
boolean populateAds) {
boolean populateAds,
boolean playedAds) {
long windowStartTimeUs = now - liveWindowDurationUs;
int sequencePeriodCount = adSequencePattern.length;
long sequenceDurationUs = calculateAdSequencePatternDurationUs(adSequencePattern);
@ -226,6 +232,10 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
/* adGroupIndex= */ 0, /* adDurationsUs...= */ periodDurationUs)
.withContentResumeOffsetUs(
/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ periodDurationUs);
if (playedAds) {
adPlaybackState =
adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
}
}
}
liveWindow.add(

View File

@ -43,7 +43,8 @@ public class FakeMultiPeriodLiveTimelineTest {
/* nowUs= */ 60_000_000L,
adSequencePattern,
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
Timeline.Period period = new Timeline.Period();
Timeline.Window window = new Timeline.Window();
@ -79,7 +80,8 @@ public class FakeMultiPeriodLiveTimelineTest {
/* nowUs= */ 100_000_000L,
adSequencePattern,
/* isContentTimeline= */ false,
/* populateAds= */ true);
/* populateAds= */ true,
/* playedAds= */ false);
Timeline.Period period = new Timeline.Period();
Timeline.Window window = new Timeline.Window();
@ -138,7 +140,8 @@ public class FakeMultiPeriodLiveTimelineTest {
/* nowUs= */ 100_000_000L,
adSequencePattern,
/* isContentTimeline= */ false,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
Timeline.Period period = new Timeline.Period();
Timeline.Window window = new Timeline.Window();
@ -189,7 +192,8 @@ public class FakeMultiPeriodLiveTimelineTest {
/* nowUs= */ 60_000_123L,
adSequencePattern,
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
Timeline.Period period = new Timeline.Period();
Timeline.Window window = new Timeline.Window();
@ -273,7 +277,8 @@ public class FakeMultiPeriodLiveTimelineTest {
nowUs,
adSequencePattern,
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ true);
assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs)
.isEqualTo(Util.usToMs(nowUs - liveWindowDurationUs));
@ -333,7 +338,8 @@ public class FakeMultiPeriodLiveTimelineTest {
nowUs,
adSequencePattern,
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
assertThat(timeline.getWindow(0, new Timeline.Window()).windowStartTimeMs)
.isEqualTo(Util.usToMs(nowUs - liveWindowDurationUs));
@ -353,7 +359,8 @@ public class FakeMultiPeriodLiveTimelineTest {
/* nowUs= */ 120_000_000L,
new boolean[] {false, true, true, true},
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
Timeline.Period period = new Timeline.Period();
Timeline.Window window = new Timeline.Window();
@ -390,7 +397,8 @@ public class FakeMultiPeriodLiveTimelineTest {
/* nowUs= */ 250_000_000L,
new boolean[] {false, true, false, true, false},
/* isContentTimeline= */ true,
/* populateAds= */ false);
/* populateAds= */ false,
/* playedAds= */ false);
assertThat(timeline.getPeriodCount()).isEqualTo(10);
assertThat(timeline.getWindow(0, window).windowStartTimeMs).isEqualTo(30_000L);