Add adId to AdGroup

PiperOrigin-RevId: 706761644
This commit is contained in:
bachinger 2024-12-16 10:44:56 -08:00 committed by Copybara-Service
parent d5d85558c1
commit 3fe1f2a734
2 changed files with 171 additions and 21 deletions

View File

@ -41,6 +41,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
/**
* Represents ad group times and information on the state and URIs of ads within each ad group.
@ -90,6 +91,9 @@ public final class AdPlaybackState {
/** The durations of each ad in the ad group, in microseconds. */
public final long[] durationsUs;
/** The optional IDs of the ads. */
public final @NullableType String[] ids;
/**
* The offset in microseconds which should be added to the content stream when resuming playback
* after the ad group.
@ -114,7 +118,8 @@ public final class AdPlaybackState {
/* mediaItems= */ new MediaItem[0],
/* durationsUs= */ new long[0],
/* contentResumeOffsetUs= */ 0,
/* isServerSideInserted= */ false);
/* isServerSideInserted= */ false,
/* ids= */ new String[0]);
}
@SuppressWarnings("deprecation") // Intentionally assigning deprecated field
@ -126,7 +131,8 @@ public final class AdPlaybackState {
@NullableType MediaItem[] mediaItems,
long[] durationsUs,
long contentResumeOffsetUs,
boolean isServerSideInserted) {
boolean isServerSideInserted,
@NullableType String[] ids) {
checkArgument(states.length == mediaItems.length);
this.timeUs = timeUs;
this.count = count;
@ -140,6 +146,7 @@ public final class AdPlaybackState {
for (int i = 0; i < uris.length; i++) {
uris[i] = mediaItems[i] == null ? null : checkNotNull(mediaItems[i].localConfiguration).uri;
}
this.ids = ids;
}
/**
@ -211,7 +218,8 @@ public final class AdPlaybackState {
&& Arrays.equals(states, adGroup.states)
&& Arrays.equals(durationsUs, adGroup.durationsUs)
&& contentResumeOffsetUs == adGroup.contentResumeOffsetUs
&& isServerSideInserted == adGroup.isServerSideInserted;
&& isServerSideInserted == adGroup.isServerSideInserted
&& Arrays.equals(ids, adGroup.ids);
}
@Override
@ -224,6 +232,7 @@ public final class AdPlaybackState {
result = 31 * result + Arrays.hashCode(durationsUs);
result = 31 * result + (int) (contentResumeOffsetUs ^ (contentResumeOffsetUs >>> 32));
result = 31 * result + (isServerSideInserted ? 1 : 0);
result = 31 * result + Arrays.hashCode(ids);
return result;
}
@ -238,7 +247,8 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/** Returns a new instance with the ad count set to {@code count}. */
@ -247,6 +257,7 @@ public final class AdPlaybackState {
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, count);
long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count);
@NullableType MediaItem[] mediaItems = Arrays.copyOf(this.mediaItems, count);
@NullableType String[] ids = Arrays.copyOf(this.ids, count);
return new AdGroup(
timeUs,
count,
@ -255,7 +266,8 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/**
@ -281,6 +293,9 @@ public final class AdPlaybackState {
@NullableType MediaItem[] mediaItems = Arrays.copyOf(this.mediaItems, states.length);
mediaItems[index] = mediaItem;
states[index] = AD_STATE_AVAILABLE;
@NullableType
String[] ids =
this.ids.length == states.length ? this.ids : Arrays.copyOf(this.ids, states.length);
return new AdGroup(
timeUs,
count,
@ -289,7 +304,8 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/**
@ -317,6 +333,9 @@ public final class AdPlaybackState {
this.mediaItems.length == states.length
? this.mediaItems
: Arrays.copyOf(this.mediaItems, states.length);
@NullableType
String[] ids =
this.ids.length == states.length ? this.ids : Arrays.copyOf(this.ids, states.length);
states[index] = state;
return new AdGroup(
timeUs,
@ -326,7 +345,8 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/** Returns a new instance with the specified ad durations, in microseconds. */
@ -345,7 +365,37 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/** Returns a new instance with the specified ID for the given ad index. */
@CheckResult
public AdGroup withAdId(String adId, @IntRange(from = 0) int index) {
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, index + 1);
long[] durationsUs =
this.durationsUs.length == states.length
? this.durationsUs
: copyDurationsUsWithSpaceForAdCount(this.durationsUs, states.length);
@NullableType
MediaItem[] mediaItems =
this.mediaItems.length == states.length
? this.mediaItems
: Arrays.copyOf(this.mediaItems, states.length);
@NullableType
String[] ids =
this.ids.length == states.length ? this.ids : Arrays.copyOf(this.ids, states.length);
ids[index] = adId;
return new AdGroup(
timeUs,
count,
originalCount,
states,
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
}
/** Returns an instance with the specified {@link #contentResumeOffsetUs}. */
@ -359,7 +409,8 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/** Returns an instance with the specified value for {@link #isServerSideInserted}. */
@ -373,7 +424,8 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/** Returns an instance with the specified value for {@link #originalCount}. */
@ -386,7 +438,8 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/** Removes the last ad from the ad group. */
@ -398,6 +451,7 @@ public final class AdPlaybackState {
if (durationsUs.length > newCount) {
newDurationsUs = Arrays.copyOf(durationsUs, newCount);
}
@NullableType String[] newIds = Arrays.copyOf(ids, newCount);
return new AdGroup(
timeUs,
newCount,
@ -406,7 +460,8 @@ public final class AdPlaybackState {
newMediaItems,
newDurationsUs,
/* contentResumeOffsetUs= */ Util.sum(newDurationsUs),
isServerSideInserted);
isServerSideInserted,
newIds);
}
/**
@ -424,7 +479,8 @@ public final class AdPlaybackState {
/* mediaItems= */ new MediaItem[0],
/* durationsUs= */ new long[0],
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
int count = this.states.length;
@AdState int[] states = Arrays.copyOf(this.states, count);
@ -441,7 +497,8 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/**
@ -470,7 +527,21 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids);
}
/**
* Returns the index of the ad with the given ad ID, or {@link C#INDEX_UNSET} if the ad ID can't
* be found.
*/
public int getIndexOfAdId(String adId) {
for (int i = 0; i < ids.length; i++) {
if (Objects.equals(ids[i], adId)) {
return i;
}
}
return C.INDEX_UNSET;
}
@CheckResult
@ -500,6 +571,7 @@ public final class AdPlaybackState {
private static final String FIELD_IS_SERVER_SIDE_INSERTED = Util.intToStringMaxRadix(6);
private static final String FIELD_ORIGINAL_COUNT = Util.intToStringMaxRadix(7);
@VisibleForTesting static final String FIELD_MEDIA_ITEMS = Util.intToStringMaxRadix(8);
static final String FIELD_IDS = Util.intToStringMaxRadix(9);
// Intentionally assigning deprecated field.
// putParcelableArrayList actually supports null elements.
@ -516,6 +588,7 @@ public final class AdPlaybackState {
bundle.putLongArray(FIELD_DURATIONS_US, durationsUs);
bundle.putLong(FIELD_CONTENT_RESUME_OFFSET_US, contentResumeOffsetUs);
bundle.putBoolean(FIELD_IS_SERVER_SIDE_INSERTED, isServerSideInserted);
bundle.putStringArrayList(FIELD_IDS, new ArrayList<>(Arrays.asList(ids)));
return bundle;
}
@ -536,6 +609,7 @@ public final class AdPlaybackState {
@Nullable long[] durationsUs = bundle.getLongArray(FIELD_DURATIONS_US);
long contentResumeOffsetUs = bundle.getLong(FIELD_CONTENT_RESUME_OFFSET_US);
boolean isServerSideInserted = bundle.getBoolean(FIELD_IS_SERVER_SIDE_INSERTED);
@Nullable ArrayList<String> ids = bundle.getStringArrayList(FIELD_IDS);
return new AdGroup(
timeUs,
count,
@ -544,7 +618,8 @@ public final class AdPlaybackState {
getMediaItemsFromBundleArrays(mediaItemBundleList, uriList),
durationsUs == null ? new long[0] : durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids == null ? new String[0] : ids.toArray(new String[0]));
}
private ArrayList<@NullableType Bundle> getMediaItemsArrayBundles() {
@ -914,6 +989,17 @@ public final class AdPlaybackState {
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/** Returns an instance with the specified ad ID for the given ad. */
@CheckResult
public AdPlaybackState withAdId(
@IntRange(from = 0) int adGroupIndex, @IntRange(from = 0) int adIndexInAdGroup, String adId) {
int adjustedIndex = adGroupIndex - removedAdGroupCount;
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
adGroups[adjustedIndex] = adGroups[adjustedIndex].withAdId(adId, adIndexInAdGroup);
return new AdPlaybackState(
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/**
* Returns an instance with all ads in the specified ad group skipped (except for those already
* marked as played or in the error state).
@ -1074,7 +1160,7 @@ public final class AdPlaybackState {
}
/**
* Appends a live postroll placeholder ad group to the ad playback state.
* Appends a live post roll placeholder ad group to the ad playback state.
*
* <p>Adding such a placeholder is only required for periods of server side ad insertion live
* streams. A player is not expected to play this placeholder. It is only used to indicate that
@ -1110,6 +1196,18 @@ public final class AdPlaybackState {
return adGroupIndex == adGroupCount - 1 && getAdGroup(adGroupIndex).isLivePostrollPlaceholder();
}
/**
* Returns the index of the ad with the given ad ID in the given ad group, or {@link
* C#INDEX_UNSET} if the ad ID can't be found.
*
* @param adGroupIndex The ad group index.
* @param adId The ad ID.
* @return The ad index in the ad group, or {@link C#INDEX_UNSET} if the ad ID is not found.
*/
public int getAdIndexOfAdId(int adGroupIndex, String adId) {
return getAdGroup(adGroupIndex).getIndexOfAdId(adId);
}
/**
* Returns a copy of the ad playback state with the given ads ID.
*
@ -1131,7 +1229,8 @@ public final class AdPlaybackState {
Arrays.copyOf(adGroup.mediaItems, adGroup.mediaItems.length),
Arrays.copyOf(adGroup.durationsUs, adGroup.durationsUs.length),
adGroup.contentResumeOffsetUs,
adGroup.isServerSideInserted);
adGroup.isServerSideInserted,
adGroup.ids);
}
return new AdPlaybackState(
adsId,

View File

@ -44,9 +44,18 @@ public class AdPlaybackStateTest {
new AdPlaybackState(TEST_ADS_ID, TEST_AD_GROUP_TIMES_US).withRemovedAdGroupCount(1);
assertThat(state.getAdGroup(1).count).isEqualTo(C.LENGTH_UNSET);
state = state.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1);
assertThat(state.getAdGroup(1).states).hasLength(0);
assertThat(state.getAdGroup(1).mediaItems).hasLength(0);
assertThat(state.getAdGroup(1).durationsUs).hasLength(0);
assertThat(state.getAdGroup(1).ids).hasLength(0);
assertThat(state.getAdGroup(1).count).isEqualTo(1);
state = state.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 4);
assertThat(state.getAdGroup(1).count).isEqualTo(4);
assertThat(state.getAdGroup(1).states).hasLength(4);
assertThat(state.getAdGroup(1).mediaItems).hasLength(4);
assertThat(state.getAdGroup(1).durationsUs).hasLength(4);
assertThat(state.getAdGroup(1).ids).hasLength(4);
}
@Test
@ -488,9 +497,12 @@ public class AdPlaybackStateTest {
.withPlayedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0)
.withAvailableAdMediaItem(
/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, TEST_MEDIA_ITEM)
.withAdId(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, "ad-1-0")
.withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 2)
.withSkippedAd(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0)
.withPlayedAd(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1)
.withAdId(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, "ad-2-0")
.withAdId(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, "ad-2-1")
.withAvailableAdMediaItem(
/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, TEST_MEDIA_ITEM)
.withAvailableAdMediaItem(
@ -528,7 +540,9 @@ public class AdPlaybackStateTest {
.withAdMediaItem(new MediaItem.Builder().setUri(Uri.EMPTY).build(), /* index= */ 1)
.withAdDurationsUs(new long[] {1234, 5678})
.withContentResumeOffsetUs(4444)
.withIsServerSideInserted(true);
.withIsServerSideInserted(true)
.withAdId("id-0", 0)
.withAdId("id-1", 1);
assertThat(AdPlaybackState.AdGroup.fromBundle(adGroup.toBundle())).isEqualTo(adGroup);
}
@ -540,6 +554,8 @@ public class AdPlaybackStateTest {
.withAdCount(2)
.withAdState(AD_STATE_AVAILABLE, /* index= */ 0)
.withAdState(AD_STATE_PLAYED, /* index= */ 1)
.withAdId("ad-0", /* index= */ 1)
.withAdId("ad-1", /* index= */ 1)
.withAdMediaItem(
new MediaItem.Builder().setUri("https://www.google.com").build(), /* index= */ 0)
.withAdMediaItem(new MediaItem.Builder().setUri(Uri.EMPTY).build(), /* index= */ 1)
@ -857,4 +873,39 @@ public class AdPlaybackStateTest {
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ C.TIME_UNSET))
.isEqualTo(C.INDEX_UNSET);
}
@Test
public void getAdIndexOfAdId() {
AdPlaybackState state =
new AdPlaybackState("adsId", /* adGroupTimesUs...= */ 0L, 1L, 2L)
.withAdCount(0, 1)
.withAdCount(1, 3)
.withAdCount(2, 2)
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad-0-0")
.withAdId(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0, "ad-1-0")
.withAdId(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 2, "ad-1-2")
.withAdId(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0, "ad-2-0")
.withAdId(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 1, "ad-2-1")
.withRemovedAdGroupCount(/* removedAdGroupCount= */ 1);
assertThat(state.getAdIndexOfAdId(/* adGroupIndex= */ 0, "ad-0-0")).isEqualTo(C.INDEX_UNSET);
assertThat(state.getAdIndexOfAdId(/* adGroupIndex= */ 1, "ad-1-0")).isEqualTo(0);
assertThat(state.getAdIndexOfAdId(/* adGroupIndex= */ 1, "ad-1-1")).isEqualTo(C.INDEX_UNSET);
assertThat(state.getAdIndexOfAdId(/* adGroupIndex= */ 1, "ad-1-2")).isEqualTo(2);
assertThat(state.getAdIndexOfAdId(/* adGroupIndex= */ 2, "ad-2-0")).isEqualTo(0);
assertThat(state.getAdIndexOfAdId(/* adGroupIndex= */ 2, "ad-2-1")).isEqualTo(1);
}
@Test
public void fromBundle_withNullElements_correctlyBundledUnbundled() {
AdPlaybackState.AdGroup adGroup =
new AdPlaybackState.AdGroup(/* timeUs= */ 0L)
.withAdCount(3)
.withAdId(/* adId= */ "0", /* index= */ 0)
.withAdId(/* adId= */ "2", /* index= */ 2);
// Asserts that the missing @NullableType in fromBundle() isn't harmful.
assertThat(AdPlaybackState.AdGroup.fromBundle(adGroup.toBundle()).ids[1]).isNull();
assertThat(AdPlaybackState.AdGroup.fromBundle(adGroup.toBundle())).isEqualTo(adGroup);
}
}