Commit 378b3f6e by tonihei Committed by Oliver Woodman

Add Util class for server-side inserted ads.

When working with SSAI ads, we need to easily convert positions between
the underlying stream and the media model that exposes ad groups. To
simplify this, we can add util methods (that are testable on their own).

In addition, we need an easy way to build AdPlaybackStates for SSAI
inserted ads. The metadata is obtained out-of-band and usually has the
ad group start and end times in the underlying stream only. Hence, we
also add a util method to add a new ad group based on this information.

PiperOrigin-RevId: 374369360
parent c7db9fb3
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.ads; package com.google.android.exoplayer2.source.ads;
import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.common.collect.ObjectArrays.concat;
import static java.lang.Math.max; import static java.lang.Math.max;
import android.net.Uri; import android.net.Uri;
...@@ -508,25 +507,48 @@ public final class AdPlaybackState implements Bundleable { ...@@ -508,25 +507,48 @@ public final class AdPlaybackState implements Bundleable {
} }
/** /**
* Returns an instance with the specified ad group times. * Returns an instance with the specified ad group time.
* *
* <p>If the number of ad groups differs, ad groups are either removed or empty ad groups are * @param adGroupIndex The index of the ad group.
* added. * @param adGroupTimeUs The new ad group time, in microseconds, or {@link C#TIME_END_OF_SOURCE} to
* indicate a postroll ad.
* @return The updated ad playback state.
*/
@CheckResult
public AdPlaybackState withAdGroupTimeUs(int adGroupIndex, long adGroupTimeUs) {
long[] adGroupTimesUs = Arrays.copyOf(this.adGroupTimesUs, this.adGroupCount);
adGroupTimesUs[adGroupIndex] = adGroupTimeUs;
return new AdPlaybackState(
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
}
/**
* Returns an instance with a new ad group.
* *
* @param adGroupTimesUs The new ad group times, in microseconds. * @param adGroupIndex The insertion index of the new group.
* @param adGroupTimeUs The ad group time, in microseconds, or {@link C#TIME_END_OF_SOURCE} to
* indicate a postroll ad.
* @return The updated ad playback state. * @return The updated ad playback state.
*/ */
@CheckResult @CheckResult
public AdPlaybackState withAdGroupTimesUs(long... adGroupTimesUs) { public AdPlaybackState withNewAdGroup(int adGroupIndex, long adGroupTimeUs) {
AdGroup[] adGroups = AdGroup newAdGroup = new AdGroup();
adGroupTimesUs.length < adGroupCount AdGroup[] adGroups = Util.nullSafeArrayAppend(this.adGroups, newAdGroup);
? Util.nullSafeArrayCopy(this.adGroups, adGroupTimesUs.length) System.arraycopy(
: adGroupTimesUs.length == adGroupCount /* src= */ adGroups,
? this.adGroups /* srcPos= */ adGroupIndex,
: concat( /* dest= */ adGroups,
this.adGroups, /* destPos= */ adGroupIndex + 1,
createEmptyAdGroups(adGroupTimesUs.length - adGroupCount), /* length= */ adGroupCount - adGroupIndex);
AdGroup.class); adGroups[adGroupIndex] = newAdGroup;
long[] adGroupTimesUs = Arrays.copyOf(this.adGroupTimesUs, adGroupCount + 1);
System.arraycopy(
/* src= */ adGroupTimesUs,
/* srcPos= */ adGroupIndex,
/* dest= */ adGroupTimesUs,
/* destPos= */ adGroupIndex + 1,
/* length= */ adGroupCount - adGroupIndex);
adGroupTimesUs[adGroupIndex] = adGroupTimeUs;
return new AdPlaybackState( return new AdPlaybackState(
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs); adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
} }
...@@ -607,6 +629,18 @@ public final class AdPlaybackState implements Bundleable { ...@@ -607,6 +629,18 @@ public final class AdPlaybackState implements Bundleable {
} }
/** /**
* Returns an instance with the specified ad durations, in microseconds, in the specified ad
* group.
*/
@CheckResult
public AdPlaybackState withAdDurationsUs(int adGroupIndex, long... adDurationsUs) {
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdDurationsUs(adDurationsUs);
return new AdPlaybackState(
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
}
/**
* Returns an instance with the specified ad resume position, in microseconds, relative to the * Returns an instance with the specified ad resume position, in microseconds, relative to the
* start of the current ad. * start of the current ad.
*/ */
......
...@@ -73,35 +73,58 @@ public class AdPlaybackStateTest { ...@@ -73,35 +73,58 @@ public class AdPlaybackStateTest {
} }
@Test @Test
public void withAdGroupTimesUs_removingGroups_keepsRemainingGroups() { public void withAdGroupTimeUs_updatesAdGroupTimeUs() {
AdPlaybackState state = AdPlaybackState state = new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0, 10_000);
new AdPlaybackState(TEST_ADS_ID, new long[] {0, C.msToUs(10_000)})
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI);
state = state.withAdGroupTimesUs(new long[] {C.msToUs(3_000)}); state =
state
.withAdGroupTimeUs(/* adGroupIndex= */ 0, 3_000)
.withAdGroupTimeUs(/* adGroupIndex= */ 1, 6_000);
assertThat(state.adGroupCount).isEqualTo(1); assertThat(state.adGroupCount).isEqualTo(2);
assertThat(state.adGroups[0].count).isEqualTo(2); assertThat(state.adGroupTimesUs[0]).isEqualTo(3_000);
assertThat(state.adGroups[0].uris[1]).isSameInstanceAs(TEST_URI); assertThat(state.adGroupTimesUs[1]).isEqualTo(6_000);
} }
@Test @Test
public void withAdGroupTimesUs_addingGroups_keepsExistingGroups() { public void withNewAdGroup_addsGroupAndKeepsExistingGroups() {
AdPlaybackState state = AdPlaybackState state =
new AdPlaybackState(TEST_ADS_ID, new long[] {0, C.msToUs(10_000)}) new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 3_000, 6_000)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2) .withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1) .withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI) .withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 1, TEST_URI)
.withSkippedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0); .withSkippedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0);
state = state.withAdGroupTimesUs(new long[] {0, C.msToUs(3_000), C.msToUs(20_000)}); state =
state
assertThat(state.adGroupCount).isEqualTo(3); .withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ 1_000)
assertThat(state.adGroups[0].count).isEqualTo(2); .withNewAdGroup(/* adGroupIndex= */ 2, /* adGroupTimeUs= */ 5_000)
assertThat(state.adGroups[0].uris[1]).isSameInstanceAs(TEST_URI); .withNewAdGroup(/* adGroupIndex= */ 4, /* adGroupTimeUs= */ 8_000);
assertThat(state.adGroups[1].states[0]).isEqualTo(AdPlaybackState.AD_STATE_SKIPPED);
assertThat(state.adGroupCount).isEqualTo(5);
assertThat(state.adGroups[0].count).isEqualTo(C.INDEX_UNSET);
assertThat(state.adGroups[1].count).isEqualTo(2);
assertThat(state.adGroups[1].uris[1]).isSameInstanceAs(TEST_URI);
assertThat(state.adGroups[2].count).isEqualTo(C.INDEX_UNSET); assertThat(state.adGroups[2].count).isEqualTo(C.INDEX_UNSET);
assertThat(state.adGroups[3].count).isEqualTo(1);
assertThat(state.adGroups[3].states[0]).isEqualTo(AdPlaybackState.AD_STATE_SKIPPED);
assertThat(state.adGroups[4].count).isEqualTo(C.INDEX_UNSET);
}
@Test
public void withAdDurationsUs_updatesAdDurations() {
AdPlaybackState state =
new AdPlaybackState(TEST_ADS_ID, /* adGroupTimesUs...= */ 0, 10_000)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 2)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 2)
.withAdDurationsUs(new long[][] {new long[] {5_000, 6_000}, new long[] {7_000, 8_000}});
state = state.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ 1_000, 2_000);
assertThat(state.adGroups[0].durationsUs[0]).isEqualTo(5_000);
assertThat(state.adGroups[0].durationsUs[1]).isEqualTo(6_000);
assertThat(state.adGroups[1].durationsUs[0]).isEqualTo(1_000);
assertThat(state.adGroups[1].durationsUs[1]).isEqualTo(2_000);
} }
@Test @Test
......
...@@ -10479,8 +10479,9 @@ public final class ExoPlayerTest { ...@@ -10479,8 +10479,9 @@ public final class ExoPlayerTest {
/* durationUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET,
initialAdPlaybackState)); initialAdPlaybackState));
AdPlaybackState updatedAdPlaybackState = AdPlaybackState updatedAdPlaybackState =
initialAdPlaybackState.withAdGroupTimesUs( initialAdPlaybackState.withAdGroupTimeUs(
/* adGroupTimesUs...= */ firstSampleTimeUs + 18 * C.MICROS_PER_SECOND); /* adGroupIndex= */ 0,
/* adGroupTimeUs= */ firstSampleTimeUs + 18 * C.MICROS_PER_SECOND);
Timeline updatedTimeline = Timeline updatedTimeline =
new FakeTimeline( new FakeTimeline(
new TimelineWindowDefinition( new TimelineWindowDefinition(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment