Correct ad durations when timeline moves more than a single period
This change improves `ImaUtil.maybeCorrectPreviouslyUnknownAdDuration` to handles the case when the timeline moves forward more than a single period while an ad group with unknown period duration is being played. PiperOrigin-RevId: 522292612
This commit is contained in:
parent
14ba173dfe
commit
76e195ff5a
@ -28,7 +28,7 @@ import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInVodMulti
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupDurationUsForLiveAdPeriodIndex;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.getWindowStartTimeUs;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.handleAdPeriodRemovedFromTimeline;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.maybeCorrectPreviouslyUnknownAdDuration;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.maybeCorrectPreviouslyUnknownAdDurations;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.secToMsRounded;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.secToUsRounded;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.splitAdGroup;
|
||||
@ -691,7 +691,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||
// If the ad started playing while the corresponding period in the timeline had an unknown
|
||||
// duration, the ad duration is estimated and needs to be corrected when the actual duration
|
||||
// is reported.
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
}
|
||||
this.contentTimeline = contentTimeline;
|
||||
invalidateServerSideAdInsertionAdPlaybackState();
|
||||
|
@ -46,6 +46,7 @@ import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSchemeDataSource;
|
||||
import androidx.media3.datasource.DataSourceUtil;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import com.google.ads.interactivemedia.v3.api.Ad;
|
||||
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
|
||||
import com.google.ads.interactivemedia.v3.api.AdError;
|
||||
import com.google.ads.interactivemedia.v3.api.AdErrorEvent;
|
||||
@ -537,23 +538,30 @@ import java.util.Set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a previously estimated ad duration with the period duration from the timeline.
|
||||
* Updates previously inserted ad durations with actual period durations from the timeline and
|
||||
* returns the updated {@linkplain AdPlaybackState ad playback state}.
|
||||
*
|
||||
* <p>This method must only be called for multi period live streams and is useful in the case that
|
||||
* an ad started playing while its period duration was still unknown. In this case the estimated
|
||||
* ad duration was used which can be corrected as soon as the {@code contentTimeline} was
|
||||
* refreshed with the actual period duration.
|
||||
* {@linkplain #addLiveAdBreak(long, long, int, long, int, AdPlaybackState) a live ad has been
|
||||
* inserted} while the duration of the corresponding period was still unknown. In this case the
|
||||
* {@linkplain Ad#getDuration() estimated ad duration} was used which must be corrected as soon as
|
||||
* the live window of the {@code contentTimeline} advances and the previously unknown period
|
||||
* duration is available.
|
||||
*
|
||||
* <p>The method queries the {@linkplain AdPlaybackState ad playback state} for an ad that starts
|
||||
* at the period start time of the last period that has a known duration. If found, the ad
|
||||
* duration is set to the period duration and the new ad playback state is returned. If not found
|
||||
* or the duration is already correct the ad playback state remains unchanged.
|
||||
* <p>Roughly, the logic checks whether an ad group of the ad playback state fits in or overlaps
|
||||
* one or several periods in the content timeline. Starting at the first ad inside the window, the
|
||||
* ad duration is set to the duration of the corresponding period until a period with an unknown
|
||||
* duration or the end of the ad group is reached.
|
||||
*
|
||||
* <p>If the previously playing ad period isn't available in the content timeline anymore, no
|
||||
* correction is applied. The resulting position discontinuity of {@link
|
||||
* Player#DISCONTINUITY_REASON_REMOVE} needs to be handled accordingly elsewhere.
|
||||
*
|
||||
* @param contentTimeline The live content timeline.
|
||||
* @param adPlaybackState The ad playback state.
|
||||
* @return The (potentially) updated ad playback state.
|
||||
*/
|
||||
public static AdPlaybackState maybeCorrectPreviouslyUnknownAdDuration(
|
||||
public static AdPlaybackState maybeCorrectPreviouslyUnknownAdDurations(
|
||||
Timeline contentTimeline, AdPlaybackState adPlaybackState) {
|
||||
Timeline.Window window = contentTimeline.getWindow(/* windowIndex= */ 0, new Timeline.Window());
|
||||
if (window.firstPeriodIndex == window.lastPeriodIndex || adPlaybackState.adGroupCount < 2) {
|
||||
@ -561,51 +569,68 @@ import java.util.Set;
|
||||
return adPlaybackState;
|
||||
}
|
||||
Timeline.Period period = new Timeline.Period();
|
||||
// Get the first period from the end with a known duration.
|
||||
int periodIndex = window.lastPeriodIndex;
|
||||
while (periodIndex >= window.firstPeriodIndex
|
||||
&& contentTimeline.getPeriod(periodIndex, period).durationUs == C.TIME_UNSET) {
|
||||
periodIndex--;
|
||||
int lastPeriodIndex = window.lastPeriodIndex;
|
||||
if (contentTimeline.getPeriod(lastPeriodIndex, period).durationUs == C.TIME_UNSET) {
|
||||
lastPeriodIndex--;
|
||||
contentTimeline.getPeriod(lastPeriodIndex, period);
|
||||
}
|
||||
// Search for an ad group at or before the period start.
|
||||
// Search for an unplayed ad group at or before the period start.
|
||||
long windowStartTimeUs =
|
||||
getWindowStartTimeUs(window.windowStartTimeMs, window.positionInFirstPeriodUs);
|
||||
long periodStartTimeUs = windowStartTimeUs + period.positionInWindowUs;
|
||||
long lastCompletePeriodStartTimeUs = windowStartTimeUs + period.positionInWindowUs;
|
||||
int adGroupIndex =
|
||||
adPlaybackState.getAdGroupIndexForPositionUs(
|
||||
periodStartTimeUs, /* periodDurationUs= */ C.TIME_UNSET);
|
||||
lastCompletePeriodStartTimeUs, /* periodDurationUs= */ C.TIME_UNSET);
|
||||
if (adGroupIndex == C.INDEX_UNSET) {
|
||||
// No ad group at or before the period start.
|
||||
// No unplayed ads before the last period with a duration. Nothing to do.
|
||||
return adPlaybackState;
|
||||
}
|
||||
|
||||
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
|
||||
if (adGroup.timeUs + adGroup.contentResumeOffsetUs < periodStartTimeUs) {
|
||||
// Ad group ends before the period starts.
|
||||
|
||||
long periodStartTimeUs = windowStartTimeUs - window.positionInFirstPeriodUs;
|
||||
if (adGroup.timeUs + adGroup.contentResumeOffsetUs <= periodStartTimeUs) {
|
||||
// Ad group ends before first period in window. Discontinuity of reason REMOVE.
|
||||
return adPlaybackState;
|
||||
}
|
||||
// Period is inside the ad group. Get ad start that matches the period start.
|
||||
long adGroupDurationUs = 0;
|
||||
for (int adIndex = 0; adIndex < adGroup.durationsUs.length; adIndex++) {
|
||||
long adDurationUs = adGroup.durationsUs[adIndex];
|
||||
if (adGroup.timeUs + adGroupDurationUs < periodStartTimeUs) {
|
||||
adGroupDurationUs += adDurationUs;
|
||||
continue;
|
||||
}
|
||||
if (period.durationUs == adDurationUs) {
|
||||
// No update required.
|
||||
// The ads at the start of the ad group may be out of the window already. Skip them.
|
||||
int firstAdIndexInWindow = 0;
|
||||
long adStartTimeUs = adGroup.timeUs;
|
||||
while (adStartTimeUs < periodStartTimeUs) {
|
||||
if (adGroup.states[firstAdIndexInWindow] == AD_STATE_AVAILABLE) {
|
||||
// The previously available ad is not in the timeline anymore. Discontinuity of reason
|
||||
// `DISCONTINUITY_REASON_REMOVE`.
|
||||
return adPlaybackState;
|
||||
}
|
||||
// Set the ad duration to the period duration.
|
||||
adPlaybackState =
|
||||
updateAdDurationInAdGroup(
|
||||
adGroupIndex, /* adIndexInAdGroup= */ adIndex, period.durationUs, adPlaybackState);
|
||||
// Get the ad group again and set the new content resume offset after update.
|
||||
adGroupDurationUs = sum(adPlaybackState.getAdGroup(adGroupIndex).durationsUs);
|
||||
return adPlaybackState.withContentResumeOffsetUs(adGroupIndex, adGroupDurationUs);
|
||||
// Skip ad before first period of window.
|
||||
adStartTimeUs += adGroup.durationsUs[firstAdIndexInWindow++];
|
||||
}
|
||||
// Return unchanged.
|
||||
return adPlaybackState;
|
||||
int firstPeriodIndexInAdGroup = C.INDEX_UNSET;
|
||||
for (int i = window.firstPeriodIndex; i <= lastPeriodIndex; i++) {
|
||||
if (adGroup.timeUs <= periodStartTimeUs) {
|
||||
firstPeriodIndexInAdGroup = i;
|
||||
break;
|
||||
}
|
||||
periodStartTimeUs += contentTimeline.getPeriod(/* periodIndex= */ i, period).durationUs;
|
||||
}
|
||||
checkState(firstPeriodIndexInAdGroup != C.INDEX_UNSET);
|
||||
|
||||
// Update all ad durations that we know and are not yet correct.
|
||||
for (int i = firstAdIndexInWindow; i < adGroup.durationsUs.length; i++) {
|
||||
int adPeriodIndex = firstPeriodIndexInAdGroup + (i - firstAdIndexInWindow);
|
||||
if (adPeriodIndex > lastPeriodIndex) {
|
||||
break;
|
||||
}
|
||||
contentTimeline.getPeriod(adPeriodIndex, period);
|
||||
if (period.durationUs != adGroup.durationsUs[i]) {
|
||||
// Set the ad duration to the period duration.
|
||||
adPlaybackState =
|
||||
updateAdDurationInAdGroup(
|
||||
adGroupIndex, /* adIndexInAdGroup= */ i, period.durationUs, adPlaybackState);
|
||||
}
|
||||
}
|
||||
// Get the ad group again and set the new content resume offset after update.
|
||||
long adGroupDurationUs = sum(adPlaybackState.getAdGroup(adGroupIndex).durationsUs);
|
||||
return adPlaybackState.withContentResumeOffsetUs(adGroupIndex, adGroupDurationUs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,7 @@ import static androidx.media3.exoplayer.ima.ImaUtil.addLiveAdBreak;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInLiveMultiPeriodTimeline;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInVodMultiPeriodTimeline;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.handleAdPeriodRemovedFromTimeline;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.maybeCorrectPreviouslyUnknownAdDuration;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.maybeCorrectPreviouslyUnknownAdDurations;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.secToUsRounded;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.splitAdGroup;
|
||||
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState;
|
||||
@ -1074,7 +1074,7 @@ public class ImaUtilTest {
|
||||
/* populateAds= */ false,
|
||||
/* playedAds= */ false);
|
||||
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).timeUs).isEqualTo(80_000_000L);
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
@ -1116,7 +1116,7 @@ public class ImaUtilTest {
|
||||
/* populateAds= */ false,
|
||||
/* playedAds= */ false);
|
||||
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).timeUs).isEqualTo(80_000_000L);
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
@ -1135,14 +1135,8 @@ public class ImaUtilTest {
|
||||
addAdGroupToAdPlaybackState(
|
||||
adPlaybackState,
|
||||
/* fromPositionUs= */ 80_000_000L,
|
||||
/* contentResumeOffsetUs= */ 123,
|
||||
/* adDurationsUs...= */ 123);
|
||||
adPlaybackState =
|
||||
addAdGroupToAdPlaybackState(
|
||||
adPlaybackState,
|
||||
/* fromPositionUs= */ 90_000_000L,
|
||||
/* contentResumeOffsetUs= */ 123,
|
||||
/* adDurationsUs...= */ 123);
|
||||
/* contentResumeOffsetUs= */ 123L,
|
||||
/* adDurationsUs...= */ 123L);
|
||||
FakeMultiPeriodLiveTimeline contentTimeline =
|
||||
new FakeMultiPeriodLiveTimeline(
|
||||
/* availabilityStartTimeMs= */ 0,
|
||||
@ -1157,7 +1151,7 @@ public class ImaUtilTest {
|
||||
/* playedAds= */ false);
|
||||
|
||||
AdPlaybackState correctedAdPlaybackState =
|
||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
||||
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(contentTimeline.getWindow(/* windowIndex= */ 0, new Window()).windowStartTimeMs)
|
||||
.isEqualTo(100_000L);
|
||||
@ -1186,117 +1180,459 @@ public class ImaUtilTest {
|
||||
/* playedAds= */ false);
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
|
||||
// Insert first ad resulting in group [10_000, 20_000, 0]
|
||||
// Insert first ad resulting in group [10_000_000, 29_000_123, 0, 0]
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 30_000_000,
|
||||
/* adDurationUs= */ 10_000_000,
|
||||
/* adPositionInAdPod= */ 1,
|
||||
/* totalAdDurationUs= */ 40_000_123,
|
||||
/* totalAdDurationUs= */ 39_000_123,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
adPlaybackState);
|
||||
|
||||
AdPlaybackState correctedAdPlaybackState =
|
||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
||||
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
// Assert starting point: no change because the second ad period is still last of window.
|
||||
// Assert no change because the second ad period is still last of window.
|
||||
assertThat(correctedAdPlaybackState).isSameInstanceAs(adPlaybackState);
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 30_000_123L, 0L, 0L)
|
||||
.inOrder();
|
||||
|
||||
// Get third ad period into timeline so the second ad period gets a duration: [c, a, a, a]
|
||||
// Get third ad period into timeline so the second ad period gets a duration: [c, a, a, a], a
|
||||
contentTimeline.advanceNowUs(1L);
|
||||
correctedAdPlaybackState =
|
||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, correctedAdPlaybackState);
|
||||
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, correctedAdPlaybackState);
|
||||
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 10_000_000L, 20_000_123L, 0L)
|
||||
.containsExactly(10_000_000L, 10_000_000L, 19_000_123L, 0L)
|
||||
.inOrder();
|
||||
|
||||
// Get next ad period into timeline so the third ad period gets a duration: [c, a, a, a, a]
|
||||
// Second ad event resulting in group [10_000_000, 10_000_000, 19_000_123, 0]
|
||||
correctedAdPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 40_000_000L,
|
||||
/* adDurationUs= */ 10_000_000L,
|
||||
/* adPositionInAdPod= */ 2,
|
||||
/* totalAdDurationUs= */ 39_000_123L,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
correctedAdPlaybackState);
|
||||
|
||||
// Get last ad period into timeline so the third ad period gets a duration: [c, a, a, a, a]
|
||||
contentTimeline.advanceNowUs(10_000_000L);
|
||||
correctedAdPlaybackState =
|
||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, correctedAdPlaybackState);
|
||||
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, correctedAdPlaybackState);
|
||||
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 10_000_123L)
|
||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 9_000_123L)
|
||||
.inOrder();
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
||||
.asList()
|
||||
.containsExactly(1, 0, 0, 0)
|
||||
.containsExactly(1, 1, 0, 0)
|
||||
.inOrder();
|
||||
|
||||
// It doesn't matter whether the live break event or the correction propagates the remainder
|
||||
// forward. Updating the ad by ad event later only marks the ad as available.
|
||||
// The event of the previously corrected ad sets the same duration and marks the ad available.
|
||||
correctedAdPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 40_000_000,
|
||||
/* adDurationUs= */ 10_000_000,
|
||||
/* adPositionInAdPod= */ 2,
|
||||
/* totalAdDurationUs= */ 40_000_000,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
correctedAdPlaybackState);
|
||||
|
||||
// No change in durations.
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 10_000_123L)
|
||||
.inOrder();
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
||||
.asList()
|
||||
.containsExactly(1, 1, 0, 0);
|
||||
|
||||
correctedAdPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 40_000_000,
|
||||
/* adDurationUs= */ 10_000_000,
|
||||
/* currentContentPeriodPositionUs= */ 50_000_000L,
|
||||
/* adDurationUs= */ 10_000_000L,
|
||||
/* adPositionInAdPod= */ 3,
|
||||
/* totalAdDurationUs= */ 40_000_000,
|
||||
/* totalAdDurationUs= */ 39_000_123L,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
correctedAdPlaybackState);
|
||||
|
||||
// No change in durations.
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 10_000_123L)
|
||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 9_000_123L)
|
||||
.inOrder();
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
||||
.asList()
|
||||
.containsExactly(1, 1, 1, 0);
|
||||
|
||||
// The last ad is inserted with ad pod duration 123 as fallback of the missing duration.
|
||||
correctedAdPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 40_000_000,
|
||||
/* adDurationUs= */ 9_999_999L,
|
||||
/* adPositionInAdPod= */ 3,
|
||||
/* totalAdDurationUs= */ 40_000_000,
|
||||
/* currentContentPeriodPositionUs= */ 40_000_000L,
|
||||
/* adDurationUs= */ 123L,
|
||||
/* adPositionInAdPod= */ 4,
|
||||
/* totalAdDurationUs= */ 40_000_000L,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
correctedAdPlaybackState);
|
||||
|
||||
// Last duration updated with ad pod duration.
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 9_999_999L)
|
||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 123L)
|
||||
.inOrder();
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
||||
.asList()
|
||||
.containsExactly(1, 1, 1, 1);
|
||||
|
||||
// Get next period into timeline so the 4th ad period gets a duration: [..., a, a, c]
|
||||
// Get period after the ad group into timeline. All ad periods have a duration: [..., a, a, c]
|
||||
contentTimeline.advanceNowUs(10_000_000L);
|
||||
correctedAdPlaybackState =
|
||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, correctedAdPlaybackState);
|
||||
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, correctedAdPlaybackState);
|
||||
|
||||
// Last duration corrected when period arrives.
|
||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 10_000_000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
maybeCorrectPreviouslyUnknownAdDuration_timelineMovesMultiplePeriodsForward_adDurationCorrected() {
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
|
||||
// Timeline window to start with: c, a, a, a, [a, c, a], a, a, a
|
||||
FakeMultiPeriodLiveTimeline contentTimeline =
|
||||
new FakeMultiPeriodLiveTimeline(
|
||||
/* availabilityStartTimeMs= */ 0,
|
||||
/* liveWindowDurationUs= */ 40_000_321L,
|
||||
/* nowUs= */ 109_234_000L,
|
||||
/* adSequencePattern= */ new boolean[] {false, true, true, true, true},
|
||||
/* periodDurationMsPattern= */ new long[] {
|
||||
PERIOD_DURATION_MS, 10_123L, 10_457L, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS
|
||||
},
|
||||
/* isContentTimeline= */ true,
|
||||
/* populateAds= */ false,
|
||||
/* playedAds= */ false);
|
||||
// Get the period start position at which to insert the ad.
|
||||
long adPeriodStartTimeUs =
|
||||
contentTimeline.getWindowStartTimeUs()
|
||||
+ contentTimeline.getPeriod(/* periodIndex= */ 2, new Period()).positionInWindowUs;
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ adPeriodStartTimeUs,
|
||||
/* adDurationUs= */ 123L, // Incorrect duration to be corrected.
|
||||
/* adPositionInAdPod= */ 1,
|
||||
/* totalAdDurationUs= */ 28_000_000L,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(123L, 27_999_877L, 0L, 0L)
|
||||
.inOrder();
|
||||
|
||||
// Advance the live window in timeline: c, a, a, a, a, [c, a, a, a], a, c
|
||||
contentTimeline.advanceNowUs(20_000_000L);
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_123_000L, 10_457_000L, 17_542_877L, 0L)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
maybeCorrectPreviouslyUnknownAdDuration_allPeriodsInWindowWithKnownDuration_adDurationCorrected() {
|
||||
// Timeline with window: c, a, a, a, a, [c, a, a, a], a, c
|
||||
long nowUs = 38_064_000L + 38_064_000L - 3_333_000L;
|
||||
long liveWindowDurationUs = 4_731_351L;
|
||||
FakeMultiPeriodLiveTimeline contentTimeline =
|
||||
new FakeMultiPeriodLiveTimeline(
|
||||
/* availabilityStartTimeMs= */ 0,
|
||||
/* liveWindowDurationUs= */ liveWindowDurationUs,
|
||||
nowUs,
|
||||
/* adSequencePattern= */ new boolean[] {false, true, true, true, true},
|
||||
/* periodDurationMsPattern= */ new long[] {
|
||||
PERIOD_DURATION_MS, 1_231L, 2_000L, 1_500L, 3_333L
|
||||
},
|
||||
/* isContentTimeline= */ true,
|
||||
/* populateAds= */ false,
|
||||
/* playedAds= */ false) {
|
||||
@Override
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
super.getPeriod(periodIndex, period, setIds);
|
||||
if (periodIndex == 3 && period.durationUs == C.TIME_UNSET) {
|
||||
// Normally the FakeMultiPeriodLiveTimeline sets the last period to an unknown
|
||||
// duration. Make sure that the correct duration is used when overriding.
|
||||
long positionInFirstPeriodUs =
|
||||
getWindow(period.windowIndex, new Window()).positionInFirstPeriodUs;
|
||||
period.durationUs = positionInFirstPeriodUs != 0 ? 1_500_000L : 3_333_000L;
|
||||
}
|
||||
return period;
|
||||
}
|
||||
};
|
||||
Window window = contentTimeline.getWindow(0, new Window());
|
||||
long windowStartTimeUs =
|
||||
ImaUtil.getWindowStartTimeUs(window.windowStartTimeMs, window.positionInFirstPeriodUs);
|
||||
long firstAdPeriodStartTimeUs =
|
||||
windowStartTimeUs
|
||||
+ contentTimeline.getPeriod(/* periodIndex= */ 1, new Period()).positionInWindowUs;
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ firstAdPeriodStartTimeUs,
|
||||
/* adDurationUs= */ 753L,
|
||||
/* adPositionInAdPod= */ 1,
|
||||
/* totalAdDurationUs= */ 8_000_000L,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(753L, 7_999_247L, 0L, 0L)
|
||||
.inOrder();
|
||||
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(1_231_000L, 2_000_000L, 1_500_000L, 4_499_247L)
|
||||
.inOrder();
|
||||
|
||||
// After advancing: c, a, a, a, a, c, [a, a, a, a], c
|
||||
contentTimeline.advanceNowUs(351L);
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(1_231_000L, 2_000_000L, 1_500_000L, 3_333_000L)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
maybeCorrectPreviouslyUnknownAdDuration_timelineMovesMultiplePeriodsForwardStartOfAdGroupNotInWindow_adDurationCorrected() {
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
|
||||
// Window with content and ad periods: c, a, a, a, a, [c, a, a], a, a, c
|
||||
// Supposed insertion of ad for period with unknown duration.
|
||||
// durationsUs: [10_000_000L, 28_000_000L, 0L, 0L]
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 100_000_000L,
|
||||
/* adDurationUs= */ 10_000_000L,
|
||||
/* adPositionInAdPod= */ 1,
|
||||
/* totalAdDurationUs= */ 38_000_000L,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
adPlaybackState);
|
||||
adPlaybackState =
|
||||
adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
|
||||
// durationsUs: [10_000_000L, 123L, 27_999_877L, 0L]
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 110_000_000L,
|
||||
/* adDurationUs= */ 123L,
|
||||
/* adPositionInAdPod= */ 2,
|
||||
/* totalAdDurationUs= */ 38_000_000L,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
adPlaybackState);
|
||||
// Correct with window that move more than a single period: c, a, a, a, a, c, a, [a, a, a, c]
|
||||
FakeMultiPeriodLiveTimeline contentTimeline =
|
||||
new FakeMultiPeriodLiveTimeline(
|
||||
/* availabilityStartTimeMs= */ 0,
|
||||
/* liveWindowDurationUs= */ 40_000_000L,
|
||||
/* nowUs= */ 159_234_567L,
|
||||
/* adSequencePattern= */ new boolean[] {false, true, true, true, true},
|
||||
/* periodDurationMsPattern= */ new long[] {
|
||||
PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS
|
||||
},
|
||||
/* isContentTimeline= */ true,
|
||||
/* populateAds= */ false,
|
||||
/* playedAds= */ false);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 123L, 27_999_877L, 0L)
|
||||
.inOrder();
|
||||
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 10_000_000L)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
maybeCorrectPreviouslyUnknownAdDuration_timelineMovesMultiplePeriodsForwardWithinAdOnlyWindow_adDurationCorrected() {
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
|
||||
// Supposed window when inserting ads: c, a, a, [a, a, a], a, a, a, c
|
||||
// durationsUs: [10_000_000L, 10_000_000L, 10_000_000L, 10_000_000L, 123L, 0, 0, 0]
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 30_000_000L,
|
||||
/* adDurationUs= */ 10_000_000L,
|
||||
/* adPositionInAdPod= */ 1,
|
||||
/* totalAdDurationUs= */ 78_000_000L,
|
||||
/* totalAdsInAdPod= */ 8,
|
||||
adPlaybackState);
|
||||
adPlaybackState =
|
||||
adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 40_000_000L,
|
||||
/* adDurationUs= */ 10_000_000L,
|
||||
/* adPositionInAdPod= */ 2,
|
||||
/* totalAdDurationUs= */ 78_000_000L,
|
||||
/* totalAdsInAdPod= */ 8,
|
||||
adPlaybackState);
|
||||
adPlaybackState =
|
||||
adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1);
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 50_000_000L,
|
||||
/* adDurationUs= */ 10_000_000L,
|
||||
/* adPositionInAdPod= */ 3,
|
||||
/* totalAdDurationUs= */ 78_000_000L,
|
||||
/* totalAdsInAdPod= */ 8,
|
||||
adPlaybackState);
|
||||
adPlaybackState =
|
||||
adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 2);
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 60_000_000L,
|
||||
/* adDurationUs= */ 10_000_000L,
|
||||
/* adPositionInAdPod= */ 4,
|
||||
/* totalAdDurationUs= */ 78_000_000L,
|
||||
/* totalAdsInAdPod= */ 8,
|
||||
adPlaybackState);
|
||||
adPlaybackState =
|
||||
adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 3);
|
||||
// Ad event for the ad period that is last in the window.
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 70_000_000L,
|
||||
/* adDurationUs= */ 123L,
|
||||
/* adPositionInAdPod= */ 4,
|
||||
/* totalAdDurationUs= */ 78_000_000L,
|
||||
/* totalAdsInAdPod= */ 8,
|
||||
adPlaybackState);
|
||||
// Correct with window that move more than a single period: c, a, a, a, a, [a, a, a, a], c
|
||||
// Still playing at adIndex=4
|
||||
FakeMultiPeriodLiveTimeline contentTimeline =
|
||||
new FakeMultiPeriodLiveTimeline(
|
||||
/* availabilityStartTimeMs= */ 0,
|
||||
/* liveWindowDurationUs= */ 30_000_000L,
|
||||
/* nowUs= */ 109_234_567L,
|
||||
/* adSequencePattern= */ new boolean[] {
|
||||
false, true, true, true, true, true, true, true, true
|
||||
},
|
||||
/* periodDurationMsPattern= */ new long[] {
|
||||
PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS,
|
||||
AD_PERIOD_DURATION_MS
|
||||
},
|
||||
/* isContentTimeline= */ true,
|
||||
/* populateAds= */ false,
|
||||
/* playedAds= */ false);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(
|
||||
10_000_000L, 10_000_000L, 10_000_000L, 10_000_000L, 123L, 37_999_877L, 0L, 0L)
|
||||
.inOrder();
|
||||
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
17_999_877L)
|
||||
.inOrder();
|
||||
|
||||
adPlaybackState =
|
||||
adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 4);
|
||||
// Advance to get a duration for the last ad period: c, a, a, a, a, a, [a, a, a, c]
|
||||
contentTimeline.advanceNowUs(/* durationUs= */ 10_000_000L);
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
10_000_000L)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeCorrectPreviouslyUnknownAdDuration_playingAdPeriodRemoved_doNothing() {
|
||||
long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
|
||||
long periodDurationUs = msToUs(PERIOD_DURATION_MS);
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
|
||||
// Window with content and ad periods: c, a, a, a, a, [c, a, a], a, a, c
|
||||
// Supposed insertion of ad for period with unknown duration. PLaying first ad.
|
||||
// durationsUs: [10_000_000L, 28_000_000L, 0L, 0L]
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 100_000_000L,
|
||||
/* adDurationUs= */ 10_000_000L,
|
||||
/* adPositionInAdPod= */ 1,
|
||||
/* totalAdDurationUs= */ 38_000_000L,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
adPlaybackState);
|
||||
adPlaybackState =
|
||||
adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
|
||||
// Playback advances to second ad. Insert second ad break. Playing on last period of window.
|
||||
// durationsUs: [10_000_000L, 123L, 27_999_877L, 0L]
|
||||
adPlaybackState =
|
||||
addLiveAdBreak(
|
||||
/* currentContentPeriodPositionUs= */ 110_000_000L,
|
||||
/* adDurationUs= */ 123L,
|
||||
/* adPositionInAdPod= */ 2,
|
||||
/* totalAdDurationUs= */ 38_000_000L,
|
||||
/* totalAdsInAdPod= */ 4,
|
||||
adPlaybackState);
|
||||
// Window advances to a state where the playing ad period has been removed:
|
||||
// c, a, a, a, a, c, a, a, [a, a, c]
|
||||
FakeMultiPeriodLiveTimeline contentTimeline =
|
||||
new FakeMultiPeriodLiveTimeline(
|
||||
/* availabilityStartTimeMs= */ 0,
|
||||
/* liveWindowDurationUs= */ 40_000_000L,
|
||||
/* nowUs= */ 169_234_567L,
|
||||
/* adSequencePattern= */ new boolean[] {false, true, true, true, true},
|
||||
/* periodDurationMsPattern= */ new long[] {
|
||||
periodDurationUs,
|
||||
adPeriodDurationUs,
|
||||
adPeriodDurationUs,
|
||||
adPeriodDurationUs,
|
||||
adPeriodDurationUs
|
||||
},
|
||||
/* isContentTimeline= */ true,
|
||||
/* populateAds= */ false,
|
||||
/* playedAds= */ false);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
.asList()
|
||||
.containsExactly(10_000_000L, 123L, 27_999_877L, 0L)
|
||||
.inOrder();
|
||||
|
||||
AdPlaybackState correctedAdPlaybackState =
|
||||
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(correctedAdPlaybackState).isSameInstanceAs(adPlaybackState);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maybeCorrectPreviouslyUnknownAdDuration_singleContentPeriodTimeline_doNothing() {
|
||||
FakeMultiPeriodLiveTimeline contentTimeline =
|
||||
@ -1327,7 +1663,7 @@ public class ImaUtilTest {
|
||||
/* adDurationsUs...= */ 123);
|
||||
|
||||
AdPlaybackState correctedAdPlaybackState =
|
||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
||||
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(correctedAdPlaybackState).isSameInstanceAs(adPlaybackState);
|
||||
}
|
||||
@ -1356,7 +1692,7 @@ public class ImaUtilTest {
|
||||
/* contentResumeOffsetUs= */ 123L,
|
||||
/* adDurationsUs...= */ 123L);
|
||||
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).timeUs).isEqualTo(80_000_000L);
|
||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||
|
Loading…
x
Reference in New Issue
Block a user