diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index ffa9bafa4f..31d6d332dd 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -534,6 +534,9 @@ public class PlayerActivity extends Activity mediaSource = null; trackSelector = null; } + if (adsLoader != null) { + adsLoader.setPlayer(null); + } releaseMediaDrm(); } @@ -597,6 +600,7 @@ public class PlayerActivity extends Activity // The demo app has a non-null overlay frame layout. playerView.getOverlayFrameLayout().addView(adUiViewGroup); } + adsLoader.setPlayer(player); AdsMediaSource.MediaSourceFactory adMediaSourceFactory = new AdsMediaSource.MediaSourceFactory() { @Override diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 9b4b66125c..985408cb70 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -46,7 +46,6 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; @@ -73,7 +72,13 @@ import java.util.List; import java.util.Map; import java.util.Set; -/** Loads ads using the IMA SDK. All methods are called on the main thread. */ +/** + * {@link AdsLoader} using the IMA SDK. All methods must be called on the main thread. + * + *

The player instance that will play the loaded ads must be set before playback using {@link + * #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling + * {@link #release()}. + */ public final class ImaAdsLoader implements Player.EventListener, AdsLoader, @@ -92,9 +97,9 @@ public final class ImaAdsLoader private final Context context; - private @Nullable ImaSdkSettings imaSdkSettings; - private @Nullable AdEventListener adEventListener; - private @Nullable Set adUiElements; + @Nullable private ImaSdkSettings imaSdkSettings; + @Nullable private AdEventListener adEventListener; + @Nullable private Set adUiElements; private int vastLoadTimeoutMs; private int mediaLoadTimeoutMs; private int mediaBitrate; @@ -316,10 +321,11 @@ public final class ImaAdsLoader private final AdDisplayContainer adDisplayContainer; private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader; + @Nullable private Player nextPlayer; private Object pendingAdRequestContext; private List supportedMimeTypes; - private EventListener eventListener; - private Player player; + @Nullable private EventListener eventListener; + @Nullable private Player player; private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastAdProgress; private int lastVolumePercentage; @@ -525,6 +531,14 @@ public final class ImaAdsLoader // AdsLoader implementation. + @Override + public void setPlayer(@Nullable Player player) { + Assertions.checkState(Looper.getMainLooper() == Looper.myLooper()); + Assertions.checkState( + player == null || player.getApplicationLooper() == Looper.getMainLooper()); + nextPlayer = player; + } + @Override public void setSupportedContentTypes(@C.ContentType int... contentTypes) { List supportedMimeTypes = new ArrayList<>(); @@ -549,9 +563,10 @@ public final class ImaAdsLoader } @Override - public void attachPlayer(ExoPlayer player, EventListener eventListener, ViewGroup adUiViewGroup) { - Assertions.checkArgument(player.getApplicationLooper() == Looper.getMainLooper()); - this.player = player; + public void start(EventListener eventListener, ViewGroup adUiViewGroup) { + Assertions.checkNotNull( + nextPlayer, "Set player using adsLoader.setPlayer before preparing the player."); + player = nextPlayer; this.eventListener = eventListener; lastVolumePercentage = 0; lastAdProgress = null; @@ -575,7 +590,7 @@ public final class ImaAdsLoader } @Override - public void detachPlayer() { + public void stop() { if (adsManager != null && imaPausedContent) { adPlaybackState = adPlaybackState.withAdResumePositionUs( diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java index b0fe731480..0b097f26f0 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java @@ -111,7 +111,7 @@ public class ImaAdsLoaderTest { @Test public void testAttachPlayer_setsAdUiViewGroup() { setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); - imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup); + imaAdsLoader.start(adsLoaderListener, adUiViewGroup); verify(adDisplayContainer, atLeastOnce()).setAdContainer(adUiViewGroup); } @@ -119,7 +119,7 @@ public class ImaAdsLoaderTest { @Test public void testAttachPlayer_updatesAdPlaybackState() { setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); - imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup); + imaAdsLoader.start(adsLoaderListener, adUiViewGroup); assertThat(adsLoaderListener.adPlaybackState) .isEqualTo( @@ -131,14 +131,14 @@ public class ImaAdsLoaderTest { public void testAttachAfterRelease() { setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); imaAdsLoader.release(); - imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup); + imaAdsLoader.start(adsLoaderListener, adUiViewGroup); } @Test public void testAttachAndCallbacksAfterRelease() { setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); imaAdsLoader.release(); - imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup); + imaAdsLoader.start(adsLoaderListener, adUiViewGroup); fakeExoPlayer.setPlayingContentPosition(/* position= */ 0); fakeExoPlayer.setState(Player.STATE_READY, true); @@ -166,7 +166,7 @@ public class ImaAdsLoaderTest { setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); // Load the preroll ad. - imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup); + imaAdsLoader.start(adsLoaderListener, adUiViewGroup); imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD)); imaAdsLoader.loadAd(TEST_URI.toString()); imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD)); @@ -210,6 +210,7 @@ public class ImaAdsLoaderTest { .setImaFactory(testImaFactory) .setImaSdkSettings(imaSdkSettings) .buildForAdTag(TEST_URI); + imaAdsLoader.setPlayer(fakeExoPlayer); } private static AdEvent getAdEvent(AdEventType adEventType, @Nullable Ad ad) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java index f041542356..51de225414 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java @@ -15,9 +15,10 @@ */ package com.google.android.exoplayer2.source.ads; +import android.support.annotation.Nullable; import android.view.ViewGroup; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException; import com.google.android.exoplayer2.upstream.DataSpec; import java.io.IOException; @@ -30,16 +31,16 @@ import java.io.IOException; * with a new copy of the current {@link AdPlaybackState} whenever further information about ads * becomes known (for example, when an ad media URI is available, or an ad has played to the end). * - *

{@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} will be called when the ads media - * source first initializes, at which point the loader can request ads. If the player enters the - * background, {@link #detachPlayer()} will be called. Loaders should maintain any ad playback state - * in preparation for a later call to {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. If - * an ad is playing when the player is detached, update the ad playback state with the current - * playback position using {@link AdPlaybackState#withAdResumePositionUs(long)}. + *

{@link #start(EventListener, ViewGroup)} will be called when the ads media source first + * initializes, at which point the loader can request ads. If the player enters the background, + * {@link #stop()} will be called. Loaders should maintain any ad playback state in preparation for + * a later call to {@link #start(EventListener, ViewGroup)}. If an ad is playing when the player is + * detached, update the ad playback state with the current playback position using {@link + * AdPlaybackState#withAdResumePositionUs(long)}. * *

If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the - * implementation of {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} should invoke the - * same listener to provide the existing playback state to the new player. + * implementation of {@link #start(EventListener, ViewGroup)} should invoke the same listener to + * provide the existing playback state to the new player. */ public interface AdsLoader { @@ -75,9 +76,34 @@ public interface AdsLoader { } + // Methods called by the application. + /** - * Sets the supported content types for ad media. Must be called before the first call to - * {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. Subsequent calls may be ignored. + * Sets the player that will play the loaded ads. + * + *

This method must be called before the player is prepared with media using this ads loader. + * + *

This method must also be called on the main thread and only players which are accessed on + * the main thread are supported ({@code player.getApplicationLooper() == + * Looper.getMainLooper()}). + * + * @param player The player instance that will play the loaded ads. May be null to delete the + * reference to a previously set player. + */ + void setPlayer(@Nullable Player player); + + /** + * Releases the loader. Must be called by the application on the main thread when the instance is + * no longer needed. + */ + void release(); + + // Methods called by AdsMediaSource. + + /** + * Sets the supported content types for ad media. Must be called before the first call to {@link + * #start(EventListener, ViewGroup)}. Subsequent calls may be ignored. Called on the main thread + * by {@link AdsMediaSource}. * * @param contentTypes The supported content types for ad media. Each element must be one of * {@link C#TYPE_DASH}, {@link C#TYPE_HLS}, {@link C#TYPE_SS} and {@link C#TYPE_OTHER}. @@ -85,32 +111,23 @@ public interface AdsLoader { void setSupportedContentTypes(@C.ContentType int... contentTypes); /** - * Attaches a player that will play ads loaded using this instance. Called on the main thread by - * {@link AdsMediaSource}. + * Starts using the ads loader for playback. Called on the main thread by {@link AdsMediaSource}. * - * @param player The player instance that will play the loaded ads. Only players which are - * accessed on the main thread are supported ({@code player.getApplicationLooper() == - * Looper.getMainLooper()}). * @param eventListener Listener for ads loader events. * @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI. */ - void attachPlayer(ExoPlayer player, EventListener eventListener, ViewGroup adUiViewGroup); + void start(EventListener eventListener, ViewGroup adUiViewGroup); /** - * Detaches the attached player and event listener. Called on the main thread by - * {@link AdsMediaSource}. + * Stops using the ads loader for playback and deregisters the event listener. Called on the main + * thread by {@link AdsMediaSource}. */ - void detachPlayer(); - - /** - * Releases the loader. Called by the application on the main thread when the instance is no - * longer needed. - */ - void release(); + void stop(); /** * Notifies the ads loader that the player was not able to prepare media for a given ad. * Implementations should update the ad playback state as the specified ad has failed to load. + * Called on the main thread by {@link AdsMediaSource}. * * @param adGroupIndex The index of the ad group. * @param adIndexInAdGroup The index of the ad in the ad group. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 4bf661ddc0..a8ae3938af 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -337,7 +337,7 @@ public final class AdsMediaSource extends CompositeMediaSource { final ComponentListener componentListener = new ComponentListener(); this.componentListener = componentListener; prepareChildSource(DUMMY_CONTENT_MEDIA_PERIOD_ID, contentMediaSource); - mainHandler.post(() -> adsLoader.attachPlayer(player, componentListener, adUiViewGroup)); + mainHandler.post(() -> adsLoader.start(componentListener, adUiViewGroup)); } @Override @@ -406,7 +406,7 @@ public final class AdsMediaSource extends CompositeMediaSource { adPlaybackState = null; adGroupMediaSources = new MediaSource[0][]; adGroupTimelines = new Timeline[0][]; - mainHandler.post(adsLoader::detachPlayer); + mainHandler.post(adsLoader::stop); } @Override