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 @@
`androidx.media2.session.MediaSession`.
* Cast extension: Implement playlist API and deprecate the old queue
manipulation API.
* IMA extension: Upgrade to IMA SDK 3.19.4, bringing in a fix for setting the
media load timeout
([#7170](https://github.com/google/ExoPlayer/issues/7170)).
* IMA extension:
* Upgrade to IMA SDK 3.19.4, bringing in a fix for setting the
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.
* Add Guava dependency.
......
......@@ -29,6 +29,7 @@ dependencies {
implementation project(modulePrefix + 'library-core')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
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
androidTestImplementation project(modulePrefix + 'testutils')
androidTestImplementation 'androidx.multidex:multidex:' + androidxMultidexVersion
......
......@@ -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.AdsRenderingSettings;
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.player.AdMediaInfo;
import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider;
......@@ -108,6 +109,7 @@ public final class ImaAdsLoaderTest {
@Mock private AdsRequest mockAdsRequest;
@Mock private AdsManagerLoadedEvent mockAdsManagerLoadedEvent;
@Mock private com.google.ads.interactivemedia.v3.api.AdsLoader mockAdsLoader;
@Mock private FriendlyObstruction mockFriendlyObstruction;
@Mock private ImaFactory mockImaFactory;
@Mock private AdPodInfo mockAdPodInfo;
@Mock private Ad mockPrerollSingleAd;
......@@ -162,8 +164,8 @@ public final class ImaAdsLoaderTest {
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.start(adsLoaderListener, adViewProvider);
verify(mockAdDisplayContainer, atLeastOnce()).setAdContainer(adViewGroup);
verify(mockAdDisplayContainer, atLeastOnce()).registerVideoControlsOverlay(adOverlayView);
verify(mockImaFactory, atLeastOnce()).createAdDisplayContainer(adViewGroup, videoAdPlayer);
verify(mockAdDisplayContainer).registerFriendlyObstruction(mockFriendlyObstruction);
}
@Test
......@@ -637,8 +639,8 @@ public final class ImaAdsLoaderTest {
imaAdsLoader.stop();
InOrder inOrder = inOrder(mockAdDisplayContainer);
inOrder.verify(mockAdDisplayContainer).registerVideoControlsOverlay(adOverlayView);
inOrder.verify(mockAdDisplayContainer).unregisterAllVideoControlsOverlays();
inOrder.verify(mockAdDisplayContainer).registerFriendlyObstruction(mockFriendlyObstruction);
inOrder.verify(mockAdDisplayContainer).unregisterAllFriendlyObstructions();
}
@Test
......@@ -758,16 +760,16 @@ public final class ImaAdsLoaderTest {
doAnswer(
invocation -> {
videoAdPlayer = invocation.getArgument(0);
return null;
videoAdPlayer = invocation.getArgument(1);
return mockAdDisplayContainer;
})
.when(mockAdDisplayContainer)
.setPlayer(any());
when(mockImaFactory.createAdDisplayContainer()).thenReturn(mockAdDisplayContainer);
.when(mockImaFactory)
.createAdDisplayContainer(any(), any());
when(mockImaFactory.createAdsRenderingSettings()).thenReturn(mockAdsRenderingSettings);
when(mockImaFactory.createAdsRequest()).thenReturn(mockAdsRequest);
when(mockImaFactory.createAdsLoader(any(), any(), any())).thenReturn(mockAdsLoader);
when(mockImaFactory.createFriendlyObstruction(any(), any(), any()))
.thenReturn(mockFriendlyObstruction);
when(mockAdPodInfo.getPodIndex()).thenReturn(0);
when(mockAdPodInfo.getTotalAds()).thenReturn(1);
......
......@@ -17,12 +17,18 @@ package com.google.android.exoplayer2.source.ads;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.common.collect.ImmutableList;
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}.
......@@ -70,23 +76,90 @@ public interface AdsLoader {
default void onAdTapped() {}
}
/** Provides views for the ad UI. */
/** Provides information about views for the ad playback UI. */
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();
/** @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
* for controlling playback and should be excluded from ad viewability measurements by the
* {@link AdsLoader} (if it supports this).
* Returns a list of {@link OverlayInfo} instances describing views that are 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).
*
* <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
* 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();
@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.
......
......@@ -68,6 +68,7 @@ import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView;
import com.google.android.exoplayer2.video.VideoListener;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -1258,15 +1259,20 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider
}
@Override
public View[] getAdOverlayViews() {
ArrayList<View> overlayViews = new ArrayList<>();
public List<AdsLoader.OverlayInfo> getAdOverlayInfos() {
List<AdsLoader.OverlayInfo> overlayViews = new ArrayList<>();
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) {
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.
......
......@@ -67,6 +67,7 @@ import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView;
import com.google.android.exoplayer2.video.VideoListener;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -1248,15 +1249,20 @@ public class StyledPlayerView extends FrameLayout implements AdsLoader.AdViewPro
}
@Override
public View[] getAdOverlayViews() {
ArrayList<View> overlayViews = new ArrayList<>();
public List<AdsLoader.OverlayInfo> getAdOverlayInfos() {
List<AdsLoader.OverlayInfo> overlayViews = new ArrayList<>();
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) {
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.
......
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