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 e95213b6ca..1f578194b9 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 @@ -115,6 +115,21 @@ import java.util.Objects; @UnstableApi public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSource { + /** A listener to be notified of stream events. */ + public interface StreamEventListener { + + /** + * Called when the {@linkplain StreamManager#getStreamId() stream ID} provided by the IMA SDK + * changed. + * + *

This method is called on the main thread. + * + * @param mediaItem The media item that the source resolved to the given stream ID. + * @param streamId The stream ID. + */ + void onStreamIdChanged(MediaItem mediaItem, String streamId); + } + /** * Factory for creating {@link ImaServerSideAdInsertionMediaSource * ImaServerSideAdInsertionMediaSources}. @@ -182,9 +197,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou adsLoader, imaAdsLoader, streamPlayer, - contentMediaSourceFactory, - adsLoader.configuration.applicationAdEventListener, - adsLoader.configuration.applicationAdErrorListener); + contentMediaSourceFactory); adsLoader.addMediaSourceResources(mediaSource, streamPlayer, imaAdsLoader); return mediaSource; } @@ -200,6 +213,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou private final AdViewProvider adViewProvider; @Nullable private ImaSdkSettings imaSdkSettings; + private StreamEventListener streamEventListener; @Nullable private AdEventListener adEventListener; @Nullable private AdErrorEvent.AdErrorListener adErrorListener; private State state; @@ -218,6 +232,10 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou companionAdSlots = ImmutableList.of(); state = new State(ImmutableMap.of()); focusSkipButtonWhenAvailable = true; + streamEventListener = + (mediaItem, streamId) -> { + // Do nothing. + }; } /** @@ -236,6 +254,18 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou return this; } + /** + * Sets the optional {@link StreamEventListener} that will be called for stream events. + * + * @param streamEventListener The stream event listener. + * @return This builder, for convenience. + */ + @CanIgnoreReturnValue + public AdsLoader.Builder setStreamEventListener(StreamEventListener streamEventListener) { + this.streamEventListener = streamEventListener; + return this; + } + /** * Sets the optional {@link AdEventListener} that will be passed to {@link * StreamManager#addAdEventListener(AdEventListener)} when the stream manager becomes @@ -318,6 +348,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou new ServerSideAdInsertionConfiguration( adViewProvider, imaSdkSettings, + streamEventListener, adEventListener, adErrorListener, companionAdSlots, @@ -505,6 +536,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou private final MediaSource.Factory contentMediaSourceFactory; private final AdsLoader adsLoader; private final com.google.ads.interactivemedia.v3.api.AdsLoader sdkAdsLoader; + private final StreamEventListener streamEventListener; @Nullable private final AdEventListener applicationAdEventListener; @Nullable private final AdErrorListener applicationAdErrorListener; private final boolean isLiveStream; @@ -517,6 +549,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou @Nullable private Loader loader; @Nullable private StreamManager streamManager; + @Nullable private String streamId; @Nullable private ServerSideAdInsertionMediaSource serverSideAdInsertionMediaSource; @Nullable private IOException loadError; @Nullable private Timeline contentTimeline; @@ -532,9 +565,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou AdsLoader adsLoader, com.google.ads.interactivemedia.v3.api.AdsLoader sdkAdsLoader, StreamPlayer streamPlayer, - MediaSource.Factory contentMediaSourceFactory, - @Nullable AdEventListener applicationAdEventListener, - @Nullable AdErrorListener applicationAdErrorListener) { + MediaSource.Factory contentMediaSourceFactory) { this.player = player; this.mediaItem = mediaItem; this.streamRequest = streamRequest; @@ -542,8 +573,9 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou this.sdkAdsLoader = sdkAdsLoader; this.streamPlayer = streamPlayer; this.contentMediaSourceFactory = contentMediaSourceFactory; - this.applicationAdEventListener = applicationAdEventListener; - this.applicationAdErrorListener = applicationAdErrorListener; + this.streamEventListener = adsLoader.configuration.streamEventListener; + this.applicationAdEventListener = adsLoader.configuration.applicationAdEventListener; + this.applicationAdErrorListener = adsLoader.configuration.applicationAdErrorListener; Assertions.checkArgument(player.getApplicationLooper() == Looper.getMainLooper()); mainHandler = new Handler(Looper.getMainLooper()); Uri streamRequestUri = checkNotNull(mediaItem.localConfiguration).uri; @@ -677,9 +709,15 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou } this.streamManager.removeAdEventListener(componentListener); this.streamManager.destroy(); + streamId = null; } this.streamManager = streamManager; if (streamManager != null) { + String newStreamId = streamManager.getStreamId(); + if (!Objects.equals(streamId, newStreamId)) { + streamId = newStreamId; + streamEventListener.onStreamIdChanged(getMediaItem(), newStreamId); + } streamManager.addAdEventListener(componentListener); if (applicationAdEventListener != null) { streamManager.addAdEventListener(applicationAdEventListener); 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 2cbb1b4963..5c4cf7a26a 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 @@ -174,6 +174,7 @@ import java.util.Set; public final AdViewProvider adViewProvider; public final ImaSdkSettings imaSdkSettings; + public final ImaServerSideAdInsertionMediaSource.StreamEventListener streamEventListener; @Nullable public final AdEvent.AdEventListener applicationAdEventListener; @Nullable public final AdErrorEvent.AdErrorListener applicationAdErrorListener; public final ImmutableList companionAdSlots; @@ -183,6 +184,7 @@ import java.util.Set; public ServerSideAdInsertionConfiguration( AdViewProvider adViewProvider, ImaSdkSettings imaSdkSettings, + ImaServerSideAdInsertionMediaSource.StreamEventListener streamEventListener, @Nullable AdEvent.AdEventListener applicationAdEventListener, @Nullable AdErrorEvent.AdErrorListener applicationAdErrorListener, List companionAdSlots, @@ -190,6 +192,7 @@ import java.util.Set; boolean debugModeEnabled) { this.imaSdkSettings = imaSdkSettings; this.adViewProvider = adViewProvider; + this.streamEventListener = streamEventListener; this.applicationAdEventListener = applicationAdEventListener; this.applicationAdErrorListener = applicationAdErrorListener; this.companionAdSlots = ImmutableList.copyOf(companionAdSlots);