diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSource.java
index 55708bb749..e6db19a82b 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSource.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSource.java
@@ -82,10 +82,32 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
implements MediaSource.MediaSourceCaller, MediaSourceEventListener, DrmSessionEventListener {
+ /**
+ * Receives ad playback state update requests when the {@link Timeline} of the content media
+ * source has changed.
+ */
+ public interface AdPlaybackStateUpdater {
+ /**
+ * Called when the content source has refreshed the timeline.
+ *
+ *
If true is returned the source refresh publication is deferred, to wait for an {@link
+ * #setAdPlaybackState(AdPlaybackState) ad playback state update}. If false is returned, the
+ * source refresh is immediately published.
+ *
+ *
Called on the playback thread.
+ *
+ * @param contentTimeline The {@link Timeline} of the wrapped content media source.
+ * @return true to defer the source refresh publication, or false to immediately publish the
+ * source refresh.
+ */
+ boolean onAdPlaybackStateUpdateRequested(Timeline contentTimeline);
+ }
+
private final MediaSource mediaSource;
private final ListMultimap mediaPeriods;
private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcherWithoutId;
private final DrmSessionEventListener.EventDispatcher drmEventDispatcherWithoutId;
+ @Nullable private final AdPlaybackStateUpdater adPlaybackStateUpdater;
@GuardedBy("this")
@Nullable
@@ -99,11 +121,15 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
* Creates the media source.
*
* @param mediaSource The {@link MediaSource} to wrap.
+ * @param adPlaybackStateUpdater The optional {@link AdPlaybackStateUpdater} to be called before a
+ * source refresh is published.
*/
// Calling BaseMediaSource.createEventDispatcher from the constructor.
@SuppressWarnings("nullness:method.invocation")
- public ServerSideAdInsertionMediaSource(MediaSource mediaSource) {
+ public ServerSideAdInsertionMediaSource(
+ MediaSource mediaSource, @Nullable AdPlaybackStateUpdater adPlaybackStateUpdater) {
this.mediaSource = mediaSource;
+ this.adPlaybackStateUpdater = adPlaybackStateUpdater;
mediaPeriods = ArrayListMultimap.create();
adPlaybackState = AdPlaybackState.NONE;
mediaSourceEventDispatcherWithoutId = createEventDispatcher(/* mediaPeriodId= */ null);
@@ -193,10 +219,11 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
@Override
public void onSourceInfoRefreshed(MediaSource source, Timeline timeline) {
this.contentTimeline = timeline;
- if (AdPlaybackState.NONE.equals(adPlaybackState)) {
- return;
+ if ((adPlaybackStateUpdater == null
+ || !adPlaybackStateUpdater.onAdPlaybackStateUpdateRequested(timeline))
+ && !AdPlaybackState.NONE.equals(adPlaybackState)) {
+ refreshSourceInfo(new ServerSideAdInsertionTimeline(timeline, adPlaybackState));
}
- refreshSourceInfo(new ServerSideAdInsertionTimeline(timeline, adPlaybackState));
}
@Override
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 ff99372c5d..b6f7ca45eb 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
@@ -82,7 +82,8 @@ public final class ServerSideAdInsertionMediaSourceTest {
/* windowOffsetInFirstPeriodUs= */ 42_000_000L,
AdPlaybackState.NONE));
ServerSideAdInsertionMediaSource mediaSource =
- new ServerSideAdInsertionMediaSource(new FakeMediaSource(wrappedTimeline));
+ new ServerSideAdInsertionMediaSource(
+ new FakeMediaSource(wrappedTimeline), /* adPlaybackStateUpdater= */ null);
// Test with one ad group before the window, and the window starting within the second ad group.
AdPlaybackState adPlaybackState =
new AdPlaybackState(
@@ -155,8 +156,8 @@ public final class ServerSideAdInsertionMediaSourceTest {
ServerSideAdInsertionMediaSource mediaSource =
new ServerSideAdInsertionMediaSource(
- new DefaultMediaSourceFactory(context)
- .createMediaSource(MediaItem.fromUri(TEST_ASSET)));
+ new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
+ /* adPlaybackStateUpdater= */ null);
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
adPlaybackState =
addAdGroupToAdPlaybackState(
@@ -214,8 +215,8 @@ public final class ServerSideAdInsertionMediaSourceTest {
ServerSideAdInsertionMediaSource mediaSource =
new ServerSideAdInsertionMediaSource(
- new DefaultMediaSourceFactory(context)
- .createMediaSource(MediaItem.fromUri(TEST_ASSET)));
+ new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
+ /* adPlaybackStateUpdater= */ null);
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
adPlaybackState =
addAdGroupToAdPlaybackState(
@@ -274,8 +275,8 @@ public final class ServerSideAdInsertionMediaSourceTest {
ServerSideAdInsertionMediaSource mediaSource =
new ServerSideAdInsertionMediaSource(
- new DefaultMediaSourceFactory(context)
- .createMediaSource(MediaItem.fromUri(TEST_ASSET)));
+ new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
+ /* adPlaybackStateUpdater= */ null);
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
adPlaybackState =
addAdGroupToAdPlaybackState(
@@ -328,8 +329,8 @@ public final class ServerSideAdInsertionMediaSourceTest {
ServerSideAdInsertionMediaSource mediaSource =
new ServerSideAdInsertionMediaSource(
- new DefaultMediaSourceFactory(context)
- .createMediaSource(MediaItem.fromUri(TEST_ASSET)));
+ new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)),
+ /* adPlaybackStateUpdater= */ null);
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */ new Object());
adPlaybackState =
addAdGroupToAdPlaybackState(