Setup VOD cue points to fill them when loaded

This makes sure the number of ads in an ad group matches to the number of periods representing an ad group in a multi-period timeline. This makes it easier to accurately mark ads as played in multi-period windows which is needed to correctly prevent seeking over unplayed ads.

PiperOrigin-RevId: 425317085
This commit is contained in:
bachinger 2022-01-31 10:32:10 +00:00 committed by Ian Baker
parent 3ec41e2b71
commit bdd64ce6a1
2 changed files with 312 additions and 9 deletions

View File

@ -15,13 +15,17 @@
*/
package com.google.android.exoplayer2.ext.ima;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Util.sum;
import static java.lang.Math.max;
import android.content.Context;
import android.os.Looper;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
import com.google.ads.interactivemedia.v3.api.AdError;
@ -263,6 +267,92 @@ import java.util.Set;
}
}
/**
* Expands a placeholder ad group with a single ad to the requested number of ads and sets the
* duration of the inserted ad.
*
* <p>The remaining ad group duration is propagated to the ad following the inserted ad. If the
* inserted ad is the last ad, the remaining ad group duration is wrapped around to the first ad
* in the group.
*
* @param adGroupIndex The ad group index of the ad group to expand.
* @param adIndexInAdGroup The ad index to set the duration.
* @param adDurationUs The duration of the ad.
* @param adGroupDurationUs The duration of the whole ad group.
* @param adsInAdGroupCount The number of ads of the ad group.
* @param adPlaybackState The ad playback state to modify.
* @return The updated ad playback state.
*/
@CheckResult
public static AdPlaybackState expandAdGroupPlaceholder(
int adGroupIndex,
long adGroupDurationUs,
int adIndexInAdGroup,
long adDurationUs,
int adsInAdGroupCount,
AdPlaybackState adPlaybackState) {
checkArgument(adIndexInAdGroup < adsInAdGroupCount);
long[] adDurationsUs =
updateAdDurationAndPropagate(
new long[adsInAdGroupCount], adIndexInAdGroup, adDurationUs, adGroupDurationUs);
return adPlaybackState
.withAdCount(adGroupIndex, adDurationsUs.length)
.withAdDurationsUs(adGroupIndex, adDurationsUs);
}
/**
* Updates the duration of an ad in and ad group.
*
* <p>The difference of the previous duration and the updated duration is propagated to the ad
* following the updated ad. If the updated ad is the last ad, the remaining duration is wrapped
* around to the first ad in the group.
*
* <p>The remaining ad duration is only propagated if the destination ad has a duration of 0.
*
* @param adGroupIndex The ad group index of the ad group to expand.
* @param adIndexInAdGroup The ad index to set the duration.
* @param adDurationUs The duration of the ad.
* @param adPlaybackState The ad playback state to modify.
* @return The updated ad playback state.
*/
@CheckResult
public static AdPlaybackState updateAdDurationInAdGroup(
int adGroupIndex, int adIndexInAdGroup, long adDurationUs, AdPlaybackState adPlaybackState) {
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
checkArgument(adIndexInAdGroup < adGroup.durationsUs.length);
long[] adDurationsUs =
updateAdDurationAndPropagate(
Arrays.copyOf(adGroup.durationsUs, adGroup.durationsUs.length),
adIndexInAdGroup,
adDurationUs,
adGroup.durationsUs[adIndexInAdGroup]);
return adPlaybackState.withAdDurationsUs(adGroupIndex, adDurationsUs);
}
/**
* Updates the duration of the given ad in the array and propagates the difference to the total
* duration to the next ad. If the updated ad is the last ad, the remaining duration is wrapped
* around to the first ad in the group.
*
* <p>The remaining ad duration is only propagated if the destination ad has a duration of 0.
*
* @param adDurationsUs The array to edit.
* @param adIndex The index of the ad in the durations array.
* @param adDurationUs The new ad duration.
* @param totalDurationUs The total duration the difference of which to propagate to the next ad.
* @return The updated input array, for convenience.
*/
/* package */ static long[] updateAdDurationAndPropagate(
long[] adDurationsUs, int adIndex, long adDurationUs, long totalDurationUs) {
adDurationsUs[adIndex] = adDurationUs;
int nextAdIndex = (adIndex + 1) % adDurationsUs.length;
if (adDurationsUs[nextAdIndex] == 0) {
// Propagate the remaining duration to the next ad.
adDurationsUs[nextAdIndex] = max(0, totalDurationUs - adDurationUs);
}
return adDurationsUs;
}
/**
* Splits an {@link AdPlaybackState} into a separate {@link AdPlaybackState} for each period of a
* content timeline. Ad group times are expected to not take previous ad duration into account and
@ -304,7 +394,7 @@ import java.util.Set;
}
// The ad group start timeUs is in content position. We need to add the ad
// duration before the ad group to translate the start time to the position in the period.
long adGroupDurationUs = getTotalDurationUs(adGroup.durationsUs);
long adGroupDurationUs = sum(adGroup.durationsUs);
long elapsedAdGroupAdDurationUs = 0;
for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) {
contentTimeline.getPeriod(j, period, /* setIds= */ true);
@ -375,13 +465,5 @@ import java.util.Set;
return adPlaybackState;
}
private static long getTotalDurationUs(long[] durationsUs) {
long totalDurationUs = 0;
for (long adDurationUs : durationsUs) {
totalDurationUs += adDurationUs;
}
return totalDurationUs;
}
private ImaUtil() {}
}

