Commit 3214851f by andrewlewis Committed by kim-vde

Migrate off deprecated IMA SDK APIs

AdDisplayContainer now takes the video ad player at construction time,
and obstructions are registered/unregistered via a new method. Also
'content complete' is now notified via ad callbacks rather than the
AdsLoader.

PiperOrigin-RevId: 320567666
parent bcbe3106
...@@ -239,9 +239,13 @@ ...@@ -239,9 +239,13 @@
`androidx.media2.session.MediaSession`. `androidx.media2.session.MediaSession`.
* Cast extension: Implement playlist API and deprecate the old queue * Cast extension: Implement playlist API and deprecate the old queue
manipulation API. manipulation API.
* IMA extension: Upgrade to IMA SDK 3.19.4, bringing in a fix for setting the * IMA extension:
media load timeout * Upgrade to IMA SDK 3.19.4, bringing in a fix for setting the
([#7170](https://github.com/google/ExoPlayer/issues/7170)). media load timeout
([#7170](https://github.com/google/ExoPlayer/issues/7170)).
* Migrate to new 'friendly obstruction' IMA SDK APIs, and allow apps to
register a purpose and detail reason for overlay views via
`AdsLoader.AdViewProvider`.
* Demo app: Retain previous position in list of samples. * Demo app: Retain previous position in list of samples.
* Add Guava dependency. * Add Guava dependency.
......
...@@ -29,6 +29,7 @@ dependencies { ...@@ -29,6 +29,7 @@ dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0' implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
androidTestImplementation project(modulePrefix + 'testutils') androidTestImplementation project(modulePrefix + 'testutils')
androidTestImplementation 'androidx.multidex:multidex:' + androidxMultidexVersion androidTestImplementation 'androidx.multidex:multidex:' + androidxMultidexVersion
......
...@@ -42,6 +42,7 @@ import com.google.ads.interactivemedia.v3.api.AdsManager; ...@@ -42,6 +42,7 @@ import com.google.ads.interactivemedia.v3.api.AdsManager;
import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent; import com.google.ads.interactivemedia.v3.api.AdsManagerLoadedEvent;
import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings; import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings;
import com.google.ads.interactivemedia.v3.api.AdsRequest; import com.google.ads.interactivemedia.v3.api.AdsRequest;
import com.google.ads.interactivemedia.v3.api.FriendlyObstruction;
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo; import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo;
import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider; import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider;
...@@ -108,6 +109,7 @@ public final class ImaAdsLoaderTest { ...@@ -108,6 +109,7 @@ public final class ImaAdsLoaderTest {
@Mock private AdsRequest mockAdsRequest; @Mock private AdsRequest mockAdsRequest;
@Mock private AdsManagerLoadedEvent mockAdsManagerLoadedEvent; @Mock private AdsManagerLoadedEvent mockAdsManagerLoadedEvent;
@Mock private com.google.ads.interactivemedia.v3.api.AdsLoader mockAdsLoader; @Mock private com.google.ads.interactivemedia.v3.api.AdsLoader mockAdsLoader;
@Mock private FriendlyObstruction mockFriendlyObstruction;
@Mock private ImaFactory mockImaFactory; @Mock private ImaFactory mockImaFactory;
@Mock private AdPodInfo mockAdPodInfo; @Mock private AdPodInfo mockAdPodInfo;
@Mock private Ad mockPrerollSingleAd; @Mock private Ad mockPrerollSingleAd;
...@@ -162,8 +164,8 @@ public final class ImaAdsLoaderTest { ...@@ -162,8 +164,8 @@ public final class ImaAdsLoaderTest {
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS); setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(adsLoaderListener, adViewProvider); imaAdsLoader.start(adsLoaderListener, adViewProvider);
verify(mockAdDisplayContainer, atLeastOnce()).setAdContainer(adViewGroup); verify(mockImaFactory, atLeastOnce()).createAdDisplayContainer(adViewGroup, videoAdPlayer);
verify(mockAdDisplayContainer, atLeastOnce()).registerVideoControlsOverlay(adOverlayView); verify(mockAdDisplayContainer).registerFriendlyObstruction(mockFriendlyObstruction);
} }
@Test @Test
...@@ -637,8 +639,8 @@ public final class ImaAdsLoaderTest { ...@@ -637,8 +639,8 @@ public final class ImaAdsLoaderTest {
imaAdsLoader.stop(); imaAdsLoader.stop();
InOrder inOrder = inOrder(mockAdDisplayContainer); InOrder inOrder = inOrder(mockAdDisplayContainer);
inOrder.verify(mockAdDisplayContainer).registerVideoControlsOverlay(adOverlayView); inOrder.verify(mockAdDisplayContainer).registerFriendlyObstruction(mockFriendlyObstruction);
inOrder.verify(mockAdDisplayContainer).unregisterAllVideoControlsOverlays(); inOrder.verify(mockAdDisplayContainer).unregisterAllFriendlyObstructions();
} }
@Test @Test
...@@ -758,16 +760,16 @@ public final class ImaAdsLoaderTest { ...@@ -758,16 +760,16 @@ public final class ImaAdsLoaderTest {
doAnswer( doAnswer(
invocation -> { invocation -> {
videoAdPlayer = invocation.getArgument(0); videoAdPlayer = invocation.getArgument(1);
return null; return mockAdDisplayContainer;
}) })
.when(mockAdDisplayContainer) .when(mockImaFactory)
.setPlayer(any()); .createAdDisplayContainer(any(), any());
when(mockImaFactory.createAdDisplayContainer()).thenReturn(mockAdDisplayContainer);
when(mockImaFactory.createAdsRenderingSettings()).thenReturn(mockAdsRenderingSettings); when(mockImaFactory.createAdsRenderingSettings()).thenReturn(mockAdsRenderingSettings);
when(mockImaFactory.createAdsRequest()).thenReturn(mockAdsRequest); when(mockImaFactory.createAdsRequest()).thenReturn(mockAdsRequest);
when(mockImaFactory.createAdsLoader(any(), any(), any())).thenReturn(mockAdsLoader); when(mockImaFactory.createAdsLoader(any(), any(), any())).thenReturn(mockAdsLoader);
when(mockImaFactory.createFriendlyObstruction(any(), any(), any()))
.thenReturn(mockFriendlyObstruction);
when(mockAdPodInfo.getPodIndex()).thenReturn(0); when(mockAdPodInfo.getPodIndex()).thenReturn(0);
when(mockAdPodInfo.getTotalAds()).thenReturn(1); when(mockAdPodInfo.getTotalAds()).thenReturn(1);
......
...@@ -17,12 +17,18 @@ package com.google.android.exoplayer2.source.ads; ...@@ -17,12 +17,18 @@ package com.google.android.exoplayer2.source.ads;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player; 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 com.google.common.collect.ImmutableList;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/** /**
* Interface for loaders of ads, which can be used with {@link AdsMediaSource}. * Interface for loaders of ads, which can be used with {@link AdsMediaSource}.
...@@ -70,23 +76,90 @@ public interface AdsLoader { ...@@ -70,23 +76,90 @@ public interface AdsLoader {
default void onAdTapped() {} default void onAdTapped() {}
} }
/** Provides views for the ad UI. */ /** Provides information about views for the ad playback UI. */
interface AdViewProvider { interface AdViewProvider {
/** Returns the {@link ViewGroup} on top of the player that will show any ad UI. */ /**
* Returns the {@link ViewGroup} on top of the player that will show any ad UI. Any views on top
* of the returned view group must be described by {@link OverlayInfo OverlayInfos} returned by
* {@link #getAdOverlayInfos()}, for accurate viewability measurement.
*/
ViewGroup getAdViewGroup(); ViewGroup getAdViewGroup();
/** @deprecated Use {@link #getAdOverlayInfos()} instead. */
@Deprecated
default View[] getAdOverlayViews() {
return new View[0];
}
/** /**
* Returns an array of views that are shown on top of the ad view group, but that are essential * Returns a list of {@link OverlayInfo} instances describing views that are on top of the ad
* for controlling playback and should be excluded from ad viewability measurements by the * view group, but that are essential for controlling playback and should be excluded from ad
* {@link AdsLoader} (if it supports this). * viewability measurements by the {@link AdsLoader} (if it supports this).
* *
* <p>Each view must be either a fully transparent overlay (for capturing touch events), or a * <p>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 * 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 * button to pause/resume playback or a transient full-screen or cast button). For more
* information see the documentation for your ads loader. * information see the documentation for your ads loader.
*/ */
View[] getAdOverlayViews(); @SuppressWarnings("deprecation")
default List<OverlayInfo> getAdOverlayInfos() {
ImmutableList.Builder<OverlayInfo> listBuilder = new ImmutableList.Builder<>();
// Call through to deprecated version.
for (View view : getAdOverlayViews()) {
listBuilder.add(new OverlayInfo(view, OverlayInfo.PURPOSE_CONTROLS));
}
return listBuilder.build();
}
}
/** Provides information about an overlay view shown on top of an ad view group. */
final class OverlayInfo {
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({PURPOSE_CONTROLS, PURPOSE_CLOSE_AD, PURPOSE_OTHER, PURPOSE_NOT_VISIBLE})
public @interface Purpose {}
/** Purpose for playback controls overlaying the player. */
public static final int PURPOSE_CONTROLS = 0;
/** Purpose for ad close buttons overlaying the player. */
public static final int PURPOSE_CLOSE_AD = 1;
/** Purpose for other overlays. */
public static final int PURPOSE_OTHER = 2;
/** Purpose for overlays that are not visible. */
public static final int PURPOSE_NOT_VISIBLE = 3;
/** The overlay view. */
public final View view;
/** The purpose of the overlay view. */
@Purpose public final int purpose;
/** An optional, detailed reason that the overlay view is needed. */
@Nullable public final String reasonDetail;
/**
* Creates a new overlay info.
*
* @param view The view that is overlaying the player.
* @param purpose The purpose of the view.
*/
public OverlayInfo(View view, @Purpose int purpose) {
this(view, purpose, /* detailedReason= */ null);
}
/**
* Creates a new overlay info.
*
* @param view The view that is overlaying the player.
* @param purpose The purpose of the view.
* @param detailedReason An optional, detailed reason that the view is on top of the player. See
* the documentation for the {@link AdsLoader} implementation for more information on this
* string's formatting.
*/
public OverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) {
this.view = view;
this.purpose = purpose;
this.reasonDetail = detailedReason;
}
} }
// Methods called by the application. // Methods called by the application.
......
...@@ -68,6 +68,7 @@ import com.google.android.exoplayer2.util.RepeatModeUtil; ...@@ -68,6 +68,7 @@ import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView; import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView;
import com.google.android.exoplayer2.video.VideoListener; import com.google.android.exoplayer2.video.VideoListener;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -1258,15 +1259,20 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider ...@@ -1258,15 +1259,20 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
} }
@Override @Override
public View[] getAdOverlayViews() { public List<AdsLoader.OverlayInfo> getAdOverlayInfos() {
ArrayList<View> overlayViews = new ArrayList<>(); List<AdsLoader.OverlayInfo> overlayViews = new ArrayList<>();
if (overlayFrameLayout != null) { if (overlayFrameLayout != null) {
overlayViews.add(overlayFrameLayout); overlayViews.add(
new AdsLoader.OverlayInfo(
overlayFrameLayout,
AdsLoader.OverlayInfo.PURPOSE_NOT_VISIBLE,
/* detailedReason= */ "Transparent overlay does not impact viewability"));
} }
if (controller != null) { if (controller != null) {
overlayViews.add(controller); overlayViews.add(
new AdsLoader.OverlayInfo(controller, AdsLoader.OverlayInfo.PURPOSE_CONTROLS));
} }
return overlayViews.toArray(new View[0]); return ImmutableList.copyOf(overlayViews);
} }
// Internal methods. // Internal methods.
......
...@@ -67,6 +67,7 @@ import com.google.android.exoplayer2.util.RepeatModeUtil; ...@@ -67,6 +67,7 @@ import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView; import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView;
import com.google.android.exoplayer2.video.VideoListener; import com.google.android.exoplayer2.video.VideoListener;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -1248,15 +1249,20 @@ public class StyledPlayerView extends FrameLayout implements AdsLoader.AdViewPro ...@@ -1248,15 +1249,20 @@ public class StyledPlayerView extends FrameLayout implements AdsLoader.AdViewPro
} }
@Override @Override
public View[] getAdOverlayViews() { public List<AdsLoader.OverlayInfo> getAdOverlayInfos() {
ArrayList<View> overlayViews = new ArrayList<>(); List<AdsLoader.OverlayInfo> overlayViews = new ArrayList<>();
if (overlayFrameLayout != null) { if (overlayFrameLayout != null) {
overlayViews.add(overlayFrameLayout); overlayViews.add(
new AdsLoader.OverlayInfo(
overlayFrameLayout,
AdsLoader.OverlayInfo.PURPOSE_NOT_VISIBLE,
/* detailedReason= */ "Transparent overlay does not impact viewability"));
} }
if (controller != null) { if (controller != null) {
overlayViews.add(controller); overlayViews.add(
new AdsLoader.OverlayInfo(controller, AdsLoader.OverlayInfo.PURPOSE_CONTROLS));
} }
return overlayViews.toArray(new View[0]); return ImmutableList.copyOf(overlayViews);
} }
// Internal methods. // Internal methods.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment