Set Player separately in AdsLoader interface.

Passing the player through MediaSource.prepare is only needed for the AdsLoader
and complicates other usages of MediaSource. Providing the player directly to
the AdsLoader is also in line with the usage pattern of PlayerView and other
components.

Also rename methods to start/stop to better reflect their usage.

PiperOrigin-RevId: 227682112
This commit is contained in:
tonihei 2019-01-03 14:54:22 +00:00 committed by Andrew Lewis
parent de39925ce9
commit caca14c5f9
5 changed files with 81 additions and 44 deletions

View File

@ -534,6 +534,9 @@ public class PlayerActivity extends Activity
mediaSource = null; mediaSource = null;
trackSelector = null; trackSelector = null;
} }
if (adsLoader != null) {
adsLoader.setPlayer(null);
}
releaseMediaDrm(); releaseMediaDrm();
} }
@ -597,6 +600,7 @@ public class PlayerActivity extends Activity
// The demo app has a non-null overlay frame layout. // The demo app has a non-null overlay frame layout.
playerView.getOverlayFrameLayout().addView(adUiViewGroup); playerView.getOverlayFrameLayout().addView(adUiViewGroup);
} }
adsLoader.setPlayer(player);
AdsMediaSource.MediaSourceFactory adMediaSourceFactory = AdsMediaSource.MediaSourceFactory adMediaSourceFactory =
new AdsMediaSource.MediaSourceFactory() { new AdsMediaSource.MediaSourceFactory() {
@Override @Override

View File

@ -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.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
@ -73,7 +72,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.
*
* <p>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 public final class ImaAdsLoader
implements Player.EventListener, implements Player.EventListener,
AdsLoader, AdsLoader,
@ -92,9 +97,9 @@ public final class ImaAdsLoader
private final Context context; private final Context context;
private @Nullable ImaSdkSettings imaSdkSettings; @Nullable private ImaSdkSettings imaSdkSettings;
private @Nullable AdEventListener adEventListener; @Nullable private AdEventListener adEventListener;
private @Nullable Set<UiElement> adUiElements; @Nullable private Set<UiElement> adUiElements;
private int vastLoadTimeoutMs; private int vastLoadTimeoutMs;
private int mediaLoadTimeoutMs; private int mediaLoadTimeoutMs;
private int mediaBitrate; private int mediaBitrate;
@ -316,10 +321,11 @@ public final class ImaAdsLoader
private final AdDisplayContainer adDisplayContainer; private final AdDisplayContainer adDisplayContainer;
private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader; private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader;
@Nullable private Player nextPlayer;
private Object pendingAdRequestContext; private Object pendingAdRequestContext;
private List<String> supportedMimeTypes; private List<String> supportedMimeTypes;
private EventListener eventListener; @Nullable private EventListener eventListener;
private Player player; @Nullable private Player player;
private VideoProgressUpdate lastContentProgress; private VideoProgressUpdate lastContentProgress;
private VideoProgressUpdate lastAdProgress; private VideoProgressUpdate lastAdProgress;
private int lastVolumePercentage; private int lastVolumePercentage;
@ -525,6 +531,14 @@ public final class ImaAdsLoader
// AdsLoader implementation. // 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 @Override
public void setSupportedContentTypes(@C.ContentType int... contentTypes) { public void setSupportedContentTypes(@C.ContentType int... contentTypes) {
List<String> supportedMimeTypes = new ArrayList<>(); List<String> supportedMimeTypes = new ArrayList<>();
@ -549,9 +563,10 @@ public final class ImaAdsLoader
} }
@Override @Override
public void attachPlayer(ExoPlayer player, EventListener eventListener, ViewGroup adUiViewGroup) { public void start(EventListener eventListener, ViewGroup adUiViewGroup) {
Assertions.checkArgument(player.getApplicationLooper() == Looper.getMainLooper()); Assertions.checkNotNull(
this.player = player; nextPlayer, "Set player using adsLoader.setPlayer before preparing the player.");
player = nextPlayer;
this.eventListener = eventListener; this.eventListener = eventListener;
lastVolumePercentage = 0; lastVolumePercentage = 0;
lastAdProgress = null; lastAdProgress = null;
@ -575,7 +590,7 @@ public final class ImaAdsLoader
} }
@Override @Override
public void detachPlayer() { public void stop() {
if (adsManager != null && imaPausedContent) { if (adsManager != null && imaPausedContent) {
adPlaybackState = adPlaybackState =
adPlaybackState.withAdResumePositionUs( adPlaybackState.withAdResumePositionUs(

View File

@ -111,7 +111,7 @@ public class ImaAdsLoaderTest {
@Test @Test
public void testAttachPlayer_setsAdUiViewGroup() { public void testAttachPlayer_setsAdUiViewGroup() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); 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); verify(adDisplayContainer, atLeastOnce()).setAdContainer(adUiViewGroup);
} }
@ -119,7 +119,7 @@ public class ImaAdsLoaderTest {
@Test @Test
public void testAttachPlayer_updatesAdPlaybackState() { public void testAttachPlayer_updatesAdPlaybackState() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup); imaAdsLoader.start(adsLoaderListener, adUiViewGroup);
assertThat(adsLoaderListener.adPlaybackState) assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo( .isEqualTo(
@ -131,14 +131,14 @@ public class ImaAdsLoaderTest {
public void testAttachAfterRelease() { public void testAttachAfterRelease() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.release(); imaAdsLoader.release();
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup); imaAdsLoader.start(adsLoaderListener, adUiViewGroup);
} }
@Test @Test
public void testAttachAndCallbacksAfterRelease() { public void testAttachAndCallbacksAfterRelease() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.release(); imaAdsLoader.release();
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup); imaAdsLoader.start(adsLoaderListener, adUiViewGroup);
fakeExoPlayer.setPlayingContentPosition(/* position= */ 0); fakeExoPlayer.setPlayingContentPosition(/* position= */ 0);
fakeExoPlayer.setState(Player.STATE_READY, true); fakeExoPlayer.setState(Player.STATE_READY, true);
@ -166,7 +166,7 @@ public class ImaAdsLoaderTest {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS); setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
// Load the preroll ad. // Load the preroll ad.
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup); imaAdsLoader.start(adsLoaderListener, adUiViewGroup);
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD)); imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD));
imaAdsLoader.loadAd(TEST_URI.toString()); imaAdsLoader.loadAd(TEST_URI.toString());
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD)); imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD));
@ -210,6 +210,7 @@ public class ImaAdsLoaderTest {
.setImaFactory(testImaFactory) .setImaFactory(testImaFactory)
.setImaSdkSettings(imaSdkSettings) .setImaSdkSettings(imaSdkSettings)
.buildForAdTag(TEST_URI); .buildForAdTag(TEST_URI);
imaAdsLoader.setPlayer(fakeExoPlayer);
} }
private static AdEvent getAdEvent(AdEventType adEventType, @Nullable Ad ad) { private static AdEvent getAdEvent(AdEventType adEventType, @Nullable Ad ad) {

View File

@ -15,9 +15,10 @@
*/ */
package com.google.android.exoplayer2.source.ads; package com.google.android.exoplayer2.source.ads;
import android.support.annotation.Nullable;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.google.android.exoplayer2.C; 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.source.ads.AdsMediaSource.AdLoadException;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import java.io.IOException; 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 * 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). * becomes known (for example, when an ad media URI is available, or an ad has played to the end).
* *
* <p>{@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} will be called when the ads media * <p>{@link #start(EventListener, ViewGroup)} will be called when the ads media source first
* source first initializes, at which point the loader can request ads. If the player enters the * initializes, at which point the loader can request ads. If the player enters the background,
* background, {@link #detachPlayer()} will be called. Loaders should maintain any ad playback state * {@link #stop()} will be called. Loaders should maintain any ad playback state in preparation for
* in preparation for a later call to {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. If * a later call to {@link #start(EventListener, ViewGroup)}. If an ad is playing when the player is
* an ad is playing when the player is detached, update the ad playback state with the current * detached, update the ad playback state with the current playback position using {@link
* playback position using {@link AdPlaybackState#withAdResumePositionUs(long)}. * AdPlaybackState#withAdResumePositionUs(long)}.
* *
* <p>If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the * <p>If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the
* implementation of {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} should invoke the * implementation of {@link #start(EventListener, ViewGroup)} should invoke the same listener to
* same listener to provide the existing playback state to the new player. * provide the existing playback state to the new player.
*/ */
public interface AdsLoader { 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 * Sets the player that will play the loaded ads.
* {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. Subsequent calls may be ignored. *
* <p>This method must be called before the player is prepared with media using this ads loader.
*
* <p>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 * @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}. * {@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); void setSupportedContentTypes(@C.ContentType int... contentTypes);
/** /**
* Attaches a player that will play ads loaded using this instance. Called on the main thread by * Starts using the ads loader for playback. Called on the main thread by {@link AdsMediaSource}.
* {@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 eventListener Listener for ads loader events.
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI. * @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 * Stops using the ads loader for playback and deregisters the event listener. Called on the main
* {@link AdsMediaSource}. * thread by {@link AdsMediaSource}.
*/ */
void detachPlayer(); void stop();
/**
* Releases the loader. Called by the application on the main thread when the instance is no
* longer needed.
*/
void release();
/** /**
* Notifies the ads loader that the player was not able to prepare media for a given ad. * 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. * 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 adGroupIndex The index of the ad group.
* @param adIndexInAdGroup The index of the ad in the ad group. * @param adIndexInAdGroup The index of the ad in the ad group.

View File

@ -337,7 +337,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
final ComponentListener componentListener = new ComponentListener(); final ComponentListener componentListener = new ComponentListener();
this.componentListener = componentListener; this.componentListener = componentListener;
prepareChildSource(DUMMY_CONTENT_MEDIA_PERIOD_ID, contentMediaSource); prepareChildSource(DUMMY_CONTENT_MEDIA_PERIOD_ID, contentMediaSource);
mainHandler.post(() -> adsLoader.attachPlayer(player, componentListener, adUiViewGroup)); mainHandler.post(() -> adsLoader.start(componentListener, adUiViewGroup));
} }
@Override @Override
@ -406,7 +406,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
adPlaybackState = null; adPlaybackState = null;
adGroupMediaSources = new MediaSource[0][]; adGroupMediaSources = new MediaSource[0][];
adGroupTimelines = new Timeline[0][]; adGroupTimelines = new Timeline[0][];
mainHandler.post(adsLoader::detachPlayer); mainHandler.post(adsLoader::stop);
} }
@Override @Override