Add support for audio-only ad display containers
Issue: #7689 PiperOrigin-RevId: 325752377
This commit is contained in:
parent
f2866a4942
commit
acc8453628
@ -266,6 +266,10 @@
|
||||
* Migrate to new 'friendly obstruction' IMA SDK APIs, and allow apps to
|
||||
register a purpose and detail reason for overlay views via
|
||||
`AdsLoader.AdViewProvider`.
|
||||
* Add support for audio-only ads display containers by returning `null`
|
||||
from `AdsLoader.AdViewProvider.getAdViewGroup`, and allow skipping
|
||||
audio-only ads via `ImaAdsLoader.skipAd`
|
||||
([#7689](https://github.com/google/ExoPlayer/issues/7689)).
|
||||
* Add `ImaAdsLoader.Builder.setCompanionAdSlots` so it's possible to set
|
||||
companion ad slots without accessing the `AdDisplayContainer`.
|
||||
* Add missing notification of `VideoAdPlayerCallback.onLoaded`.
|
||||
|
@ -609,15 +609,21 @@ 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 adViewGroup 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, or {@code
|
||||
* null} if playing audio-only ads.
|
||||
*/
|
||||
public void requestAds(ViewGroup adViewGroup) {
|
||||
public void requestAds(@Nullable ViewGroup adViewGroup) {
|
||||
if (hasAdPlaybackState || adsManager != null || pendingAdRequestContext != null) {
|
||||
// Ads have already been requested.
|
||||
return;
|
||||
}
|
||||
adDisplayContainer =
|
||||
imaFactory.createAdDisplayContainer(adViewGroup, /* player= */ componentListener);
|
||||
if (adViewGroup != null) {
|
||||
adDisplayContainer =
|
||||
imaFactory.createAdDisplayContainer(adViewGroup, /* player= */ componentListener);
|
||||
} else {
|
||||
adDisplayContainer =
|
||||
imaFactory.createAudioAdDisplayContainer(context, /* player= */ componentListener);
|
||||
}
|
||||
if (companionAdSlots != null) {
|
||||
adDisplayContainer.setCompanionSlots(companionAdSlots);
|
||||
}
|
||||
@ -639,6 +645,19 @@ public final class ImaAdsLoader
|
||||
adsLoader.requestAds(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips the current ad.
|
||||
*
|
||||
* <p>This method is intended for apps that play audio-only ads and so need to provide their own
|
||||
* UI for users to skip skippable ads. Apps showing video ads should not call this method, as the
|
||||
* IMA SDK provides the UI to skip ads in the ad view group passed via {@link AdViewProvider}.
|
||||
*/
|
||||
public void skipAd() {
|
||||
if (adsManager != null) {
|
||||
adsManager.skip();
|
||||
}
|
||||
}
|
||||
|
||||
// com.google.android.exoplayer2.source.ads.AdsLoader implementation.
|
||||
|
||||
@Override
|
||||
@ -1582,6 +1601,8 @@ public final class ImaAdsLoader
|
||||
* non-linear ads, and slots for companion ads.
|
||||
*/
|
||||
AdDisplayContainer createAdDisplayContainer(ViewGroup container, VideoAdPlayer player);
|
||||
/** Creates an {@link AdDisplayContainer} to hold the player for audio ads. */
|
||||
AdDisplayContainer createAudioAdDisplayContainer(Context context, VideoAdPlayer player);
|
||||
/**
|
||||
* Creates a {@link FriendlyObstruction} to describe an obstruction considered "friendly" for
|
||||
* viewability measurement purposes.
|
||||
@ -1817,6 +1838,11 @@ public final class ImaAdsLoader
|
||||
return ImaSdkFactory.createAdDisplayContainer(container, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdDisplayContainer createAudioAdDisplayContainer(Context context, VideoAdPlayer player) {
|
||||
return ImaSdkFactory.createAudioAdDisplayContainer(context, player);
|
||||
}
|
||||
|
||||
// The reasonDetail parameter to createFriendlyObstruction is annotated @Nullable but the
|
||||
// annotation is not kept in the obfuscated dependency.
|
||||
@SuppressWarnings("nullness:argument.type.incompatible")
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.ext.ima;
|
||||
|
||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
@ -31,7 +32,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.ads.interactivemedia.v3.api.Ad;
|
||||
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
|
||||
@ -117,6 +117,7 @@ public final class ImaAdsLoaderTest {
|
||||
|
||||
private ViewGroup adViewGroup;
|
||||
private AdsLoader.AdViewProvider adViewProvider;
|
||||
private AdsLoader.AdViewProvider audioAdsAdViewProvider;
|
||||
private AdEvent.AdEventListener adEventListener;
|
||||
private ContentProgressProvider contentProgressProvider;
|
||||
private VideoAdPlayer videoAdPlayer;
|
||||
@ -127,8 +128,8 @@ public final class ImaAdsLoaderTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
setupMocks();
|
||||
adViewGroup = new FrameLayout(ApplicationProvider.getApplicationContext());
|
||||
View adOverlayView = new View(ApplicationProvider.getApplicationContext());
|
||||
adViewGroup = new FrameLayout(getApplicationContext());
|
||||
View adOverlayView = new View(getApplicationContext());
|
||||
adViewProvider =
|
||||
new AdsLoader.AdViewProvider() {
|
||||
@Override
|
||||
@ -142,6 +143,18 @@ public final class ImaAdsLoaderTest {
|
||||
new AdsLoader.OverlayInfo(adOverlayView, AdsLoader.OverlayInfo.PURPOSE_CLOSE_AD));
|
||||
}
|
||||
};
|
||||
audioAdsAdViewProvider =
|
||||
new AdsLoader.AdViewProvider() {
|
||||
@Override
|
||||
public ViewGroup getAdViewGroup() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<AdsLoader.OverlayInfo> getAdOverlayInfos() {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@After
|
||||
@ -165,9 +178,21 @@ public final class ImaAdsLoaderTest {
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
||||
verify(mockImaFactory, atLeastOnce()).createAdDisplayContainer(adViewGroup, videoAdPlayer);
|
||||
verify(mockImaFactory, never()).createAudioAdDisplayContainer(any(), any());
|
||||
verify(mockAdDisplayContainer).registerFriendlyObstruction(mockFriendlyObstruction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startForAudioOnlyAds_createsAudioOnlyAdDisplayContainer() {
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.start(adsLoaderListener, audioAdsAdViewProvider);
|
||||
|
||||
verify(mockImaFactory, atLeastOnce())
|
||||
.createAudioAdDisplayContainer(getApplicationContext(), videoAdPlayer);
|
||||
verify(mockImaFactory, never()).createAdDisplayContainer(any(), any());
|
||||
verify(mockAdDisplayContainer, never()).registerFriendlyObstruction(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void start_withPlaceholderContent_initializedAdsLoader() {
|
||||
Timeline placeholderTimeline = new PlaceholderTimeline(MediaItem.fromUri(Uri.EMPTY));
|
||||
@ -470,7 +495,7 @@ public final class ImaAdsLoaderTest {
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||
new ImaAdsLoader.Builder(getApplicationContext())
|
||||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
@ -502,7 +527,7 @@ public final class ImaAdsLoaderTest {
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||
new ImaAdsLoader.Builder(getApplicationContext())
|
||||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
@ -534,7 +559,7 @@ public final class ImaAdsLoaderTest {
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||
new ImaAdsLoader.Builder(getApplicationContext())
|
||||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
@ -570,7 +595,7 @@ public final class ImaAdsLoaderTest {
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||
new ImaAdsLoader.Builder(getApplicationContext())
|
||||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
@ -609,7 +634,7 @@ public final class ImaAdsLoaderTest {
|
||||
setupPlayback(
|
||||
CONTENT_TIMELINE,
|
||||
cuePoints,
|
||||
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||
new ImaAdsLoader.Builder(getApplicationContext())
|
||||
.setPlayAdBeforeStartPosition(false)
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
@ -696,7 +721,7 @@ public final class ImaAdsLoaderTest {
|
||||
setupPlayback(
|
||||
contentTimeline,
|
||||
cuePoints,
|
||||
new ImaAdsLoader.Builder(ApplicationProvider.getApplicationContext())
|
||||
new ImaAdsLoader.Builder(getApplicationContext())
|
||||
.setImaFactory(mockImaFactory)
|
||||
.setImaSdkSettings(mockImaSdkSettings)
|
||||
.buildForAdTag(TEST_URI));
|
||||
@ -765,6 +790,13 @@ public final class ImaAdsLoaderTest {
|
||||
})
|
||||
.when(mockImaFactory)
|
||||
.createAdDisplayContainer(any(), any());
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
videoAdPlayer = invocation.getArgument(1);
|
||||
return mockAdDisplayContainer;
|
||||
})
|
||||
.when(mockImaFactory)
|
||||
.createAudioAdDisplayContainer(any(), any());
|
||||
when(mockImaFactory.createAdsRenderingSettings()).thenReturn(mockAdsRenderingSettings);
|
||||
when(mockImaFactory.createAdsRequest()).thenReturn(mockAdsRequest);
|
||||
when(mockImaFactory.createAdsLoader(any(), any(), any())).thenReturn(mockAdsLoader);
|
||||
|
@ -80,10 +80,12 @@ public interface AdsLoader {
|
||||
interface AdViewProvider {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Returns the {@link ViewGroup} on top of the player that will show any ad UI, or {@code null}
|
||||
* if playing audio-only ads. Any views on top of the returned view group must be described by
|
||||
* {@link OverlayInfo OverlayInfos} returned by {@link #getAdOverlayInfos()}, for accurate
|
||||
* viewability measurement.
|
||||
*/
|
||||
@Nullable
|
||||
ViewGroup getAdViewGroup();
|
||||
|
||||
/** @deprecated Use {@link #getAdOverlayInfos()} instead. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user