Add client side post roll placeholder

PiperOrigin-RevId: 707142019
This commit is contained in:
bachinger 2024-12-17 09:56:15 -08:00 committed by Copybara-Service
parent 32ab258c43
commit aa2ee8f702
7 changed files with 349 additions and 82 deletions

View file

@ -35,6 +35,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.errorprone.annotations.InlineMe;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -71,7 +72,7 @@ public final class AdPlaybackState {
/**
* The original number of ads in the ad group in case the ad group is only partially available,
* or {@link C#LENGTH_UNSET} if unknown. An ad can be partially available when a server side
* or {@link C#LENGTH_UNSET} if unknown. An ad can be partially available when a server-side
* inserted ad live stream is joined while an ad is already playing and some ad information is
* missing.
*/
@ -103,6 +104,9 @@ public final class AdPlaybackState {
/** Whether this ad group is server-side inserted and part of the content stream. */
public final boolean isServerSideInserted;
/** Whether this is an ignorable placeholder that must not be attempted to be played. */
public final boolean isPlaceholder;
/**
* Creates a new ad group with an unspecified number of ads.
*
@ -119,7 +123,8 @@ public final class AdPlaybackState {
/* durationsUs= */ new long[0],
/* contentResumeOffsetUs= */ 0,
/* isServerSideInserted= */ false,
/* ids= */ new String[0]);
/* ids= */ new String[0],
/* isPlaceholder= */ false);
}
@SuppressWarnings("deprecation") // Intentionally assigning deprecated field
@ -132,7 +137,8 @@ public final class AdPlaybackState {
long[] durationsUs,
long contentResumeOffsetUs,
boolean isServerSideInserted,
@NullableType String[] ids) {
@NullableType String[] ids,
boolean isPlaceholder) {
checkArgument(states.length == mediaItems.length);
this.timeUs = timeUs;
this.count = count;
@ -147,6 +153,7 @@ public final class AdPlaybackState {
uris[i] = mediaItems[i] == null ? null : checkNotNull(mediaItems[i].localConfiguration).uri;
}
this.ids = ids;
this.isPlaceholder = isPlaceholder;
}
/**
@ -162,7 +169,7 @@ public final class AdPlaybackState {
* lastPlayedAdIndex}, or {@link #count} if no later ads should be played. If no ads have been
* played, pass -1 to get the index of the first ad to play.
*
* <p>Note: {@linkplain #isServerSideInserted Server side inserted ads} are always considered
* <p>Note: {@linkplain #isServerSideInserted server-side inserted ads} are always considered
* playable.
*/
public int getNextAdIndexToPlay(@IntRange(from = -1) int lastPlayedAdIndex) {
@ -198,8 +205,24 @@ public final class AdPlaybackState {
return false;
}
private boolean isLivePostrollPlaceholder() {
return isServerSideInserted && timeUs == C.TIME_END_OF_SOURCE && count == C.LENGTH_UNSET;
/**
* Returns whether this is a is a placeholder ad group.
*
* @param isServerSideInserted Whether the postroll placeholder must be server-side inserted.
* @return true only if this ad group has a matching {@link #isServerSideInserted} flag.
*/
public boolean isLivePostrollPlaceholder(boolean isServerSideInserted) {
return (this.isServerSideInserted == isServerSideInserted) && isLivePostrollPlaceholder();
}
/**
* Returns whether this is a placeholder ad group. It can be server-side inserted or not. Use
* {@link #isLivePostrollPlaceholder(boolean)} if you want to differentiate.
*
* @return true only if this is a live postroll placeholder.
*/
public boolean isLivePostrollPlaceholder() {
return isPlaceholder && timeUs == C.TIME_END_OF_SOURCE && count == C.LENGTH_UNSET;
}
@Override
@ -219,7 +242,8 @@ public final class AdPlaybackState {
&& Arrays.equals(durationsUs, adGroup.durationsUs)
&& contentResumeOffsetUs == adGroup.contentResumeOffsetUs
&& isServerSideInserted == adGroup.isServerSideInserted
&& Arrays.equals(ids, adGroup.ids);
&& Arrays.equals(ids, adGroup.ids)
&& isPlaceholder == adGroup.isPlaceholder;
}
@Override
@ -233,6 +257,7 @@ public final class AdPlaybackState {
result = 31 * result + (int) (contentResumeOffsetUs ^ (contentResumeOffsetUs >>> 32));
result = 31 * result + (isServerSideInserted ? 1 : 0);
result = 31 * result + Arrays.hashCode(ids);
result = 31 * result + (isPlaceholder ? 1 : 0);
return result;
}
@ -248,7 +273,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/** Returns a new instance with the ad count set to {@code count}. */
@ -267,7 +293,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/**
@ -305,7 +332,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/**
@ -346,7 +374,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/** Returns a new instance with the specified ad durations, in microseconds. */
@ -366,7 +395,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/** Returns a new instance with the specified ID for the given ad index. */
@ -395,7 +425,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/** Returns an instance with the specified {@link #contentResumeOffsetUs}. */
@ -410,7 +441,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/** Returns an instance with the specified value for {@link #isServerSideInserted}. */
@ -425,7 +457,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/** Returns an instance with the specified value for {@link #originalCount}. */
@ -439,7 +472,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/** Removes the last ad from the ad group. */
@ -461,7 +495,8 @@ public final class AdPlaybackState {
newDurationsUs,
/* contentResumeOffsetUs= */ Util.sum(newDurationsUs),
isServerSideInserted,
newIds);
newIds,
isPlaceholder);
}
/**
@ -480,7 +515,8 @@ public final class AdPlaybackState {
/* durationsUs= */ new long[0],
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
int count = this.states.length;
@AdState int[] states = Arrays.copyOf(this.states, count);
@ -498,7 +534,8 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
/**
@ -528,7 +565,22 @@ public final class AdPlaybackState {
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids);
ids,
isPlaceholder);
}
private AdGroup withIsPlaceholder(boolean isPlaceholder, boolean isServerSideInserted) {
return new AdGroup(
timeUs,
count,
originalCount,
states,
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids,
isPlaceholder);
}
/**
@ -572,6 +624,7 @@ public final class AdPlaybackState {
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);
static final String FIELD_IS_PLACEHOLDER = Util.intToStringMaxRadix(10);
// Intentionally assigning deprecated field.
// putParcelableArrayList actually supports null elements.
@ -589,6 +642,7 @@ public final class AdPlaybackState {
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)));
bundle.putBoolean(FIELD_IS_PLACEHOLDER, isPlaceholder);
return bundle;
}
@ -610,6 +664,7 @@ public final class AdPlaybackState {
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);
boolean isPlaceholder = bundle.getBoolean(FIELD_IS_PLACEHOLDER);
return new AdGroup(
timeUs,
count,
@ -619,7 +674,8 @@ public final class AdPlaybackState {
durationsUs == null ? new long[0] : durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids == null ? new String[0] : ids.toArray(new String[0]));
ids == null ? new String[0] : ids.toArray(new String[0]),
isPlaceholder);
}
private ArrayList<@NullableType Bundle> getMediaItemsArrayBundles() {
@ -929,11 +985,11 @@ public final class AdPlaybackState {
/**
* Returns an instance with the specified ad marked as {@linkplain #AD_STATE_AVAILABLE available}.
*
* <p>Must not be called with client side inserted ad groups. Client side inserted ads should use
* <p>Must not be called with client-side inserted ad groups. Client-side inserted ads should use
* {@link #withAvailableAdMediaItem}.
*
* @throws IllegalStateException in case this methods is called on an ad group that {@linkplain
* AdGroup#isServerSideInserted is not server side inserted}.
* AdGroup#isServerSideInserted is not server-side inserted}.
*/
@CheckResult
public AdPlaybackState withAvailableAd(
@ -1160,24 +1216,55 @@ public final class AdPlaybackState {
}
/**
* Appends a live post roll placeholder ad group to the ad playback state.
* @deprecated Use {@link #withLivePostrollPlaceholderAppended(boolean)} and pass {@code true}
* instead.
*/
@InlineMe(replacement = "this.withLivePostrollPlaceholderAppended(true)")
@Deprecated
public AdPlaybackState withLivePostrollPlaceholderAppended() {
return withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
}
/**
* Appends a live postroll 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
* another ad group with this ad group index will be inserted in the future.
* <p>Adding such a placeholder is only required for periods of live streams. A player is not
* expected to play this placeholder. It is only used to indicate that another ad group with this
* ad group index will be inserted in the future.
*
* <p>See {@link #endsWithLivePostrollPlaceHolder()} also.
* <p>See {@link #endsWithLivePostrollPlaceHolder()} and {@link
* #endsWithLivePostrollPlaceHolder(boolean)} also.
*
* @param isServerSideInserted Whether this is a server-side inserted ad (single stream).
* @return The new ad playback state instance ending with a live postroll placeholder.
*/
public AdPlaybackState withLivePostrollPlaceholderAppended() {
public AdPlaybackState withLivePostrollPlaceholderAppended(boolean isServerSideInserted) {
return withNewAdGroup(adGroupCount, /* adGroupTimeUs= */ C.TIME_END_OF_SOURCE)
.withIsServerSideInserted(adGroupCount, true);
.withIsPlaceholder(adGroupCount, /* isPlaceholder= */ true, isServerSideInserted);
}
@VisibleForTesting
/* package */ AdPlaybackState withIsPlaceholder(
int adGroupIndex, boolean isPlaceholder, boolean isServerSideInserted) {
int adjustedIndex = adGroupIndex - removedAdGroupCount;
if (adGroups[adjustedIndex].isPlaceholder == isPlaceholder
&& adGroups[adjustedIndex].isServerSideInserted == isServerSideInserted) {
return this;
}
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
adGroups[adjustedIndex] =
adGroups[adjustedIndex].withIsPlaceholder(isPlaceholder, isServerSideInserted);
return new AdPlaybackState(
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/**
* Returns whether the last ad group is a live postroll placeholder as inserted by {@link
* #withLivePostrollPlaceholderAppended()}.
* #withLivePostrollPlaceholderAppended(boolean)}.
*
* <p>Note: That either server-side or client-side inserted placeholders are considered. Use
* {@link #endsWithLivePostrollPlaceHolder(boolean)} if you want to test for one or the other
* only.
*
* @return Whether the ad playback state ends with a live postroll placeholder.
*/
@ -1187,7 +1274,22 @@ public final class AdPlaybackState {
}
/**
* Whether the {@link AdGroup} at the given ad group index is a live postroll placeholder.
* Returns whether the last ad group is a live postroll placeholder as inserted by {@link
* #withLivePostrollPlaceholderAppended(boolean)} .
*
* @param isServerSideInserted Whether the trailing placeholder is server-side inserted.
* @return Whether the ad playback state ends with a live postroll placeholder.
*/
public boolean endsWithLivePostrollPlaceHolder(boolean isServerSideInserted) {
int adGroupIndex = adGroupCount - 1;
return adGroupIndex >= 0 && isLivePostrollPlaceholder(adGroupIndex, isServerSideInserted);
}
/**
* Returns whether the {@link AdGroup} at the given ad group index is a live postroll placeholder.
*
* <p>Note: That either server-side or client-side inserted placeholders return true. Use {@link
* #isLivePostrollPlaceholder(int, boolean)} if you want to test for one or the other only.
*
* @param adGroupIndex The ad group index.
* @return True if the ad group at the given index is a live postroll placeholder, false if not.
@ -1196,6 +1298,19 @@ public final class AdPlaybackState {
return adGroupIndex == adGroupCount - 1 && getAdGroup(adGroupIndex).isLivePostrollPlaceholder();
}
/**
* Returns whether the {@link AdGroup} at the given ad group index is a live postroll placeholder
* and either server or client-side inserted.
*
* @param adGroupIndex The ad group index.
* @param isServerSideInserted Whether the placeholder is server-side inserted.
* @return True if the ad group at the given index is a live postroll placeholder, false if not.
*/
public boolean isLivePostrollPlaceholder(int adGroupIndex, boolean isServerSideInserted) {
return adGroupIndex == adGroupCount - 1
&& getAdGroup(adGroupIndex).isLivePostrollPlaceholder(isServerSideInserted);
}
/**
* 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.
@ -1230,7 +1345,8 @@ public final class AdPlaybackState {
Arrays.copyOf(adGroup.durationsUs, adGroup.durationsUs.length),
adGroup.contentResumeOffsetUs,
adGroup.isServerSideInserted,
adGroup.ids);
adGroup.ids,
adGroup.isPlaceholder);
}
return new AdPlaybackState(
adsId,
@ -1332,7 +1448,7 @@ public final class AdPlaybackState {
// placeholder in a period of a multi-period live window, or when c) the position actually is
// before the given period duration.
return periodDurationUs == C.TIME_UNSET
|| (adGroup.isServerSideInserted && adGroup.count == C.LENGTH_UNSET)
|| adGroup.isLivePostrollPlaceholder()
|| positionUs < periodDurationUs;
}
return positionUs < adGroupPositionUs;

View file

@ -568,18 +568,66 @@ public class AdPlaybackStateTest {
assertThat(AdPlaybackState.AdGroup.fromBundle(bundle)).isEqualTo(adGroup);
}
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // testing deprecated API
@Test
public void withLivePostrollPlaceholderAppended_emptyAdPlaybackState_insertsPlaceholder() {
AdPlaybackState adPlaybackState =
new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
AdPlaybackState emptyAdPlaybackState = new AdPlaybackState("adsId");
assertThat(adPlaybackState.adGroupCount).isEqualTo(1);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).timeUs)
.isEqualTo(C.TIME_END_OF_SOURCE);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).count).isEqualTo(C.LENGTH_UNSET);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).isServerSideInserted).isTrue();
assertThat(
emptyAdPlaybackState.withLivePostrollPlaceholderAppended(
/* isServerSideInserted= */ true))
.isEqualTo(
new AdPlaybackState("adsId", C.TIME_END_OF_SOURCE)
.withIsPlaceholder(
/* adGroupIndex= */ 0,
/* isPlaceholder= */ true,
/* isServerSideInserted= */ true));
assertThat(emptyAdPlaybackState.withLivePostrollPlaceholderAppended())
.isEqualTo(
new AdPlaybackState("adsId", C.TIME_END_OF_SOURCE)
.withIsPlaceholder(
/* adGroupIndex= */ 0,
/* isPlaceholder= */ true,
/* isServerSideInserted= */ true));
assertThat(
emptyAdPlaybackState.withLivePostrollPlaceholderAppended(
/* isServerSideInserted= */ false))
.isEqualTo(
new AdPlaybackState("adsId", C.TIME_END_OF_SOURCE)
.withIsPlaceholder(
/* adGroupIndex= */ 0,
/* isPlaceholder= */ true,
/* isServerSideInserted= */ false));
}
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // testing deprecated API
@Test
public void endsWithLivePostrollPlaceHolder_emptyAdPlaybackState_insertsPlaceholder() {
AdPlaybackState emptyAdPlaybackState = new AdPlaybackState("adsId");
assertThat(
emptyAdPlaybackState
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true)
.endsWithLivePostrollPlaceHolder())
.isTrue();
assertThat(
emptyAdPlaybackState
.withLivePostrollPlaceholderAppended()
.endsWithLivePostrollPlaceHolder(/* isServerSideInserted= */ true))
.isTrue();
assertThat(
emptyAdPlaybackState
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
.endsWithLivePostrollPlaceHolder())
.isTrue();
assertThat(
emptyAdPlaybackState
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
.endsWithLivePostrollPlaceHolder(/* isServerSideInserted= */ false))
.isTrue();
}
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // testing deprecated API
@Test
public void withLivePostrollPlaceholderAppended_withExistingAdGroups_appendsPlaceholder() {
AdPlaybackState adPlaybackState =
@ -591,13 +639,32 @@ public class AdPlaybackStateTest {
.withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs...= */ 10_000_000L)
.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ 5_000_000L);
adPlaybackState = adPlaybackState.withLivePostrollPlaceholderAppended();
assertThat(adPlaybackState.adGroupCount).isEqualTo(3);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).timeUs)
.isEqualTo(C.TIME_END_OF_SOURCE);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).count).isEqualTo(C.LENGTH_UNSET);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).isServerSideInserted).isTrue();
assertThat(
adPlaybackState.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true))
.isEqualTo(
adPlaybackState
.withNewAdGroup(/* adGroupIndex= */ 2, C.TIME_END_OF_SOURCE)
.withIsPlaceholder(
/* adGroupIndex= */ 2,
/* isPlaceholder= */ true,
/* isServerSideInserted= */ true));
assertThat(adPlaybackState.withLivePostrollPlaceholderAppended())
.isEqualTo(
adPlaybackState
.withNewAdGroup(/* adGroupIndex= */ 2, C.TIME_END_OF_SOURCE)
.withIsPlaceholder(
/* adGroupIndex= */ 2,
/* isPlaceholder= */ true,
/* isServerSideInserted= */ true));
assertThat(
adPlaybackState.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false))
.isEqualTo(
adPlaybackState
.withNewAdGroup(/* adGroupIndex= */ 2, C.TIME_END_OF_SOURCE)
.withIsPlaceholder(
/* adGroupIndex= */ 2,
/* isPlaceholder= */ true,
/* isServerSideInserted= */ false));
}
@Test
@ -611,14 +678,37 @@ public class AdPlaybackStateTest {
.withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs...= */ 10_000_000L)
.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ 5_000_000L);
boolean endsWithLivePostrollPlaceHolder = adPlaybackState.endsWithLivePostrollPlaceHolder();
assertThat(
adPlaybackState
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true)
.endsWithLivePostrollPlaceHolder())
.isTrue();
assertThat(
adPlaybackState
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true)
.endsWithLivePostrollPlaceHolder(/* isServerSideInserted= */ true))
.isTrue();
assertThat(
adPlaybackState
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true)
.endsWithLivePostrollPlaceHolder(/* isServerSideInserted= */ false))
.isFalse();
assertThat(endsWithLivePostrollPlaceHolder).isFalse();
adPlaybackState = adPlaybackState.withLivePostrollPlaceholderAppended();
endsWithLivePostrollPlaceHolder = adPlaybackState.endsWithLivePostrollPlaceHolder();
assertThat(endsWithLivePostrollPlaceHolder).isTrue();
assertThat(
adPlaybackState
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
.endsWithLivePostrollPlaceHolder())
.isTrue();
assertThat(
adPlaybackState
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
.endsWithLivePostrollPlaceHolder(/* isServerSideInserted= */ false))
.isTrue();
assertThat(
adPlaybackState
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false)
.endsWithLivePostrollPlaceHolder(/* isServerSideInserted= */ true))
.isFalse();
}
@Test
@ -783,7 +873,7 @@ public class AdPlaybackStateTest {
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
.withLivePostrollPlaceholderAppended();
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
assertThat(
state.getAdGroupIndexAfterPositionUs(
@ -811,13 +901,51 @@ public class AdPlaybackStateTest {
public void
getAdGroupIndexForPositionUs_withServerSidePostrollPlaceholderForLive_ignoresPlaceholder() {
AdPlaybackState state =
new AdPlaybackState("adsId", /* adGroupTimesUs...= */ 0L, 5_000_000L, C.TIME_END_OF_SOURCE)
new AdPlaybackState("adsId", /* adGroupTimesUs...= */ 0L, 5_000_000L)
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
assertThat(
state.getAdGroupIndexForPositionUs(
/* positionUs= */ 4_999_999L, /* periodDurationUs= */ 10_000_000L))
.isEqualTo(C.INDEX_UNSET);
assertThat(
state.getAdGroupIndexForPositionUs(
/* positionUs= */ 4_999_999L, /* periodDurationUs= */ C.TIME_UNSET))
.isEqualTo(C.INDEX_UNSET);
assertThat(
state.getAdGroupIndexForPositionUs(
/* positionUs= */ 5_000_000L, /* periodDurationUs= */ 10_000_000L))
.isEqualTo(1);
assertThat(
state.getAdGroupIndexForPositionUs(
/* positionUs= */ 5_000_000L, /* periodDurationUs= */ C.TIME_UNSET))
.isEqualTo(1);
assertThat(
state.getAdGroupIndexForPositionUs(
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ 10_000_000L))
.isEqualTo(1);
assertThat(
state.getAdGroupIndexForPositionUs(
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ C.TIME_UNSET))
.isEqualTo(1);
}
@Test
public void
getAdGroupIndexForPositionUs_withClientSidePostrollPlaceholderForLive_ignoresPlaceholder() {
AdPlaybackState state =
new AdPlaybackState("adsId", /* adGroupTimesUs...= */ 0L, 5_000_000L)
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
.withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ false);
assertThat(
state.getAdGroupIndexForPositionUs(
@ -849,8 +977,8 @@ public class AdPlaybackStateTest {
public void
getAdGroupIndexForPositionUs_withOnlyServerSidePostrollPlaceholderForLive_ignoresPlaceholder() {
AdPlaybackState state =
new AdPlaybackState("adsId", /* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE)
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true);
new AdPlaybackState("adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
assertThat(
state.getAdGroupIndexForPositionUs(

View file

@ -198,7 +198,8 @@ public final class ServerSideAdInsertionMediaSourceTest {
public void onContinueLoadingRequested(MediaPeriod source) {}
};
AdPlaybackState adPlaybackState =
new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState("adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
FakeTimeline wrappedTimeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(

View file

@ -861,7 +861,8 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
mainHandler.post(
() ->
setAdPlaybackState(
new AdPlaybackState(adsId).withLivePostrollPlaceholderAppended()));
new AdPlaybackState(adsId)
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true)));
}
prepareChildSource(/* id= */ null, serverSideAdInsertionMediaSource);
}

View file

@ -425,7 +425,9 @@ import java.util.Set;
long windowStartTimeUs =
getWindowStartTimeUs(window.windowStartTimeMs, window.positionInFirstPeriodUs);
totalElapsedContentDurationUs = windowStartTimeUs - window.positionInFirstPeriodUs;
contentOnlyAdPlaybackState = contentOnlyAdPlaybackState.withLivePostrollPlaceholderAppended();
contentOnlyAdPlaybackState =
contentOnlyAdPlaybackState.withLivePostrollPlaceholderAppended(
/* isServerSideInserted= */ true);
}
Map<Object, AdPlaybackState> adPlaybackStates = new HashMap<>();
for (int i = adPlaybackState.removedAdGroupCount; i < adPlaybackState.adGroupCount; i++) {
@ -505,7 +507,8 @@ import java.util.Set;
.withIsServerSideInserted(/* adGroupIndex= */ 0, true)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1);
if (isLiveStream) {
adPlaybackState = adPlaybackState.withLivePostrollPlaceholderAppended();
adPlaybackState =
adPlaybackState.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
}
long adGroupDurationUs = 0;
for (int i = 0; i < adGroup.count; i++) {

View file

@ -1086,7 +1086,8 @@ public class ImaUtilTest {
long liveWindowDurationUs = 60_000_000L;
long nowUs = 110_234_567L;
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
adPlaybackState =
addAdGroupToAdPlaybackState(
adPlaybackState,
@ -1134,7 +1135,8 @@ public class ImaUtilTest {
long liveWindowDurationUs = 60_000_000L;
long nowUs = 110_234_567L;
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
adPlaybackState =
addAdGroupToAdPlaybackState(
adPlaybackState,
@ -1170,7 +1172,8 @@ public class ImaUtilTest {
public void
maybeCorrectPreviouslyUnknownAdDuration_windowPastAdGroups_adPlaybackStateNotChanged() {
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
adPlaybackState =
addAdGroupToAdPlaybackState(
adPlaybackState,
@ -1219,7 +1222,8 @@ public class ImaUtilTest {
/* populateAds= */ false,
/* playedAds= */ false);
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
// Insert first ad resulting in group [10_000_000, 29_000_123, 0, 0]
adPlaybackState =
addLiveAdBreak(
@ -1322,7 +1326,8 @@ public class ImaUtilTest {
public void
maybeCorrectPreviouslyUnknownAdDuration_timelineMovesMultiplePeriodsForward_adDurationCorrected() {
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
// Timeline window to start with: c, a, a, a, [a, c, a], a, a, a
FakeMultiPeriodLiveTimeline contentTimeline =
new FakeMultiPeriodLiveTimeline(
@ -1402,7 +1407,8 @@ public class ImaUtilTest {
windowStartTimeUs
+ contentTimeline.getPeriod(/* periodIndex= */ 1, new Period()).positionInWindowUs;
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
adPlaybackState =
addLiveAdBreak(
/* currentContentPeriodPositionUs= */ firstAdPeriodStartTimeUs,
@ -1438,7 +1444,8 @@ public class ImaUtilTest {
public void
maybeCorrectPreviouslyUnknownAdDuration_timelineMovesMultiplePeriodsForwardStartOfAdGroupNotInWindow_adDurationCorrected() {
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
// 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]
@ -1496,7 +1503,8 @@ public class ImaUtilTest {
public void
maybeCorrectPreviouslyUnknownAdDuration_timelineMovesMultiplePeriodsForwardWithinAdOnlyWindow_adDurationCorrected() {
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
// 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 =
@ -1619,7 +1627,8 @@ public class ImaUtilTest {
long adPeriodDurationUs = msToUs(AD_PERIOD_DURATION_MS);
long periodDurationUs = msToUs(PERIOD_DURATION_MS);
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
// 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]
@ -1688,7 +1697,8 @@ public class ImaUtilTest {
/* populateAds= */ false,
/* playedAds= */ false);
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
adPlaybackState =
addAdGroupToAdPlaybackState(
adPlaybackState,
@ -1724,7 +1734,8 @@ public class ImaUtilTest {
/* populateAds= */ false,
/* playedAds= */ false);
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState(/* adsId= */ "adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
adPlaybackState =
addAdGroupToAdPlaybackState(
adPlaybackState,
@ -2034,7 +2045,8 @@ public class ImaUtilTest {
/* populateAds= */ false,
/* playedAds= */ false);
AdPlaybackState adPlaybackState =
new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState("adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
adPlaybackState =
addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 50_000_000,
@ -2135,7 +2147,8 @@ public class ImaUtilTest {
/* populateAds= */ false,
/* playedAds= */ false);
AdPlaybackState adPlaybackState =
new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState("adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
adPlaybackState =
addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 50_000_000,
@ -2222,7 +2235,8 @@ public class ImaUtilTest {
/* populateAds= */ false,
/* playedAds= */ false);
AdPlaybackState adPlaybackState =
new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState("adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
adPlaybackState =
addLiveAdBreak(
/* currentContentPeriodPositionUs= */ 30_000_000,
@ -2283,7 +2297,8 @@ public class ImaUtilTest {
/* populateAds= */ false,
/* playedAds= */ false);
AdPlaybackState adPlaybackState =
new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState("adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
// Ad events of the first two ads of the group have arrived (the first of the window).
adPlaybackState =
addLiveAdBreak(
@ -2365,7 +2380,8 @@ public class ImaUtilTest {
/* populateAds= */ true,
/* playedAds= */ false);
AdPlaybackState adPlaybackState =
new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
new AdPlaybackState("adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
// Ad events of the first two ads of the group have arrived (the first of the window).
adPlaybackState =
addLiveAdBreak(

View file

@ -250,7 +250,9 @@ public class FakeMultiPeriodLiveTimeline extends Timeline {
boolean isAd = adSequencePattern[lastPeriodIndex % sequencePeriodCount];
AdPlaybackState adPlaybackState = AdPlaybackState.NONE;
if (!isContentTimeline) {
adPlaybackState = new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
adPlaybackState =
new AdPlaybackState("adsId")
.withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
if (isAd && populateAds) {
adPlaybackState =
adPlaybackState