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