diff --git a/libraries/common/src/main/java/androidx/media3/common/AdPlaybackState.java b/libraries/common/src/main/java/androidx/media3/common/AdPlaybackState.java index 58c1ff1862..6e784fbc26 100644 --- a/libraries/common/src/main/java/androidx/media3/common/AdPlaybackState.java +++ b/libraries/common/src/main/java/androidx/media3/common/AdPlaybackState.java @@ -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. * - *
Note: {@linkplain #isServerSideInserted Server side inserted ads} are always considered + *
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 Must not be called with client side inserted ad groups. Client side inserted ads should use
+ * 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.
*
- * 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.
+ * 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.
*
- * See {@link #endsWithLivePostrollPlaceHolder()} also.
+ * 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)}.
+ *
+ * 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.
+ *
+ * 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;
diff --git a/libraries/common/src/test/java/androidx/media3/common/AdPlaybackStateTest.java b/libraries/common/src/test/java/androidx/media3/common/AdPlaybackStateTest.java
index a1a0a8cbf6..d98f6eca70 100644
--- a/libraries/common/src/test/java/androidx/media3/common/AdPlaybackStateTest.java
+++ b/libraries/common/src/test/java/androidx/media3/common/AdPlaybackStateTest.java
@@ -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(
diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java
index fcb65beaea..6d005f99fc 100644
--- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java
+++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java
@@ -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(
diff --git a/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaServerSideAdInsertionMediaSource.java b/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaServerSideAdInsertionMediaSource.java
index facbc90f27..66c40604bb 100644
--- a/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaServerSideAdInsertionMediaSource.java
+++ b/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaServerSideAdInsertionMediaSource.java
@@ -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);
}
diff --git a/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaUtil.java b/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaUtil.java
index 5c4cf7a26a..a9650fc18a 100644
--- a/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaUtil.java
+++ b/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaUtil.java
@@ -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