diff --git a/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java b/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java
index 95c49c34b0..fb13073840 100644
--- a/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java
+++ b/demos/ima/src/main/java/com/google/android/exoplayer2/imademo/PlayerManager.java
@@ -64,10 +64,7 @@ import com.google.android.exoplayer2.util.Util;
// Compose the content media source into a new AdsMediaSource with both ads and content.
MediaSource mediaSourceWithAds =
new AdsMediaSource(
- contentMediaSource,
- /* adMediaSourceFactory= */ this,
- adsLoader,
- playerView.getOverlayFrameLayout());
+ contentMediaSource, /* adMediaSourceFactory= */ this, adsLoader, playerView);
// Prepare the player with the source.
player.seekTo(contentPosition);
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 31d6d332dd..483ab37369 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
@@ -27,9 +27,7 @@ import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.ViewGroup;
import android.widget.Button;
-import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -151,7 +149,6 @@ public class PlayerActivity extends Activity
private AdsLoader adsLoader;
private Uri loadedAdTagUri;
- private ViewGroup adUiViewGroup;
// Activity lifecycle
@@ -474,7 +471,6 @@ public class PlayerActivity extends Activity
return buildMediaSource(uri, null);
}
- @SuppressWarnings("unchecked")
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
@ContentType int type = Util.inferContentType(uri, overrideExtension);
switch (type) {
@@ -596,9 +592,6 @@ public class PlayerActivity extends Activity
.getConstructor(android.content.Context.class, android.net.Uri.class);
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
adsLoader = loaderConstructor.newInstance(this, adTagUri);
- adUiViewGroup = new FrameLayout(this);
- // The demo app has a non-null overlay frame layout.
- playerView.getOverlayFrameLayout().addView(adUiViewGroup);
}
adsLoader.setPlayer(player);
AdsMediaSource.MediaSourceFactory adMediaSourceFactory =
@@ -613,7 +606,7 @@ public class PlayerActivity extends Activity
return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER};
}
};
- return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, adUiViewGroup);
+ return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView);
} catch (ClassNotFoundException e) {
// IMA extension not loaded.
return null;
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 ef50538930..85441ce905 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
@@ -21,6 +21,7 @@ import android.os.Looper;
import android.os.SystemClock;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
+import android.view.View;
import android.view.ViewGroup;
import com.google.ads.interactivemedia.v3.api.Ad;
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
@@ -78,6 +79,10 @@ import java.util.Set;
*
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()}.
+ *
+ *
The IMA SDK can take into account video control overlay views when calculating ad viewability.
+ * For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} and {@link
+ * AdViewProvider#getAdOverlayViews()}.
*/
public final class ImaAdsLoader
implements Player.EventListener,
@@ -487,13 +492,29 @@ public final class ImaAdsLoader
return adsLoader;
}
+ /**
+ * Returns the {@link AdDisplayContainer} used by this loader.
+ *
+ *
Note: any video controls overlays registered via {@link
+ * AdDisplayContainer#registerVideoControlsOverlay(View)} will be unregistered automatically when
+ * the media source detaches from this instance. It is therefore necessary to re-register views
+ * each time the ads loader is reused. Alternatively, provide overlay views via the {@link
+ * AdsLoader.AdViewProvider} when creating the media source to benefit from automatic
+ * registration.
+ */
+ public AdDisplayContainer getAdDisplayContainer() {
+ return adDisplayContainer;
+ }
+
/**
* Sets the slots for displaying companion ads. Individual slots can be created using {@link
* ImaSdkFactory#createCompanionAdSlot()}.
*
* @param companionSlots Slots for displaying companion ads.
* @see AdDisplayContainer#setCompanionSlots(Collection)
+ * @deprecated Use {@code getAdDisplayContainer().setCompanionSlots(...)}.
*/
+ @Deprecated
public void setCompanionSlots(Collection companionSlots) {
adDisplayContainer.setCompanionSlots(companionSlots);
}
@@ -505,14 +526,14 @@ public final class ImaAdsLoader
* called, so it is only necessary to call this method if you want to request ads before preparing
* the player.
*
- * @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
+ * @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
*/
- public void requestAds(ViewGroup adUiViewGroup) {
+ public void requestAds(ViewGroup adViewGroup) {
if (adPlaybackState != null || adsManager != null || pendingAdRequestContext != null) {
// Ads have already been requested.
return;
}
- adDisplayContainer.setAdContainer(adUiViewGroup);
+ adDisplayContainer.setAdContainer(adViewGroup);
pendingAdRequestContext = new Object();
AdsRequest request = imaFactory.createAdsRequest();
if (adTagUri != null) {
@@ -562,7 +583,7 @@ public final class ImaAdsLoader
}
@Override
- public void start(EventListener eventListener, ViewGroup adUiViewGroup) {
+ public void start(EventListener eventListener, AdViewProvider adViewProvider) {
Assertions.checkNotNull(
nextPlayer, "Set player using adsLoader.setPlayer before preparing the player.");
player = nextPlayer;
@@ -570,7 +591,12 @@ public final class ImaAdsLoader
lastVolumePercentage = 0;
lastAdProgress = null;
lastContentProgress = null;
- adDisplayContainer.setAdContainer(adUiViewGroup);
+ ViewGroup adViewGroup = adViewProvider.getAdViewGroup();
+ adDisplayContainer.setAdContainer(adViewGroup);
+ View[] adOverlayViews = adViewProvider.getAdOverlayViews();
+ for (View view : adOverlayViews) {
+ adDisplayContainer.registerVideoControlsOverlay(view);
+ }
player.addListener(this);
maybeNotifyPendingAdLoadError();
if (adPlaybackState != null) {
@@ -584,7 +610,7 @@ public final class ImaAdsLoader
startAdPlayback();
} else {
// Ads haven't loaded yet, so request them.
- requestAds(adUiViewGroup);
+ requestAds(adViewGroup);
}
}
@@ -599,6 +625,7 @@ public final class ImaAdsLoader
lastVolumePercentage = getVolume();
lastAdProgress = getAdProgress();
lastContentProgress = getContentProgress();
+ adDisplayContainer.unregisterAllVideoControlsOverlays();
player.removeListener(this);
player = null;
eventListener = null;
diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
index b8024d6534..d20ccbd728 100644
--- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
+++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
@@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.testutil.StubExoPlayer;
+import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import java.util.ArrayList;
/** A fake player for testing content/ad playback. */
@@ -109,6 +110,11 @@ import java.util.ArrayList;
// ExoPlayer methods. Other methods are unsupported.
+ @Override
+ public AudioComponent getAudioComponent() {
+ return null;
+ }
+
@Override
public Looper getApplicationLooper() {
return Looper.getMainLooper();
@@ -134,6 +140,16 @@ import java.util.ArrayList;
return playWhenReady;
}
+ @Override
+ public int getRendererCount() {
+ return 0;
+ }
+
+ @Override
+ public TrackSelectionArray getCurrentTrackSelections() {
+ return new TrackSelectionArray();
+ }
+
@Override
public Timeline getCurrentTimeline() {
return timeline;
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 0253e7db13..dabae2de4b 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
@@ -17,11 +17,13 @@ package com.google.android.exoplayer2.ext.ima;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.net.Uri;
import android.support.annotation.Nullable;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.google.ads.interactivemedia.v3.api.Ad;
@@ -49,6 +51,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@@ -73,7 +76,9 @@ public class ImaAdsLoaderTest {
private @Mock AdDisplayContainer adDisplayContainer;
private @Mock AdsManager adsManager;
private SingletonImaFactory testImaFactory;
- private ViewGroup adUiViewGroup;
+ private ViewGroup adViewGroup;
+ private View adOverlayView;
+ private AdsLoader.AdViewProvider adViewProvider;
private TestAdsLoaderListener adsLoaderListener;
private FakePlayer fakeExoPlayer;
private ImaAdsLoader imaAdsLoader;
@@ -90,7 +95,20 @@ public class ImaAdsLoaderTest {
adDisplayContainer,
fakeAdsRequest,
fakeAdsLoader);
- adUiViewGroup = new FrameLayout(RuntimeEnvironment.application);
+ adViewGroup = new FrameLayout(RuntimeEnvironment.application);
+ adOverlayView = new View(RuntimeEnvironment.application);
+ adViewProvider =
+ new AdsLoader.AdViewProvider() {
+ @Override
+ public ViewGroup getAdViewGroup() {
+ return adViewGroup;
+ }
+
+ @Override
+ public View[] getAdOverlayViews() {
+ return new View[] {adOverlayView};
+ }
+ };
}
@After
@@ -111,15 +129,16 @@ public class ImaAdsLoaderTest {
@Test
public void testStart_setsAdUiViewGroup() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
- imaAdsLoader.start(adsLoaderListener, adUiViewGroup);
+ imaAdsLoader.start(adsLoaderListener, adViewProvider);
- verify(adDisplayContainer, atLeastOnce()).setAdContainer(adUiViewGroup);
+ verify(adDisplayContainer, atLeastOnce()).setAdContainer(adViewGroup);
+ verify(adDisplayContainer, atLeastOnce()).registerVideoControlsOverlay(adOverlayView);
}
@Test
public void testStart_updatesAdPlaybackState() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
- imaAdsLoader.start(adsLoaderListener, adUiViewGroup);
+ imaAdsLoader.start(adsLoaderListener, adViewProvider);
assertThat(adsLoaderListener.adPlaybackState)
.isEqualTo(
@@ -131,14 +150,14 @@ public class ImaAdsLoaderTest {
public void testStartAfterRelease() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.release();
- imaAdsLoader.start(adsLoaderListener, adUiViewGroup);
+ imaAdsLoader.start(adsLoaderListener, adViewProvider);
}
@Test
public void testStartAndCallbacksAfterRelease() {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.release();
- imaAdsLoader.start(adsLoaderListener, adUiViewGroup);
+ imaAdsLoader.start(adsLoaderListener, adViewProvider);
fakeExoPlayer.setPlayingContentPosition(/* position= */ 0);
fakeExoPlayer.setState(Player.STATE_READY, true);
@@ -146,7 +165,7 @@ public class ImaAdsLoaderTest {
// Note: we can't currently call getContentProgress/getAdProgress as a VerifyError is thrown
// when using Robolectric and accessing VideoProgressUpdate.VIDEO_TIME_NOT_READY, due to the IMA
// SDK being proguarded.
- imaAdsLoader.requestAds(adUiViewGroup);
+ imaAdsLoader.requestAds(adViewGroup);
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD));
imaAdsLoader.loadAd(TEST_URI.toString());
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD));
@@ -166,7 +185,7 @@ public class ImaAdsLoaderTest {
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
// Load the preroll ad.
- imaAdsLoader.start(adsLoaderListener, adUiViewGroup);
+ imaAdsLoader.start(adsLoaderListener, adViewProvider);
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD));
imaAdsLoader.loadAd(TEST_URI.toString());
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD));
@@ -201,6 +220,18 @@ public class ImaAdsLoaderTest {
.withAdResumePositionUs(/* adResumePositionUs= */ 0));
}
+ @Test
+ public void testStop_unregistersAllVideoControlOverlays() {
+ setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
+ imaAdsLoader.start(adsLoaderListener, adViewProvider);
+ imaAdsLoader.requestAds(adViewGroup);
+ imaAdsLoader.stop();
+
+ InOrder inOrder = inOrder(adDisplayContainer);
+ inOrder.verify(adDisplayContainer).registerVideoControlsOverlay(adOverlayView);
+ inOrder.verify(adDisplayContainer).unregisterAllVideoControlsOverlays();
+ }
+
private void setupPlayback(Timeline contentTimeline, long[][] adDurationsUs, Float[] cuePoints) {
fakeExoPlayer = new FakePlayer();
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline, adDurationsUs);
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 48ac79f227..b3054f69a0 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
@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.source.ads;
import android.support.annotation.Nullable;
+import android.view.View;
import android.view.ViewGroup;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
@@ -31,16 +32,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 #start(EventListener, ViewGroup)} will be called when the ads media source first
+ *
{@link #start(EventListener, AdViewProvider)} 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
+ * a later call to {@link #start(EventListener, AdViewProvider)}. 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 #start(EventListener, ViewGroup)} should invoke the same listener to
- * provide the existing playback state to the new player.
+ * implementation of {@link #start(EventListener, AdViewProvider)} should invoke the same listener
+ * to provide the existing playback state to the new player.
*/
public interface AdsLoader {
@@ -69,6 +70,25 @@ public interface AdsLoader {
default void onAdTapped() {}
}
+ /** Provides views for the ad UI. */
+ interface AdViewProvider {
+
+ /** Returns the {@link ViewGroup} on top of the player that will show any ad UI. */
+ ViewGroup getAdViewGroup();
+
+ /**
+ * Returns an array of views that are shown on top of the ad view group, but that are essential
+ * for controlling playback and should be excluded from ad viewability measurements by the
+ * {@link AdsLoader} (if it supports this).
+ *
+ *
Each view must be either a fully transparent overlay (for capturing touch events), or a
+ * small piece of transient UI that is essential to the user experience of playback (such as a
+ * button to pause/resume playback or a transient full-screen or cast button). For more
+ * information see the documentation for your ads loader.
+ */
+ View[] getAdOverlayViews();
+ }
+
// Methods called by the application.
/**
@@ -95,8 +115,8 @@ public interface AdsLoader {
/**
* 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}.
+ * #start(EventListener, AdViewProvider)}. 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}.
@@ -107,9 +127,9 @@ public interface AdsLoader {
* Starts using the ads loader for playback. Called on the main thread by {@link AdsMediaSource}.
*
* @param eventListener Listener for ads loader events.
- * @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
+ * @param adViewProvider Provider of views for the ad UI.
*/
- void start(EventListener eventListener, ViewGroup adUiViewGroup);
+ void start(EventListener eventListener, AdViewProvider adViewProvider);
/**
* Stops using the ads loader for playback and deregisters the event listener. Called on the main
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 e7a5d05173..c57ad6a223 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
@@ -20,7 +20,6 @@ import android.os.Handler;
import android.os.Looper;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
-import android.view.ViewGroup;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.CompositeMediaSource;
@@ -146,7 +145,7 @@ public final class AdsMediaSource extends CompositeMediaSource {
private final MediaSource contentMediaSource;
private final MediaSourceFactory adMediaSourceFactory;
private final AdsLoader adsLoader;
- private final ViewGroup adUiViewGroup;
+ private final AdsLoader.AdViewProvider adViewProvider;
private final Handler mainHandler;
private final Map> deferredMediaPeriodByAdMediaSource;
private final Timeline.Period period;
@@ -166,18 +165,18 @@ public final class AdsMediaSource extends CompositeMediaSource {
* @param contentMediaSource The {@link MediaSource} providing the content to play.
* @param dataSourceFactory Factory for data sources used to load ad media.
* @param adsLoader The loader for ads.
- * @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
+ * @param adViewProvider Provider of views for the ad UI.
*/
public AdsMediaSource(
MediaSource contentMediaSource,
DataSource.Factory dataSourceFactory,
AdsLoader adsLoader,
- ViewGroup adUiViewGroup) {
+ AdsLoader.AdViewProvider adViewProvider) {
this(
contentMediaSource,
new ExtractorMediaSource.Factory(dataSourceFactory),
adsLoader,
- adUiViewGroup);
+ adViewProvider);
}
/**
@@ -187,17 +186,17 @@ public final class AdsMediaSource extends CompositeMediaSource {
* @param contentMediaSource The {@link MediaSource} providing the content to play.
* @param adMediaSourceFactory Factory for media sources used to load ad media.
* @param adsLoader The loader for ads.
- * @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
+ * @param adViewProvider Provider of views for the ad UI.
*/
public AdsMediaSource(
MediaSource contentMediaSource,
MediaSourceFactory adMediaSourceFactory,
AdsLoader adsLoader,
- ViewGroup adUiViewGroup) {
+ AdsLoader.AdViewProvider adViewProvider) {
this.contentMediaSource = contentMediaSource;
this.adMediaSourceFactory = adMediaSourceFactory;
this.adsLoader = adsLoader;
- this.adUiViewGroup = adUiViewGroup;
+ this.adViewProvider = adViewProvider;
mainHandler = new Handler(Looper.getMainLooper());
deferredMediaPeriodByAdMediaSource = new HashMap<>();
period = new Timeline.Period();
@@ -218,7 +217,7 @@ public final class AdsMediaSource extends CompositeMediaSource {
ComponentListener componentListener = new ComponentListener();
this.componentListener = componentListener;
prepareChildSource(DUMMY_CONTENT_MEDIA_PERIOD_ID, contentMediaSource);
- mainHandler.post(() -> adsLoader.start(componentListener, adUiViewGroup));
+ mainHandler.post(() -> adsLoader.start(componentListener, adViewProvider));
}
@Override
diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
index 8871767f49..00ffaa8057 100644
--- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
+++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java
@@ -54,6 +54,7 @@ import com.google.android.exoplayer2.Player.VideoComponent;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.TrackSelection;
@@ -244,7 +245,7 @@ import java.util.List;
* PlayerView. This will cause the specified layout to be inflated instead of {@code
* exo_player_view.xml} for only the instance on which the attribute is set.
*/
-public class PlayerView extends FrameLayout {
+public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider {
// LINT.IfChange
/**
@@ -283,7 +284,7 @@ public class PlayerView extends FrameLayout {
private final SubtitleView subtitleView;
@Nullable private final View bufferingView;
@Nullable private final TextView errorMessageView;
- private final PlayerControlView controller;
+ @Nullable private final PlayerControlView controller;
private final ComponentListener componentListener;
@Nullable private final FrameLayout adOverlayFrameLayout;
@Nullable private final FrameLayout overlayFrameLayout;
@@ -1106,6 +1107,21 @@ public class PlayerView extends FrameLayout {
}
}
+ // AdsLoader.AdViewProvider implementation.
+
+ @Override
+ public ViewGroup getAdViewGroup() {
+ return Assertions.checkNotNull(
+ adOverlayFrameLayout, "exo_ad_overlay must be present for ad playback");
+ }
+
+ @Override
+ public View[] getAdOverlayViews() {
+ return controller != null ? new View[] {controller} : new View[0];
+ }
+
+ // Internal methods.
+
private boolean toggleControllerVisibility() {
if (!useController || player == null) {
return false;