View File

@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.source.ads.ServerSideAdInsertionUtil;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
@ -503,4 +504,224 @@ public class ImaUtilTest {
assertThat(periodAdPlaybackState.adsId).isEqualTo("adsId");
}
}
@Test
public void expandAdGroupPlaceHolder_expandWithFirstAdInGroup_correctExpansion() {
AdPlaybackState adPlaybackState =
ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(
AdPlaybackState.NONE,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 0,
/* adDurationsUs...= */ 30_000_000);
adPlaybackState =
ImaUtil.expandAdGroupPlaceholder(
/* adGroupIndex= */ 0,
/* adGroupDurationUs= */ 30_000_000,
/* adIndexInAdGroup= */ 0,
/* adDurationUs= */ 10_000_000,
/* adsInAdGroupCount= */ 3,
adPlaybackState);
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(/* adGroupIndex= */ 0);
assertThat(adGroup.count).isEqualTo(3);
assertThat(adGroup.durationsUs[0]).isEqualTo(10_000_000);
assertThat(adGroup.durationsUs[1]).isEqualTo(20_000_000);
assertThat(adGroup.durationsUs[2]).isEqualTo(0);
}
@Test
public void expandAdGroupPlaceHolder_expandWithMiddleAdInGroup_correctExpansion() {
AdPlaybackState adPlaybackState =
ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(
AdPlaybackState.NONE,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 0,
/* adDurationsUs...= */ 30_000_000);
adPlaybackState =
ImaUtil.expandAdGroupPlaceholder(
/* adGroupIndex= */ 0,
/* adGroupDurationUs= */ 30_000_000,
/* adIndexInAdGroup= */ 1,
/* adDurationUs= */ 10_000_000,
/* adsInAdGroupCount= */ 3,
adPlaybackState);
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(/* adGroupIndex= */ 0);
assertThat(adGroup.count).isEqualTo(3);
assertThat(adGroup.durationsUs[0]).isEqualTo(0);
assertThat(adGroup.durationsUs[1]).isEqualTo(10_000_000);
assertThat(adGroup.durationsUs[2]).isEqualTo(20_000_000);
}
@Test
public void expandAdGroupPlaceHolder_expandWithLastAdInGroup_correctDurationWrappedAround() {
AdPlaybackState adPlaybackState =
ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(
AdPlaybackState.NONE,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 0,
/* adDurationsUs...= */ 30_000_000);
adPlaybackState =
ImaUtil.expandAdGroupPlaceholder(
/* adGroupIndex= */ 0,
/* adGroupDurationUs= */ 30_000_000,
/* adIndexInAdGroup= */ 2,
/* adDurationUs= */ 10_000_000,
/* adsInAdGroupCount= */ 3,
adPlaybackState);
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(/* adGroupIndex= */ 0);
assertThat(adGroup.count).isEqualTo(3);
assertThat(adGroup.durationsUs[0]).isEqualTo(20_000_000);
assertThat(adGroup.durationsUs[1]).isEqualTo(0);
assertThat(adGroup.durationsUs[2]).isEqualTo(10_000_000);
}
@Test
public void expandAdGroupPlaceHolder_expandSingleAdInAdGroup_noExpansionCorrectDuration() {
AdPlaybackState adPlaybackState =
ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(
AdPlaybackState.NONE,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 0,
/* adDurationsUs...= */ 30_000_000);
adPlaybackState =
ImaUtil.expandAdGroupPlaceholder(
/* adGroupIndex= */ 0,
/* adGroupDurationUs= */ 30_000_000,
/* adIndexInAdGroup= */ 0,
/* adDurationUs= */ 10_000_000,
/* adsInAdGroupCount= */ 1,
adPlaybackState);
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(/* adGroupIndex= */ 0);
assertThat(adGroup.count).isEqualTo(1);
assertThat(adGroup.durationsUs[0]).isEqualTo(10_000_000);
}
@Test
public void expandAdGroupPlaceHolder_singleAdInAdGroupOverLength_correctsAdDuration() {
AdPlaybackState adPlaybackState =
ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(
AdPlaybackState.NONE,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 0,
/* adDurationsUs...= */ 10_000_001);
adPlaybackState =
ImaUtil.expandAdGroupPlaceholder(
/* adGroupIndex= */ 0,
/* adGroupDurationUs= */ 10_000_000,
/* adIndexInAdGroup= */ 0,
/* adDurationUs= */ 10_000_000,
/* adsInAdGroupCount= */ 1,
adPlaybackState);
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(/* adGroupIndex= */ 0);
assertThat(adGroup.count).isEqualTo(1);
assertThat(adGroup.durationsUs[0]).isEqualTo(10_000_000);
}
@Test
public void expandAdGroupPlaceHolder_initialDurationTooLarge_overriddenWhenExpanded() {
AdPlaybackState adPlaybackState =
ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(
AdPlaybackState.NONE,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 0,
/* adDurationsUs...= */ 30_000_000);
adPlaybackState =
ImaUtil.expandAdGroupPlaceholder(
/* adGroupIndex= */ 0,
/* adGroupDurationUs= */ 20_000_000,
/* adIndexInAdGroup= */ 1,
/* adDurationUs= */ 10_000_000,
/* adsInAdGroupCount= */ 2,
adPlaybackState);
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(/* adGroupIndex= */ 0);
assertThat(adGroup.count).isEqualTo(2);
assertThat(adGroup.durationsUs[0]).isEqualTo(10_000_000);
assertThat(adGroup.durationsUs[1]).isEqualTo(10_000_000);
}
@Test
public void insertAdDurationInAdGroup_correctDurationAndPropagation() {
AdPlaybackState adPlaybackState =
ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(
AdPlaybackState.NONE,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 0,
/* adDurationsUs...= */ 10_000_000,
20_000_000,
0);
adPlaybackState =
ImaUtil.updateAdDurationInAdGroup(
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 1,
/* adDurationUs= */ 15_000_000,
adPlaybackState);
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(0);
assertThat(adGroup.count).isEqualTo(3);
assertThat(adGroup.durationsUs[0]).isEqualTo(10_000_000);
assertThat(adGroup.durationsUs[1]).isEqualTo(15_000_000);
assertThat(adGroup.durationsUs[2]).isEqualTo(5_000_000);
}
@Test
public void insertAdDurationInAdGroup_insertLast_correctDurationAndPropagation() {
AdPlaybackState adPlaybackState =
ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(
AdPlaybackState.NONE,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 0,
/* adDurationsUs...= */ 0,
10_000_000,
20_000_000);
adPlaybackState =
ImaUtil.updateAdDurationInAdGroup(
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 2,
/* adDurationUs= */ 15_000_000,
adPlaybackState);
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(0);
assertThat(adGroup.count).isEqualTo(3);
assertThat(adGroup.durationsUs[0]).isEqualTo(5_000_000);
assertThat(adGroup.durationsUs[1]).isEqualTo(10_000_000);
assertThat(adGroup.durationsUs[2]).isEqualTo(15_000_000);
}
@Test
public void insertAdDurationInAdGroup_allDurationsSetAlready_setDurationNoPropagation() {
AdPlaybackState adPlaybackState =
ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(
AdPlaybackState.NONE,
/* fromPositionUs= */ 0,
/* contentResumeOffsetUs= */ 0,
/* adDurationsUs...= */ 5_000_000,
10_000_000,
20_000_000);
adPlaybackState =
ImaUtil.updateAdDurationInAdGroup(
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 1,
/* adDurationUs= */ 5_000_000,
adPlaybackState);
AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(0);
assertThat(adGroup.count).isEqualTo(3);
assertThat(adGroup.durationsUs[0]).isEqualTo(5_000_000);
assertThat(adGroup.durationsUs[1]).isEqualTo(5_000_000);
assertThat(adGroup.durationsUs[2]).isEqualTo(20_000_000);
}
}