diff --git a/RELEASENOTES.md b/RELEASENOTES.md index df0978fa75..87b00b0e11 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -50,6 +50,10 @@ * Remove deprecated `AdsMediaSource` constructors. Listen for media source events using `AdsMediaSource.addEventListener`, and ad interaction events by adding a listener when building `ImaAdsLoader`. + * Allow apps to register playback-related obstructing views that are on top of + their ad display containers via `AdsLoader.AdViewProvider`. `PlayerView` + implements this interface and will register its control view. This makes it + possible for ad loading SDKs to calculate ad viewability accurately. * Remove `player` and `isTopLevelSource` parameters from `MediaSource.prepare`. * VP9 extension: Remove RGB output mode and libyuv dependency, and switch to surface YUV output as the default. Remove constructor parameters `scaleToFit` 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 97c3299a4a..05c804c7a8 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 5284183a90..12fc5eb06f 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 @@ -26,9 +26,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; @@ -146,7 +144,6 @@ public class PlayerActivity extends Activity private AdsLoader adsLoader; private Uri loadedAdTagUri; - private ViewGroup adUiViewGroup; // Activity lifecycle @@ -464,7 +461,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); List offlineStreamKeys = getOfflineStreamKeys(uri); @@ -584,9 +580,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 = @@ -601,7 +594,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 4bdec23804..3bfb9aab7b 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 @@ -22,6 +22,7 @@ import android.os.SystemClock; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import android.view.View; import android.view.ViewGroup; import com.google.ads.interactivemedia.v3.api.Ad; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; @@ -79,6 +80,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, @@ -488,13 +493,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); } @@ -506,14 +527,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) { @@ -563,7 +584,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; @@ -571,7 +592,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) { @@ -585,7 +611,7 @@ public final class ImaAdsLoader startAdPlayback(); } else { // Ads haven't loaded yet, so request them. - requestAds(adUiViewGroup); + requestAds(adViewGroup); } } @@ -600,6 +626,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 75c9c789cf..76fe7f8735 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 ProgressiveMediaSource.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;