mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
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
This commit is contained in:
parent
bcbe310681
commit
3214851fbb
7 changed files with 233 additions and 82 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -40,11 +40,14 @@ import com.google.ads.interactivemedia.v3.api.AdEvent;
|
|||
import com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener;
|
||||
import com.google.ads.interactivemedia.v3.api.AdEvent.AdEventType;
|
||||
import com.google.ads.interactivemedia.v3.api.AdPodInfo;
|
||||
import com.google.ads.interactivemedia.v3.api.AdsLoader;
|
||||
import com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener;
|
||||
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.FriendlyObstructionPurpose;
|
||||
import com.google.ads.interactivemedia.v3.api.ImaSdkFactory;
|
||||
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
|
||||
import com.google.ads.interactivemedia.v3.api.UiElement;
|
||||
|
|
@ -58,7 +61,6 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
|||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
|
||||
import com.google.android.exoplayer2.source.ads.AdsLoader;
|
||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
|
|
@ -77,19 +79,25 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* {@link AdsLoader} using the IMA SDK. All methods must be called on the main thread.
|
||||
* {@link com.google.android.exoplayer2.source.ads.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()}.
|
||||
*
|
||||
* <p>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()}.
|
||||
* <p>The IMA SDK can report obstructions to the ad view for accurate viewability measurement. This
|
||||
* means that any overlay views that obstruct the ad overlay but are essential for playback need to
|
||||
* be registered via the {@link AdViewProvider} passed to the {@link
|
||||
* com.google.android.exoplayer2.source.ads.AdsMediaSource}. See the <a
|
||||
* href="https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/omsdk">
|
||||
* IMA SDK Open Measurement documentation</a> for more information.
|
||||
*/
|
||||
public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
||||
public final class ImaAdsLoader
|
||||
implements Player.EventListener, com.google.android.exoplayer2.source.ads.AdsLoader {
|
||||
|
||||
static {
|
||||
ExoPlayerLibraryInfo.registerModule("goog.exo.ima");
|
||||
|
|
@ -329,7 +337,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
* Interval at which ad progress updates are provided to the IMA SDK, in milliseconds. 100 ms is
|
||||
* the interval recommended by the IMA documentation.
|
||||
*
|
||||
* @see com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer.VideoAdPlayerCallback
|
||||
* @see VideoAdPlayer.VideoAdPlayerCallback
|
||||
*/
|
||||
private static final int AD_PROGRESS_UPDATE_INTERVAL_MS = 100;
|
||||
|
||||
|
|
@ -370,6 +378,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
*/
|
||||
private static final int IMA_AD_STATE_PAUSED = 2;
|
||||
|
||||
private final Context context;
|
||||
@Nullable private final Uri adTagUri;
|
||||
@Nullable private final String adsResponse;
|
||||
private final long adPreloadTimeoutMs;
|
||||
|
|
@ -381,15 +390,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
@Nullable private final Set<UiElement> adUiElements;
|
||||
@Nullable private final AdEventListener adEventListener;
|
||||
private final ImaFactory imaFactory;
|
||||
private final ImaSdkSettings imaSdkSettings;
|
||||
private final Timeline.Period period;
|
||||
private final Handler handler;
|
||||
private final ComponentListener componentListener;
|
||||
private final List<VideoAdPlayer.VideoAdPlayerCallback> adCallbacks;
|
||||
private final AdDisplayContainer adDisplayContainer;
|
||||
private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader;
|
||||
private final Runnable updateAdProgressRunnable;
|
||||
private final Map<AdMediaInfo, AdInfo> adInfoByAdMediaInfo;
|
||||
|
||||
private @MonotonicNonNull AdDisplayContainer adDisplayContainer;
|
||||
private @MonotonicNonNull AdsLoader adsLoader;
|
||||
private boolean wasSetPlayerCalled;
|
||||
@Nullable private Player nextPlayer;
|
||||
@Nullable private Object pendingAdRequestContext;
|
||||
|
|
@ -418,10 +428,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
@Nullable private AdMediaInfo imaAdMediaInfo;
|
||||
/** The current ad info, or {@code null} if in state {@link #IMA_AD_STATE_NONE}. */
|
||||
@Nullable private AdInfo imaAdInfo;
|
||||
/**
|
||||
* Whether {@link com.google.ads.interactivemedia.v3.api.AdsLoader#contentComplete()} has been
|
||||
* called since starting ad playback.
|
||||
*/
|
||||
/** Whether IMA has been notified that playback of content has finished. */
|
||||
private boolean sentContentComplete;
|
||||
|
||||
// Fields tracking the player/loader state.
|
||||
|
|
@ -508,6 +515,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
@Nullable AdEventListener adEventListener,
|
||||
ImaFactory imaFactory) {
|
||||
checkArgument(adTagUri != null || adsResponse != null);
|
||||
this.context = context.getApplicationContext();
|
||||
this.adTagUri = adTagUri;
|
||||
this.adsResponse = adsResponse;
|
||||
this.adPreloadTimeoutMs = adPreloadTimeoutMs;
|
||||
|
|
@ -527,17 +535,11 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
}
|
||||
imaSdkSettings.setPlayerType(IMA_SDK_SETTINGS_PLAYER_TYPE);
|
||||
imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION);
|
||||
this.imaSdkSettings = imaSdkSettings;
|
||||
period = new Timeline.Period();
|
||||
handler = Util.createHandler(getImaLooper(), /* callback= */ null);
|
||||
componentListener = new ComponentListener();
|
||||
adCallbacks = new ArrayList<>(/* initialCapacity= */ 1);
|
||||
adDisplayContainer = imaFactory.createAdDisplayContainer();
|
||||
adDisplayContainer.setPlayer(/* videoAdPlayer= */ componentListener);
|
||||
adsLoader =
|
||||
imaFactory.createAdsLoader(
|
||||
context.getApplicationContext(), imaSdkSettings, adDisplayContainer);
|
||||
adsLoader.addAdErrorListener(componentListener);
|
||||
adsLoader.addAdsLoadedListener(componentListener);
|
||||
updateAdProgressRunnable = this::updateAdProgress;
|
||||
adInfoByAdMediaInfo = new HashMap<>();
|
||||
supportedMimeTypes = Collections.emptyList();
|
||||
|
|
@ -553,23 +555,26 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@code com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by this
|
||||
* instance.
|
||||
* Returns the underlying {@link AdsLoader} wrapped by this instance, or {@code null} if ads have
|
||||
* not been requested yet.
|
||||
*/
|
||||
public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() {
|
||||
@Nullable
|
||||
public AdsLoader getAdsLoader() {
|
||||
return adsLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link AdDisplayContainer} used by this loader.
|
||||
* Returns the {@link AdDisplayContainer} used by this loader, or {@code null} if ads have not
|
||||
* been requested yet.
|
||||
*
|
||||
* <p>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.
|
||||
* AdDisplayContainer#registerFriendlyObstruction(FriendlyObstruction)} 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 com.google.android.exoplayer2.source.ads.AdsLoader.AdViewProvider} when creating the
|
||||
* media source to benefit from automatic registration.
|
||||
*/
|
||||
@Nullable
|
||||
public AdDisplayContainer getAdDisplayContainer() {
|
||||
return adDisplayContainer;
|
||||
}
|
||||
|
|
@ -588,7 +593,11 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
// Ads have already been requested.
|
||||
return;
|
||||
}
|
||||
adDisplayContainer.setAdContainer(adViewGroup);
|
||||
adDisplayContainer =
|
||||
imaFactory.createAdDisplayContainer(adViewGroup, /* player= */ componentListener);
|
||||
adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
|
||||
adsLoader.addAdErrorListener(componentListener);
|
||||
adsLoader.addAdsLoadedListener(componentListener);
|
||||
AdsRequest request = imaFactory.createAdsRequest();
|
||||
if (adTagUri != null) {
|
||||
request.setAdTagUrl(adTagUri.toString());
|
||||
|
|
@ -604,7 +613,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
adsLoader.requestAds(request);
|
||||
}
|
||||
|
||||
// AdsLoader implementation.
|
||||
// com.google.android.exoplayer2.source.ads.AdsLoader implementation.
|
||||
|
||||
@Override
|
||||
public void setPlayer(@Nullable Player player) {
|
||||
|
|
@ -650,12 +659,6 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
lastVolumePercent = 0;
|
||||
lastAdProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
|
||||
lastContentProgress = VideoProgressUpdate.VIDEO_TIME_NOT_READY;
|
||||
ViewGroup adViewGroup = adViewProvider.getAdViewGroup();
|
||||
adDisplayContainer.setAdContainer(adViewGroup);
|
||||
View[] adOverlayViews = adViewProvider.getAdOverlayViews();
|
||||
for (View view : adOverlayViews) {
|
||||
adDisplayContainer.registerVideoControlsOverlay(view);
|
||||
}
|
||||
maybeNotifyPendingAdLoadError();
|
||||
if (hasAdPlaybackState) {
|
||||
// Pass the ad playback state to the player, and resume ads if necessary.
|
||||
|
|
@ -668,7 +671,16 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
updateAdPlaybackState();
|
||||
} else {
|
||||
// Ads haven't loaded yet, so request them.
|
||||
requestAds(adViewGroup);
|
||||
requestAds(adViewProvider.getAdViewGroup());
|
||||
}
|
||||
if (adDisplayContainer != null) {
|
||||
for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) {
|
||||
adDisplayContainer.registerFriendlyObstruction(
|
||||
imaFactory.createFriendlyObstruction(
|
||||
overlayInfo.view,
|
||||
getFriendlyObstructionPurpose(overlayInfo.purpose),
|
||||
overlayInfo.reasonDetail));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -687,7 +699,9 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
lastVolumePercent = getPlayerVolumePercent();
|
||||
lastAdProgress = getAdVideoProgressUpdate();
|
||||
lastContentProgress = getContentVideoProgressUpdate();
|
||||
adDisplayContainer.unregisterAllVideoControlsOverlays();
|
||||
if (adDisplayContainer != null) {
|
||||
adDisplayContainer.unregisterAllFriendlyObstructions();
|
||||
}
|
||||
player.removeListener(this);
|
||||
this.player = null;
|
||||
eventListener = null;
|
||||
|
|
@ -697,8 +711,10 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
public void release() {
|
||||
pendingAdRequestContext = null;
|
||||
destroyAdsManager();
|
||||
adsLoader.removeAdsLoadedListener(componentListener);
|
||||
adsLoader.removeAdErrorListener(componentListener);
|
||||
if (adsLoader != null) {
|
||||
adsLoader.removeAdsLoadedListener(componentListener);
|
||||
adsLoader.removeAdErrorListener(componentListener);
|
||||
}
|
||||
imaPausedContent = false;
|
||||
imaAdState = IMA_AD_STATE_NONE;
|
||||
imaAdMediaInfo = null;
|
||||
|
|
@ -1356,7 +1372,9 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
}
|
||||
|
||||
private void sendContentComplete() {
|
||||
adsLoader.contentComplete();
|
||||
for (int i = 0; i < adCallbacks.size(); i++) {
|
||||
adCallbacks.get(i).onContentComplete();
|
||||
}
|
||||
sentContentComplete = true;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "adsLoader.contentComplete");
|
||||
|
|
@ -1442,6 +1460,21 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
return "AdMediaInfo[" + adMediaInfo.getUrl() + (adInfo != null ? ", " + adInfo : "") + "]";
|
||||
}
|
||||
|
||||
private static FriendlyObstructionPurpose getFriendlyObstructionPurpose(
|
||||
@OverlayInfo.Purpose int purpose) {
|
||||
switch (purpose) {
|
||||
case OverlayInfo.PURPOSE_CONTROLS:
|
||||
return FriendlyObstructionPurpose.VIDEO_CONTROLS;
|
||||
case OverlayInfo.PURPOSE_CLOSE_AD:
|
||||
return FriendlyObstructionPurpose.CLOSE_AD;
|
||||
case OverlayInfo.PURPOSE_NOT_VISIBLE:
|
||||
return FriendlyObstructionPurpose.NOT_VISIBLE;
|
||||
case OverlayInfo.PURPOSE_OTHER:
|
||||
default:
|
||||
return FriendlyObstructionPurpose.OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
private static DataSpec getAdsDataSpec(@Nullable Uri adTagUri) {
|
||||
return new DataSpec(adTagUri != null ? adTagUri : Uri.EMPTY);
|
||||
}
|
||||
|
|
@ -1495,16 +1528,30 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
/** Factory for objects provided by the IMA SDK. */
|
||||
@VisibleForTesting
|
||||
/* package */ interface ImaFactory {
|
||||
/** @see ImaSdkSettings */
|
||||
/** Creates {@link ImaSdkSettings} for configuring the IMA SDK. */
|
||||
ImaSdkSettings createImaSdkSettings();
|
||||
/** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRenderingSettings() */
|
||||
/**
|
||||
* Creates {@link AdsRenderingSettings} for giving the {@link AdsManager} parameters that
|
||||
* control rendering of ads.
|
||||
*/
|
||||
AdsRenderingSettings createAdsRenderingSettings();
|
||||
/** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdDisplayContainer() */
|
||||
AdDisplayContainer createAdDisplayContainer();
|
||||
/** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */
|
||||
/**
|
||||
* Creates an {@link AdDisplayContainer} to hold the player for video ads, a container for
|
||||
* non-linear ads, and slots for companion ads.
|
||||
*/
|
||||
AdDisplayContainer createAdDisplayContainer(ViewGroup container, VideoAdPlayer player);
|
||||
/**
|
||||
* Creates a {@link FriendlyObstruction} to describe an obstruction considered "friendly" for
|
||||
* viewability measurement purposes.
|
||||
*/
|
||||
FriendlyObstruction createFriendlyObstruction(
|
||||
View view,
|
||||
FriendlyObstructionPurpose friendlyObstructionPurpose,
|
||||
@Nullable String reasonDetail);
|
||||
/** Creates an {@link AdsRequest} to contain the data used to request ads. */
|
||||
AdsRequest createAdsRequest();
|
||||
/** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */
|
||||
com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
|
||||
/** Creates an {@link AdsLoader} for requesting ads using the specified settings. */
|
||||
AdsLoader createAdsLoader(
|
||||
Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer);
|
||||
}
|
||||
|
||||
|
|
@ -1515,7 +1562,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
AdErrorListener,
|
||||
VideoAdPlayer {
|
||||
|
||||
// com.google.ads.interactivemedia.v3.api.AdsLoader.AdsLoadedListener implementation.
|
||||
// AdsLoader.AdsLoadedListener implementation.
|
||||
|
||||
@Override
|
||||
public void onAdsManagerLoaded(AdsManagerLoadedEvent adsManagerLoadedEvent) {
|
||||
|
|
@ -1724,8 +1771,20 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AdDisplayContainer createAdDisplayContainer() {
|
||||
return ImaSdkFactory.getInstance().createAdDisplayContainer();
|
||||
public AdDisplayContainer createAdDisplayContainer(ViewGroup container, VideoAdPlayer player) {
|
||||
return ImaSdkFactory.createAdDisplayContainer(container, player);
|
||||
}
|
||||
|
||||
// The reasonDetail parameter to createFriendlyObstruction is annotated @Nullable but the
|
||||
// annotation is not kept in the obfuscated dependency.
|
||||
@SuppressWarnings("nullness:argument.type.incompatible")
|
||||
@Override
|
||||
public FriendlyObstruction createFriendlyObstruction(
|
||||
View view,
|
||||
FriendlyObstructionPurpose friendlyObstructionPurpose,
|
||||
@Nullable String reasonDetail) {
|
||||
return ImaSdkFactory.getInstance()
|
||||
.createFriendlyObstruction(view, friendlyObstructionPurpose, reasonDetail);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1734,7 +1793,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
|||
}
|
||||
|
||||
@Override
|
||||
public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
|
||||
public AdsLoader createAdsLoader(
|
||||
Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) {
|
||||
return ImaSdkFactory.getInstance()
|
||||
.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue