diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3d9bfed2e4..730943f431 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -29,6 +29,9 @@ * Effect: * Muxers: * IMA extension: + * Promote API that is required for apps to play + [DAI ad streams](https://developers.google.com/ad-manager/dynamic-ad-insertion/full-service) + to stable. * Session: * Hide seekbar in the media notification for live streams by not setting the duration into the platform session metadata diff --git a/api.txt b/api.txt index b644792397..66a509138d 100644 --- a/api.txt +++ b/api.txt @@ -1388,6 +1388,29 @@ package androidx.media3.exoplayer.ima { method public androidx.media3.exoplayer.ima.ImaAdsLoader build(); } + public final class ImaServerSideAdInsertionMediaSource implements androidx.media3.exoplayer.source.MediaSource { + } + + public static final class ImaServerSideAdInsertionMediaSource.AdsLoader { + method public androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.State release(); + method public void setPlayer(androidx.media3.common.Player); + } + + public static final class ImaServerSideAdInsertionMediaSource.AdsLoader.Builder { + ctor public ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(android.content.Context, androidx.media3.common.AdViewProvider); + method public androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader build(); + method public androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.Builder setAdsLoaderState(androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.State); + } + + public static class ImaServerSideAdInsertionMediaSource.AdsLoader.State { + method public static androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.State fromBundle(android.os.Bundle); + method public android.os.Bundle toBundle(); + } + + public static final class ImaServerSideAdInsertionMediaSource.Factory implements androidx.media3.exoplayer.source.MediaSource.Factory { + ctor public ImaServerSideAdInsertionMediaSource.Factory(androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader, androidx.media3.exoplayer.source.MediaSource.Factory); + } + } package androidx.media3.exoplayer.source { @@ -1397,6 +1420,7 @@ package androidx.media3.exoplayer.source { method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory clearLocalAdInsertionComponents(); method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setDataSourceFactory(androidx.media3.datasource.DataSource.Factory); method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setLocalAdInsertionComponents(androidx.media3.exoplayer.source.ads.AdsLoader.Provider, androidx.media3.common.AdViewProvider); + method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setServerSideAdInsertionMediaSourceFactory(@Nullable androidx.media3.exoplayer.source.MediaSource.Factory); } public interface MediaSource { diff --git a/demos/main/src/main/java/androidx/media3/demo/main/PlayerActivity.java b/demos/main/src/main/java/androidx/media3/demo/main/PlayerActivity.java index 9e066bd94a..6be37323bd 100644 --- a/demos/main/src/main/java/androidx/media3/demo/main/PlayerActivity.java +++ b/demos/main/src/main/java/androidx/media3/demo/main/PlayerActivity.java @@ -93,11 +93,8 @@ public class PlayerActivity extends AppCompatActivity @Nullable private AdsLoader clientSideAdsLoader; - @OptIn(markerClass = UnstableApi.class) - @Nullable - private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader; + @Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader; - @OptIn(markerClass = UnstableApi.class) private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State serverSideAdsLoaderState; @@ -301,7 +298,7 @@ public class PlayerActivity extends AppCompatActivity return true; } - @OptIn(markerClass = UnstableApi.class) // SSAI configuration + @OptIn(markerClass = UnstableApi.class) // DRM configuration private MediaSource.Factory createMediaSourceFactory() { DefaultDrmSessionManagerProvider drmSessionManagerProvider = new DefaultDrmSessionManagerProvider(); @@ -334,7 +331,6 @@ public class PlayerActivity extends AppCompatActivity playerBuilder.setRenderersFactory(renderersFactory); } - @OptIn(markerClass = UnstableApi.class) private void configurePlayerWithServerSideAdsLoader() { serverSideAdsLoader.setPlayer(player); } @@ -403,7 +399,6 @@ public class PlayerActivity extends AppCompatActivity } } - @OptIn(markerClass = UnstableApi.class) private void releaseServerSideAdsLoader() { serverSideAdsLoaderState = serverSideAdsLoader.release(); serverSideAdsLoader = null; @@ -417,14 +412,12 @@ public class PlayerActivity extends AppCompatActivity } } - @OptIn(markerClass = UnstableApi.class) private void saveServerSideAdsLoaderState(Bundle outState) { if (serverSideAdsLoaderState != null) { outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle()); } } - @OptIn(markerClass = UnstableApi.class) private void restoreServerSideAdsLoaderState(Bundle savedInstanceState) { Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE); if (adsLoaderStateBundle != null) { @@ -513,7 +506,7 @@ public class PlayerActivity extends AppCompatActivity private class PlayerErrorMessageProvider implements ErrorMessageProvider { - @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) + @OptIn(markerClass = UnstableApi.class) // Using decoder exceptions @Override public Pair getErrorMessage(PlaybackException e) { String errorString = getString(R.string.error_generic); @@ -554,7 +547,7 @@ public class PlayerActivity extends AppCompatActivity return mediaItems; } - @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) + @OptIn(markerClass = UnstableApi.class) // Using Download API private static MediaItem maybeSetDownloadProperties( MediaItem item, @Nullable DownloadRequest downloadRequest) { if (downloadRequest == null) { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/BaseMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/BaseMediaSource.java index d4b456391e..493ec6e523 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/BaseMediaSource.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/BaseMediaSource.java @@ -202,6 +202,7 @@ public abstract class BaseMediaSource implements MediaSource { return !mediaSourceCallers.isEmpty(); } + @UnstableApi @Override public final void addEventListener(Handler handler, MediaSourceEventListener eventListener) { Assertions.checkNotNull(handler); @@ -209,11 +210,13 @@ public abstract class BaseMediaSource implements MediaSource { eventDispatcher.addEventListener(handler, eventListener); } + @UnstableApi @Override public final void removeEventListener(MediaSourceEventListener eventListener) { eventDispatcher.removeEventListener(eventListener); } + @UnstableApi @Override public final void addDrmEventListener(Handler handler, DrmSessionEventListener eventListener) { Assertions.checkNotNull(handler); @@ -221,11 +224,13 @@ public abstract class BaseMediaSource implements MediaSource { drmEventDispatcher.addEventListener(handler, eventListener); } + @UnstableApi @Override public final void removeDrmEventListener(DrmSessionEventListener eventListener) { drmEventDispatcher.removeEventListener(eventListener); } + @UnstableApi @SuppressWarnings("deprecation") // Overriding deprecated method to make it final. @Override public final void prepareSource( @@ -233,6 +238,7 @@ public abstract class BaseMediaSource implements MediaSource { prepareSource(caller, mediaTransferListener, PlayerId.UNSET); } + @UnstableApi @Override public final void prepareSource( MediaSourceCaller caller, @@ -253,6 +259,7 @@ public abstract class BaseMediaSource implements MediaSource { } } + @UnstableApi @Override public final void enable(MediaSourceCaller caller) { Assertions.checkNotNull(looper); @@ -263,6 +270,7 @@ public abstract class BaseMediaSource implements MediaSource { } } + @UnstableApi @Override public final void disable(MediaSourceCaller caller) { boolean wasEnabled = !enabledMediaSourceCallers.isEmpty(); @@ -272,6 +280,7 @@ public abstract class BaseMediaSource implements MediaSource { } } + @UnstableApi @Override public final void releaseSource(MediaSourceCaller caller) { mediaSourceCallers.remove(caller); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java index c582e9977a..94a668df8a 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java @@ -313,7 +313,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { * @return This factory, for convenience. */ @CanIgnoreReturnValue - @UnstableApi public DefaultMediaSourceFactory setServerSideAdInsertionMediaSourceFactory( @Nullable MediaSource.Factory serverSideAdInsertionMediaSourceFactory) { this.serverSideAdInsertionMediaSourceFactory = serverSideAdInsertionMediaSourceFactory; 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 e47ccf32ee..09e1ed6e5d 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 @@ -112,10 +112,10 @@ import java.util.Map; import java.util.Objects; /** MediaSource for IMA server side inserted ad streams. */ -@UnstableApi public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSource { /** A listener to be notified of stream events. */ + @UnstableApi public interface StreamEventListener { /** @@ -154,6 +154,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou this.contentMediaSourceFactory = contentMediaSourceFactory; } + @UnstableApi @CanIgnoreReturnValue @Override public MediaSource.Factory setLoadErrorHandlingPolicy( @@ -162,6 +163,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou return this; } + @UnstableApi @CanIgnoreReturnValue @Override public MediaSource.Factory setDrmSessionManagerProvider( @@ -170,11 +172,13 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou return this; } + @UnstableApi @Override public @C.ContentType int[] getSupportedTypes() { return contentMediaSourceFactory.getSupportedTypes(); } + @UnstableApi @Override public MediaSource createMediaSource(MediaItem mediaItem) { checkNotNull(mediaItem.localConfiguration); @@ -248,6 +252,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou * @param imaSdkSettings The {@link ImaSdkSettings}. * @return This builder, for convenience. */ + @UnstableApi @CanIgnoreReturnValue public AdsLoader.Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { this.imaSdkSettings = imaSdkSettings; @@ -260,6 +265,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou * @param streamEventListener The stream event listener. * @return This builder, for convenience. */ + @UnstableApi @CanIgnoreReturnValue public AdsLoader.Builder setStreamEventListener(StreamEventListener streamEventListener) { this.streamEventListener = streamEventListener; @@ -271,9 +277,14 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou * StreamManager#addAdEventListener(AdEventListener)} when the stream manager becomes * available. * + *

Note: This method can be considered a stable API as long as the {@link AdEventListener} + * is provided by the IMA library. We can't declare this method stable because we don't have + * the same guarantee from the library we depend on. + * * @param adEventListener The ad event listener. * @return This builder, for convenience. */ + @UnstableApi @CanIgnoreReturnValue public AdsLoader.Builder setAdEventListener(AdEventListener adEventListener) { this.adEventListener = adEventListener; @@ -285,9 +296,14 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou * StreamManager#addAdErrorListener(AdErrorEvent.AdErrorListener)} when the stream manager * becomes available. * + *

Note: This method can be considered a stable API as long as the {@link + * AdErrorEvent.AdErrorListener} is provided by the IMA library. We can't declare this method + * stable because we don't have the same guarantee from the library we depend on. + * * @param adErrorListener The {@link AdErrorEvent.AdErrorListener}. * @return This builder, for convenience. */ + @UnstableApi @CanIgnoreReturnValue public AdsLoader.Builder setAdErrorListener(AdErrorEvent.AdErrorListener adErrorListener) { this.adErrorListener = adErrorListener; @@ -301,6 +317,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou * @return This builder, for convenience. * @see AdDisplayContainer#setCompanionSlots(Collection) */ + @UnstableApi @CanIgnoreReturnValue public AdsLoader.Builder setCompanionAdSlots(Collection companionAdSlots) { this.companionAdSlots = ImmutableList.copyOf(companionAdSlots); @@ -330,6 +347,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou * @return This builder, for convenience. * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean) */ + @UnstableApi @CanIgnoreReturnValue public AdsLoader.Builder setFocusSkipButtonWhenAvailable( boolean focusSkipButtonWhenAvailable) { @@ -405,6 +423,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou * * @deprecated Use {@link #fromBundle} instead. */ + @UnstableApi @Deprecated @SuppressWarnings("deprecation") // Deprecated instance of deprecated class public static final Bundleable.Creator CREATOR = State::fromBundle; @@ -458,6 +477,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou * * @see StreamManager#focus() */ + @UnstableApi public void focusSkipButton() { if (player == null) { return; @@ -600,11 +620,13 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou adPlaybackState = adsLoader.getAdPlaybackState(adsId); } + @UnstableApi @Override public synchronized MediaItem getMediaItem() { return mediaItem; } + @UnstableApi @Override public boolean canUpdateMediaItem(MediaItem mediaItem) { MediaItem existingMediaItem = getMediaItem(); @@ -619,11 +641,13 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou && existingMediaItem.liveConfiguration.equals(mediaItem.liveConfiguration); } + @UnstableApi @Override public synchronized void updateMediaItem(MediaItem mediaItem) { this.mediaItem = mediaItem; } + @UnstableApi @Override public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { mainHandler.post(() -> assertSingleInstanceInPlaylist(checkNotNull(player))); @@ -646,6 +670,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou } } + @UnstableApi @Override protected void onChildSourceInfoRefreshed( Void childSourceId, MediaSource mediaSource, Timeline newTimeline) { @@ -662,17 +687,20 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou }); } + @UnstableApi @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return checkNotNull(serverSideAdInsertionMediaSource) .createPeriod(id, allocator, startPositionUs); } + @UnstableApi @Override public void releasePeriod(MediaPeriod mediaPeriod) { checkNotNull(serverSideAdInsertionMediaSource).releasePeriod(mediaPeriod); } + @UnstableApi @Override public void maybeThrowSourceInfoRefreshError() throws IOException { super.maybeThrowSourceInfoRefreshError(); @@ -683,6 +711,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou } } + @UnstableApi @Override protected void releaseSourceInternal() { super.releaseSourceInternal();