diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 274e4cc4b6..0a31580488 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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`. diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 834b7e546c..81e21bc753 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -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. + * + *
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")
diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java
index 23b7110103..ee0ea41e47 100644
--- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java
+++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java
@@ -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