Allow ad groups to be marked as server-side inserted.

This helps both player the logic and clients like UI or analytics to
detect SSAI ads.

PiperOrigin-RevId: 373540754
This commit is contained in:
tonihei 2021-05-13 10:27:26 +01:00 committed by Oliver Woodman
parent bec7b0041e
commit 954a6730d5
3 changed files with 67 additions and 12 deletions

View file

@ -804,6 +804,17 @@ public abstract class Timeline implements Bundleable {
return adPlaybackState.adResumePositionUs;
}
/**
* Returns whether the ad group at index {@code adGroupIndex} is server-side inserted and part
* of the content stream.
*
* @param adGroupIndex The ad group index.
* @return Whether this ad group is server-side inserted and part of the content stream.
*/
public boolean isServerSideInsertedAdGroup(int adGroupIndex) {
return adPlaybackState.adGroups[adGroupIndex].isServerSideInserted;
}
/**
* Returns the offset in microseconds which should be added to the content stream when resuming
* playback after the specified ad group.

View file

@ -63,6 +63,8 @@ public final class AdPlaybackState implements Bundleable {
* after the ad group.
*/
public final long contentResumeOffsetUs;
/** Whether this ad group is server-side inserted and part of the content stream. */
public final boolean isServerSideInserted;
/** Creates a new ad group with an unspecified number of ads. */
public AdGroup() {
@ -71,7 +73,8 @@ public final class AdPlaybackState implements Bundleable {
/* states= */ new int[0],
/* uris= */ new Uri[0],
/* durationsUs= */ new long[0],
/* contentResumeOffsetUs= */ 0);
/* contentResumeOffsetUs= */ 0,
/* isServerSideInserted= */ false);
}
private AdGroup(
@ -79,13 +82,15 @@ public final class AdPlaybackState implements Bundleable {
@AdState int[] states,
@NullableType Uri[] uris,
long[] durationsUs,
long contentResumeOffsetUs) {
long contentResumeOffsetUs,
boolean isServerSideInserted) {
checkArgument(states.length == uris.length);
this.count = count;
this.states = states;
this.uris = uris;
this.durationsUs = durationsUs;
this.contentResumeOffsetUs = contentResumeOffsetUs;
this.isServerSideInserted = isServerSideInserted;
}
/**
@ -130,7 +135,8 @@ public final class AdPlaybackState implements Bundleable {
&& Arrays.equals(uris, adGroup.uris)
&& Arrays.equals(states, adGroup.states)
&& Arrays.equals(durationsUs, adGroup.durationsUs)
&& contentResumeOffsetUs == adGroup.contentResumeOffsetUs;
&& contentResumeOffsetUs == adGroup.contentResumeOffsetUs
&& isServerSideInserted == adGroup.isServerSideInserted;
}
@Override
@ -140,6 +146,7 @@ public final class AdPlaybackState implements Bundleable {
result = 31 * result + Arrays.hashCode(states);
result = 31 * result + Arrays.hashCode(durationsUs);
result = 31 * result + (int) (contentResumeOffsetUs ^ (contentResumeOffsetUs >>> 32));
result = 31 * result + (isServerSideInserted ? 1 : 0);
return result;
}
@ -149,7 +156,8 @@ public final class AdPlaybackState implements Bundleable {
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, count);
long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count);
@NullableType Uri[] uris = Arrays.copyOf(this.uris, count);
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
return new AdGroup(
count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted);
}
/**
@ -166,7 +174,8 @@ public final class AdPlaybackState implements Bundleable {
@NullableType Uri[] uris = Arrays.copyOf(this.uris, states.length);
uris[index] = uri;
states[index] = AD_STATE_AVAILABLE;
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
return new AdGroup(
count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted);
}
/**
@ -193,7 +202,8 @@ public final class AdPlaybackState implements Bundleable {
Uri[] uris =
this.uris.length == states.length ? this.uris : Arrays.copyOf(this.uris, states.length);
states[index] = state;
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
return new AdGroup(
count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted);
}
/** Returns a new instance with the specified ad durations, in microseconds. */
@ -204,13 +214,22 @@ public final class AdPlaybackState implements Bundleable {
} else if (count != C.LENGTH_UNSET && durationsUs.length > uris.length) {
durationsUs = Arrays.copyOf(durationsUs, uris.length);
}
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
return new AdGroup(
count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted);
}
/** Returns an instance with the specified {@link #contentResumeOffsetUs}. */
@CheckResult
public AdGroup withContentResumeOffsetUs(long contentResumeOffsetUs) {
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
return new AdGroup(
count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted);
}
/** Returns an instance with the specified value for {@link #isServerSideInserted}. */
@CheckResult
public AdGroup withIsServerSideInserted(boolean isServerSideInserted) {
return new AdGroup(
count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted);
}
/**
@ -225,7 +244,8 @@ public final class AdPlaybackState implements Bundleable {
/* states= */ new int[0],
/* uris= */ new Uri[0],
/* durationsUs= */ new long[0],
contentResumeOffsetUs);
contentResumeOffsetUs,
isServerSideInserted);
}
int count = this.states.length;
@AdState int[] states = Arrays.copyOf(this.states, count);
@ -234,7 +254,8 @@ public final class AdPlaybackState implements Bundleable {
states[i] = AD_STATE_SKIPPED;
}
}
return new AdGroup(count, states, uris, durationsUs, contentResumeOffsetUs);
return new AdGroup(
count, states, uris, durationsUs, contentResumeOffsetUs, isServerSideInserted);
}
@CheckResult
@ -265,6 +286,7 @@ public final class AdPlaybackState implements Bundleable {
FIELD_STATES,
FIELD_DURATIONS_US,
FIELD_CONTENT_RESUME_OFFSET_US,
FIELD_IS_SERVER_SIDE_INSERTED,
})
private @interface FieldNumber {}
@ -273,6 +295,7 @@ public final class AdPlaybackState implements Bundleable {
private static final int FIELD_STATES = 2;
private static final int FIELD_DURATIONS_US = 3;
private static final int FIELD_CONTENT_RESUME_OFFSET_US = 4;
private static final int FIELD_IS_SERVER_SIDE_INSERTED = 5;
// putParcelableArrayList actually supports null elements.
@SuppressWarnings("nullness:argument.type.incompatible")
@ -285,6 +308,7 @@ public final class AdPlaybackState implements Bundleable {
bundle.putIntArray(keyForField(FIELD_STATES), states);
bundle.putLongArray(keyForField(FIELD_DURATIONS_US), durationsUs);
bundle.putLong(keyForField(FIELD_CONTENT_RESUME_OFFSET_US), contentResumeOffsetUs);
bundle.putBoolean(keyForField(FIELD_IS_SERVER_SIDE_INSERTED), isServerSideInserted);
return bundle;
}
@ -302,12 +326,14 @@ public final class AdPlaybackState implements Bundleable {
int[] states = bundle.getIntArray(keyForField(FIELD_STATES));
@Nullable long[] durationsUs = bundle.getLongArray(keyForField(FIELD_DURATIONS_US));
long contentResumeOffsetUs = bundle.getLong(keyForField(FIELD_CONTENT_RESUME_OFFSET_US));
boolean isServerSideInserted = bundle.getBoolean(keyForField(FIELD_IS_SERVER_SIDE_INSERTED));
return new AdGroup(
count,
states == null ? new int[0] : states,
uriList == null ? new Uri[0] : uriList.toArray(new Uri[0]),
durationsUs == null ? new long[0] : durationsUs,
contentResumeOffsetUs);
contentResumeOffsetUs,
isServerSideInserted);
}
private static String keyForField(@AdGroup.FieldNumber int field) {
@ -605,6 +631,21 @@ public final class AdPlaybackState implements Bundleable {
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
}
/**
* Returns an instance with the specified value for {@link AdGroup#isServerSideInserted} in the
* specified ad group.
*/
@CheckResult
public AdPlaybackState withIsServerSideInserted(int adGroupIndex, boolean isServerSideInserted) {
if (adGroups[adGroupIndex].isServerSideInserted == isServerSideInserted) {
return this;
}
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
adGroups[adGroupIndex] = adGroups[adGroupIndex].withIsServerSideInserted(isServerSideInserted);
return new AdPlaybackState(
adsId, adGroupTimesUs, adGroups, adResumePositionUs, contentDurationUs);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {

View file

@ -195,6 +195,8 @@ public class AdPlaybackStateTest {
.withAdUri(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1, TEST_URI)
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 4444)
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 3333)
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true)
.withAdDurationsUs(new long[][] {{12}, {34, 56}})
.withAdResumePositionUs(123)
.withContentDurationUs(456);
@ -219,7 +221,8 @@ public class AdPlaybackStateTest {
.withAdUri(Uri.parse("https://www.google.com"), /* index= */ 0)
.withAdUri(Uri.EMPTY, /* index= */ 1)
.withAdDurationsUs(new long[] {1234, 5678})
.withContentResumeOffsetUs(4444);
.withContentResumeOffsetUs(4444)
.withIsServerSideInserted(true);
assertThat(AdPlaybackState.AdGroup.CREATOR.fromBundle(adGroup.toBundle())).isEqualTo(adGroup);
}