mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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.getAdGroupDurationUsForLiveAdPeriodIndex;
|
||||||
import static androidx.media3.exoplayer.ima.ImaUtil.getWindowStartTimeUs;
|
import static androidx.media3.exoplayer.ima.ImaUtil.getWindowStartTimeUs;
|
||||||
import static androidx.media3.exoplayer.ima.ImaUtil.handleAdPeriodRemovedFromTimeline;
|
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.secToMsRounded;
|
||||||
import static androidx.media3.exoplayer.ima.ImaUtil.secToUsRounded;
|
import static androidx.media3.exoplayer.ima.ImaUtil.secToUsRounded;
|
||||||
import static androidx.media3.exoplayer.ima.ImaUtil.splitAdGroup;
|
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
|
// 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
|
// duration, the ad duration is estimated and needs to be corrected when the actual duration
|
||||||
// is reported.
|
// is reported.
|
||||||
adPlaybackState = maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
adPlaybackState = maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||||
}
|
}
|
||||||
this.contentTimeline = contentTimeline;
|
this.contentTimeline = contentTimeline;
|
||||||
invalidateServerSideAdInsertionAdPlaybackState();
|
invalidateServerSideAdInsertionAdPlaybackState();
|
||||||
|
@ -46,6 +46,7 @@ import androidx.media3.common.util.Util;
|
|||||||
import androidx.media3.datasource.DataSchemeDataSource;
|
import androidx.media3.datasource.DataSchemeDataSource;
|
||||||
import androidx.media3.datasource.DataSourceUtil;
|
import androidx.media3.datasource.DataSourceUtil;
|
||||||
import androidx.media3.datasource.DataSpec;
|
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.AdDisplayContainer;
|
||||||
import com.google.ads.interactivemedia.v3.api.AdError;
|
import com.google.ads.interactivemedia.v3.api.AdError;
|
||||||
import com.google.ads.interactivemedia.v3.api.AdErrorEvent;
|
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
|
* <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
|
* {@linkplain #addLiveAdBreak(long, long, int, long, int, AdPlaybackState) a live ad has been
|
||||||
* ad duration was used which can be corrected as soon as the {@code contentTimeline} was
|
* inserted} while the duration of the corresponding period was still unknown. In this case the
|
||||||
* refreshed with the actual period duration.
|
* {@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
|
* <p>Roughly, the logic checks whether an ad group of the ad playback state fits in or overlaps
|
||||||
* at the period start time of the last period that has a known duration. If found, the ad
|
* one or several periods in the content timeline. Starting at the first ad inside the window, the
|
||||||
* duration is set to the period duration and the new ad playback state is returned. If not found
|
* ad duration is set to the duration of the corresponding period until a period with an unknown
|
||||||
* or the duration is already correct the ad playback state remains unchanged.
|
* 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 contentTimeline The live content timeline.
|
||||||
* @param adPlaybackState The ad playback state.
|
* @param adPlaybackState The ad playback state.
|
||||||
* @return The (potentially) updated ad playback state.
|
* @return The (potentially) updated ad playback state.
|
||||||
*/
|
*/
|
||||||
public static AdPlaybackState maybeCorrectPreviouslyUnknownAdDuration(
|
public static AdPlaybackState maybeCorrectPreviouslyUnknownAdDurations(
|
||||||
Timeline contentTimeline, AdPlaybackState adPlaybackState) {
|
Timeline contentTimeline, AdPlaybackState adPlaybackState) {
|
||||||
Timeline.Window window = contentTimeline.getWindow(/* windowIndex= */ 0, new Timeline.Window());
|
Timeline.Window window = contentTimeline.getWindow(/* windowIndex= */ 0, new Timeline.Window());
|
||||||
if (window.firstPeriodIndex == window.lastPeriodIndex || adPlaybackState.adGroupCount < 2) {
|
if (window.firstPeriodIndex == window.lastPeriodIndex || adPlaybackState.adGroupCount < 2) {
|
||||||
@ -561,51 +569,68 @@ import java.util.Set;
|
|||||||
return adPlaybackState;
|
return adPlaybackState;
|
||||||
}
|
}
|
||||||
Timeline.Period period = new Timeline.Period();
|
Timeline.Period period = new Timeline.Period();
|
||||||
// Get the first period from the end with a known duration.
|
int lastPeriodIndex = window.lastPeriodIndex;
|
||||||
int periodIndex = window.lastPeriodIndex;
|
if (contentTimeline.getPeriod(lastPeriodIndex, period).durationUs == C.TIME_UNSET) {
|
||||||
while (periodIndex >= window.firstPeriodIndex
|
lastPeriodIndex--;
|
||||||
&& contentTimeline.getPeriod(periodIndex, period).durationUs == C.TIME_UNSET) {
|
contentTimeline.getPeriod(lastPeriodIndex, period);
|
||||||
periodIndex--;
|
|
||||||
}
|
}
|
||||||
// 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 =
|
long windowStartTimeUs =
|
||||||
getWindowStartTimeUs(window.windowStartTimeMs, window.positionInFirstPeriodUs);
|
getWindowStartTimeUs(window.windowStartTimeMs, window.positionInFirstPeriodUs);
|
||||||
long periodStartTimeUs = windowStartTimeUs + period.positionInWindowUs;
|
long lastCompletePeriodStartTimeUs = windowStartTimeUs + period.positionInWindowUs;
|
||||||
int adGroupIndex =
|
int adGroupIndex =
|
||||||
adPlaybackState.getAdGroupIndexForPositionUs(
|
adPlaybackState.getAdGroupIndexForPositionUs(
|
||||||
periodStartTimeUs, /* periodDurationUs= */ C.TIME_UNSET);
|
lastCompletePeriodStartTimeUs, /* periodDurationUs= */ C.TIME_UNSET);
|
||||||
if (adGroupIndex == C.INDEX_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;
|
return adPlaybackState;
|
||||||
}
|
}
|
||||||
|
|
||||||
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
|
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;
|
return adPlaybackState;
|
||||||
}
|
}
|
||||||
// Period is inside the ad group. Get ad start that matches the period start.
|
// The ads at the start of the ad group may be out of the window already. Skip them.
|
||||||
long adGroupDurationUs = 0;
|
int firstAdIndexInWindow = 0;
|
||||||
for (int adIndex = 0; adIndex < adGroup.durationsUs.length; adIndex++) {
|
long adStartTimeUs = adGroup.timeUs;
|
||||||
long adDurationUs = adGroup.durationsUs[adIndex];
|
while (adStartTimeUs < periodStartTimeUs) {
|
||||||
if (adGroup.timeUs + adGroupDurationUs < periodStartTimeUs) {
|
if (adGroup.states[firstAdIndexInWindow] == AD_STATE_AVAILABLE) {
|
||||||
adGroupDurationUs += adDurationUs;
|
// The previously available ad is not in the timeline anymore. Discontinuity of reason
|
||||||
continue;
|
// `DISCONTINUITY_REASON_REMOVE`.
|
||||||
}
|
|
||||||
if (period.durationUs == adDurationUs) {
|
|
||||||
// No update required.
|
|
||||||
return adPlaybackState;
|
return adPlaybackState;
|
||||||
}
|
}
|
||||||
// Set the ad duration to the period duration.
|
// Skip ad before first period of window.
|
||||||
adPlaybackState =
|
adStartTimeUs += adGroup.durationsUs[firstAdIndexInWindow++];
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
// Return unchanged.
|
int firstPeriodIndexInAdGroup = C.INDEX_UNSET;
|
||||||
return adPlaybackState;
|
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.getAdGroupAndIndexInLiveMultiPeriodTimeline;
|
||||||
import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInVodMultiPeriodTimeline;
|
import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInVodMultiPeriodTimeline;
|
||||||
import static androidx.media3.exoplayer.ima.ImaUtil.handleAdPeriodRemovedFromTimeline;
|
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.secToUsRounded;
|
||||||
import static androidx.media3.exoplayer.ima.ImaUtil.splitAdGroup;
|
import static androidx.media3.exoplayer.ima.ImaUtil.splitAdGroup;
|
||||||
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState;
|
import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState;
|
||||||
@ -1074,7 +1074,7 @@ public class ImaUtilTest {
|
|||||||
/* populateAds= */ false,
|
/* populateAds= */ false,
|
||||||
/* playedAds= */ 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).timeUs).isEqualTo(80_000_000L);
|
||||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||||
@ -1116,7 +1116,7 @@ public class ImaUtilTest {
|
|||||||
/* populateAds= */ false,
|
/* populateAds= */ false,
|
||||||
/* playedAds= */ 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).timeUs).isEqualTo(80_000_000L);
|
||||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||||
@ -1135,14 +1135,8 @@ public class ImaUtilTest {
|
|||||||
addAdGroupToAdPlaybackState(
|
addAdGroupToAdPlaybackState(
|
||||||
adPlaybackState,
|
adPlaybackState,
|
||||||
/* fromPositionUs= */ 80_000_000L,
|
/* fromPositionUs= */ 80_000_000L,
|
||||||
/* contentResumeOffsetUs= */ 123,
|
/* contentResumeOffsetUs= */ 123L,
|
||||||
/* adDurationsUs...= */ 123);
|
/* adDurationsUs...= */ 123L);
|
||||||
adPlaybackState =
|
|
||||||
addAdGroupToAdPlaybackState(
|
|
||||||
adPlaybackState,
|
|
||||||
/* fromPositionUs= */ 90_000_000L,
|
|
||||||
/* contentResumeOffsetUs= */ 123,
|
|
||||||
/* adDurationsUs...= */ 123);
|
|
||||||
FakeMultiPeriodLiveTimeline contentTimeline =
|
FakeMultiPeriodLiveTimeline contentTimeline =
|
||||||
new FakeMultiPeriodLiveTimeline(
|
new FakeMultiPeriodLiveTimeline(
|
||||||
/* availabilityStartTimeMs= */ 0,
|
/* availabilityStartTimeMs= */ 0,
|
||||||
@ -1157,7 +1151,7 @@ public class ImaUtilTest {
|
|||||||
/* playedAds= */ false);
|
/* playedAds= */ false);
|
||||||
|
|
||||||
AdPlaybackState correctedAdPlaybackState =
|
AdPlaybackState correctedAdPlaybackState =
|
||||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||||
|
|
||||||
assertThat(contentTimeline.getWindow(/* windowIndex= */ 0, new Window()).windowStartTimeMs)
|
assertThat(contentTimeline.getWindow(/* windowIndex= */ 0, new Window()).windowStartTimeMs)
|
||||||
.isEqualTo(100_000L);
|
.isEqualTo(100_000L);
|
||||||
@ -1186,117 +1180,459 @@ public class ImaUtilTest {
|
|||||||
/* playedAds= */ false);
|
/* playedAds= */ false);
|
||||||
AdPlaybackState adPlaybackState =
|
AdPlaybackState adPlaybackState =
|
||||||
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
|
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 =
|
adPlaybackState =
|
||||||
addLiveAdBreak(
|
addLiveAdBreak(
|
||||||
/* currentContentPeriodPositionUs= */ 30_000_000,
|
/* currentContentPeriodPositionUs= */ 30_000_000,
|
||||||
/* adDurationUs= */ 10_000_000,
|
/* adDurationUs= */ 10_000_000,
|
||||||
/* adPositionInAdPod= */ 1,
|
/* adPositionInAdPod= */ 1,
|
||||||
/* totalAdDurationUs= */ 40_000_123,
|
/* totalAdDurationUs= */ 39_000_123,
|
||||||
/* totalAdsInAdPod= */ 4,
|
/* totalAdsInAdPod= */ 4,
|
||||||
adPlaybackState);
|
adPlaybackState);
|
||||||
|
|
||||||
AdPlaybackState correctedAdPlaybackState =
|
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).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);
|
contentTimeline.advanceNowUs(1L);
|
||||||
correctedAdPlaybackState =
|
correctedAdPlaybackState =
|
||||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, correctedAdPlaybackState);
|
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, correctedAdPlaybackState);
|
||||||
|
|
||||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||||
.asList()
|
.asList()
|
||||||
.containsExactly(10_000_000L, 10_000_000L, 20_000_123L, 0L)
|
.containsExactly(10_000_000L, 10_000_000L, 19_000_123L, 0L)
|
||||||
.inOrder();
|
.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);
|
contentTimeline.advanceNowUs(10_000_000L);
|
||||||
correctedAdPlaybackState =
|
correctedAdPlaybackState =
|
||||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, correctedAdPlaybackState);
|
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, correctedAdPlaybackState);
|
||||||
|
|
||||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||||
.asList()
|
.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();
|
.inOrder();
|
||||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
||||||
.asList()
|
.asList()
|
||||||
.containsExactly(1, 0, 0, 0)
|
.containsExactly(1, 1, 0, 0)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
|
|
||||||
// It doesn't matter whether the live break event or the correction propagates the remainder
|
// The event of the previously corrected ad sets the same duration and marks the ad available.
|
||||||
// forward. Updating the ad by ad event later only marks the ad as available.
|
|
||||||
correctedAdPlaybackState =
|
correctedAdPlaybackState =
|
||||||
addLiveAdBreak(
|
addLiveAdBreak(
|
||||||
/* currentContentPeriodPositionUs= */ 40_000_000,
|
/* currentContentPeriodPositionUs= */ 50_000_000L,
|
||||||
/* adDurationUs= */ 10_000_000,
|
/* adDurationUs= */ 10_000_000L,
|
||||||
/* 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,
|
|
||||||
/* adPositionInAdPod= */ 3,
|
/* adPositionInAdPod= */ 3,
|
||||||
/* totalAdDurationUs= */ 40_000_000,
|
/* totalAdDurationUs= */ 39_000_123L,
|
||||||
/* totalAdsInAdPod= */ 4,
|
/* totalAdsInAdPod= */ 4,
|
||||||
correctedAdPlaybackState);
|
correctedAdPlaybackState);
|
||||||
|
|
||||||
// No change in durations.
|
// No change in durations.
|
||||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||||
.asList()
|
.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();
|
.inOrder();
|
||||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
||||||
.asList()
|
.asList()
|
||||||
.containsExactly(1, 1, 1, 0);
|
.containsExactly(1, 1, 1, 0);
|
||||||
|
|
||||||
|
// The last ad is inserted with ad pod duration 123 as fallback of the missing duration.
|
||||||
correctedAdPlaybackState =
|
correctedAdPlaybackState =
|
||||||
addLiveAdBreak(
|
addLiveAdBreak(
|
||||||
/* currentContentPeriodPositionUs= */ 40_000_000,
|
/* currentContentPeriodPositionUs= */ 40_000_000L,
|
||||||
/* adDurationUs= */ 9_999_999L,
|
/* adDurationUs= */ 123L,
|
||||||
/* adPositionInAdPod= */ 3,
|
/* adPositionInAdPod= */ 4,
|
||||||
/* totalAdDurationUs= */ 40_000_000,
|
/* totalAdDurationUs= */ 40_000_000L,
|
||||||
/* totalAdsInAdPod= */ 4,
|
/* totalAdsInAdPod= */ 4,
|
||||||
correctedAdPlaybackState);
|
correctedAdPlaybackState);
|
||||||
|
|
||||||
// Last duration updated with ad pod duration.
|
// Last duration updated with ad pod duration.
|
||||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||||
.asList()
|
.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();
|
.inOrder();
|
||||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states)
|
||||||
.asList()
|
.asList()
|
||||||
.containsExactly(1, 1, 1, 1);
|
.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);
|
contentTimeline.advanceNowUs(10_000_000L);
|
||||||
correctedAdPlaybackState =
|
correctedAdPlaybackState =
|
||||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, correctedAdPlaybackState);
|
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, correctedAdPlaybackState);
|
||||||
|
|
||||||
// Last duration corrected when period arrives.
|
|
||||||
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
assertThat(correctedAdPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||||
.asList()
|
.asList()
|
||||||
.containsExactly(10_000_000L, 10_000_000L, 10_000_000L, 10_000_000L);
|
.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
|
@Test
|
||||||
public void maybeCorrectPreviouslyUnknownAdDuration_singleContentPeriodTimeline_doNothing() {
|
public void maybeCorrectPreviouslyUnknownAdDuration_singleContentPeriodTimeline_doNothing() {
|
||||||
FakeMultiPeriodLiveTimeline contentTimeline =
|
FakeMultiPeriodLiveTimeline contentTimeline =
|
||||||
@ -1327,7 +1663,7 @@ public class ImaUtilTest {
|
|||||||
/* adDurationsUs...= */ 123);
|
/* adDurationsUs...= */ 123);
|
||||||
|
|
||||||
AdPlaybackState correctedAdPlaybackState =
|
AdPlaybackState correctedAdPlaybackState =
|
||||||
maybeCorrectPreviouslyUnknownAdDuration(contentTimeline, adPlaybackState);
|
maybeCorrectPreviouslyUnknownAdDurations(contentTimeline, adPlaybackState);
|
||||||
|
|
||||||
assertThat(correctedAdPlaybackState).isSameInstanceAs(adPlaybackState);
|
assertThat(correctedAdPlaybackState).isSameInstanceAs(adPlaybackState);
|
||||||
}
|
}
|
||||||
@ -1356,7 +1692,7 @@ public class ImaUtilTest {
|
|||||||
/* contentResumeOffsetUs= */ 123L,
|
/* contentResumeOffsetUs= */ 123L,
|
||||||
/* adDurationsUs...= */ 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).timeUs).isEqualTo(80_000_000L);
|
||||||
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user