From ae4cf9f1da0c495132a28809b4b733b9fa7556db Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 6 Nov 2020 11:42:19 +0000 Subject: [PATCH] Clean up AdTagLoader and ImaAdsLoader In preparation for adding support for ads in playlists: - Make releasing a no-op if the instance was already released - Remove null checks on non-null `adDisplayContainer` and `adsLoader` - Move initializing the ads manager into a private method as it will need to be called from two places soon. - Misc other cleanup. Issue: #3750 PiperOrigin-RevId: 341021493 --- .../exoplayer2/ext/ima/AdTagLoader.java | 93 ++++++++++--------- .../exoplayer2/ext/ima/ImaAdsLoader.java | 26 +++--- 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java index ac939c56082..722e226786a 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/AdTagLoader.java @@ -152,6 +152,8 @@ private long contentDurationMs; private AdPlaybackState adPlaybackState; + private boolean released; + // Fields tracking IMA's state. /** Whether IMA has sent an ad event to pause content since the last resume content event. */ @@ -300,14 +302,12 @@ public void start(Player player, AdViewProvider adViewProvider, EventListener ev adsId, ImaUtil.getAdGroupTimesUsForCuePoints(adsManager.getAdCuePoints())); updateAdPlaybackState(); } - if (adDisplayContainer != null) { - for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) { - adDisplayContainer.registerFriendlyObstruction( - imaFactory.createFriendlyObstruction( - overlayInfo.view, - ImaUtil.getFriendlyObstructionPurpose(overlayInfo.purpose), - overlayInfo.reasonDetail)); - } + for (OverlayInfo overlayInfo : adViewProvider.getAdOverlayInfos()) { + adDisplayContainer.registerFriendlyObstruction( + imaFactory.createFriendlyObstruction( + overlayInfo.view, + ImaUtil.getFriendlyObstructionPurpose(overlayInfo.purpose), + overlayInfo.reasonDetail)); } } @@ -326,9 +326,7 @@ public void stop() { lastVolumePercent = getPlayerVolumePercent(); lastAdProgress = getAdVideoProgressUpdate(); lastContentProgress = getContentVideoProgressUpdate(); - if (adDisplayContainer != null) { - adDisplayContainer.unregisterAllFriendlyObstructions(); - } + adDisplayContainer.unregisterAllFriendlyObstructions(); player.removeListener(this); this.player = null; eventListener = null; @@ -336,16 +334,18 @@ public void stop() { /** Releases all resources used by the ad tag loader. */ public void release() { + if (released) { + return; + } + released = true; pendingAdRequestContext = null; destroyAdsManager(); - if (adsLoader != null) { - adsLoader.removeAdsLoadedListener(componentListener); - adsLoader.removeAdErrorListener(componentListener); - if (configuration.applicationAdErrorListener != null) { - adsLoader.removeAdErrorListener(configuration.applicationAdErrorListener); - } - adsLoader.release(); + adsLoader.removeAdsLoadedListener(componentListener); + adsLoader.removeAdErrorListener(componentListener); + if (configuration.applicationAdErrorListener != null) { + adsLoader.removeAdErrorListener(configuration.applicationAdErrorListener); } + adsLoader.release(); imaPausedContent = false; imaAdState = IMA_AD_STATE_NONE; imaAdMediaInfo = null; @@ -394,27 +394,15 @@ public void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason in } checkArgument(timeline.getPeriodCount() == 1); this.timeline = timeline; - long contentDurationUs = timeline.getPeriod(/* periodIndex= */ 0, period).durationUs; + Player player = checkNotNull(this.player); + long contentDurationUs = timeline.getPeriod(player.getCurrentPeriodIndex(), period).durationUs; contentDurationMs = C.usToMs(contentDurationUs); - if (contentDurationUs != C.TIME_UNSET) { + if (contentDurationUs != adPlaybackState.contentDurationUs) { adPlaybackState = adPlaybackState.withContentDurationUs(contentDurationUs); - } - @Nullable AdsManager adsManager = this.adsManager; - if (!isAdsManagerInitialized && adsManager != null) { - isAdsManagerInitialized = true; - @Nullable AdsRenderingSettings adsRenderingSettings = setupAdsRendering(); - if (adsRenderingSettings == null) { - // There are no ads to play. - destroyAdsManager(); - } else { - adsManager.init(adsRenderingSettings); - adsManager.start(); - if (configuration.debugModeEnabled) { - Log.d(TAG, "Initialized with ads rendering settings: " + adsRenderingSettings); - } - } updateAdPlaybackState(); } + long contentPositionMs = getContentPeriodPositionMs(player, timeline, period); + maybeInitializeAdsManager(contentPositionMs, contentDurationMs); handleTimelineOrPositionChanged(); } @@ -515,12 +503,33 @@ private AdsLoader requestAds( return adsLoader; } + private void maybeInitializeAdsManager(long contentPositionMs, long contentDurationMs) { + @Nullable AdsManager adsManager = this.adsManager; + if (!isAdsManagerInitialized && adsManager != null) { + isAdsManagerInitialized = true; + @Nullable + AdsRenderingSettings adsRenderingSettings = + setupAdsRendering(contentPositionMs, contentDurationMs); + if (adsRenderingSettings == null) { + // There are no ads to play. + destroyAdsManager(); + } else { + adsManager.init(adsRenderingSettings); + adsManager.start(); + if (configuration.debugModeEnabled) { + Log.d(TAG, "Initialized with ads rendering settings: " + adsRenderingSettings); + } + } + updateAdPlaybackState(); + } + } + /** * Configures ads rendering for starting playback, returning the settings for the IMA SDK or * {@code null} if no ads should play. */ @Nullable - private AdsRenderingSettings setupAdsRendering() { + private AdsRenderingSettings setupAdsRendering(long contentPositionMs, long contentDurationMs) { AdsRenderingSettings adsRenderingSettings = imaFactory.createAdsRenderingSettings(); adsRenderingSettings.setEnablePreloading(true); adsRenderingSettings.setMimeTypes( @@ -541,7 +550,6 @@ private AdsRenderingSettings setupAdsRendering() { // Skip ads based on the start position as required. long[] adGroupTimesUs = adPlaybackState.adGroupTimesUs; - long contentPositionMs = getContentPeriodPositionMs(checkNotNull(player), timeline, period); int adGroupForPositionIndex = adPlaybackState.getAdGroupIndexForPositionUs( C.msToUs(contentPositionMs), C.msToUs(contentDurationMs)); @@ -957,7 +965,6 @@ private void stopAdInternal(AdMediaInfo adMediaInfo) { } return; } - checkNotNull(player); imaAdState = IMA_AD_STATE_NONE; stopUpdatingAdProgress(); // TODO: Handle the skipped event so the ad can be marked as skipped rather than played. @@ -1155,10 +1162,12 @@ private String getAdMediaInfoString(AdMediaInfo adMediaInfo) { private static long getContentPeriodPositionMs( Player player, Timeline timeline, Timeline.Period period) { long contentWindowPositionMs = player.getContentPosition(); - return contentWindowPositionMs - - (timeline.isEmpty() - ? 0 - : timeline.getPeriod(/* periodIndex= */ 0, period).getPositionInWindowMs()); + if (timeline.isEmpty()) { + return contentWindowPositionMs; + } else { + return contentWindowPositionMs + - timeline.getPeriod(player.getCurrentPeriodIndex(), period).getPositionInWindowMs(); + } } private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) { 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 a60b7147bce..b536cd3892c 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 @@ -31,7 +31,6 @@ import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; import com.google.ads.interactivemedia.v3.api.AdErrorEvent.AdErrorListener; import com.google.ads.interactivemedia.v3.api.AdEvent.AdEventListener; -import com.google.ads.interactivemedia.v3.api.AdsLoader; import com.google.ads.interactivemedia.v3.api.AdsManager; import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings; import com.google.ads.interactivemedia.v3.api.AdsRequest; @@ -46,6 +45,7 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.source.MediaSourceFactory; +import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.MimeTypes; @@ -61,8 +61,7 @@ import java.util.Set; /** - * {@link com.google.android.exoplayer2.source.ads.AdsLoader} using the IMA SDK. All methods must be - * called on the main thread. + * {@link AdsLoader} using the IMA SDK. All methods must be called on the main thread. * *

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 @@ -83,8 +82,7 @@ * href="https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/omsdk">IMA * SDK Open Measurement documentation for more information. */ -public final class ImaAdsLoader - implements Player.EventListener, com.google.android.exoplayer2.source.ads.AdsLoader { +public final class ImaAdsLoader implements Player.EventListener, AdsLoader { static { ExoPlayerLibraryInfo.registerModule("goog.exo.ima"); @@ -154,8 +152,8 @@ public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { /** * Sets a listener for ad errors that will be passed to {@link - * AdsLoader#addAdErrorListener(AdErrorListener)} and {@link - * AdsManager#addAdErrorListener(AdErrorListener)}. + * com.google.ads.interactivemedia.v3.api.AdsLoader#addAdErrorListener(AdErrorListener)} and + * {@link AdsManager#addAdErrorListener(AdErrorListener)}. * * @param adErrorListener The ad error listener. * @return This builder, for convenience. @@ -384,11 +382,11 @@ private ImaAdsLoader( } /** - * Returns the underlying {@link AdsLoader} wrapped by this instance, or {@code null} if ads have - * not been requested yet. + * Returns the underlying {@link com.google.ads.interactivemedia.v3.api.AdsLoader} wrapped by this + * instance, or {@code null} if ads have not been requested yet. */ @Nullable - public AdsLoader getAdsLoader() { + public com.google.ads.interactivemedia.v3.api.AdsLoader getAdsLoader() { return adTagLoader != null ? adTagLoader.getAdsLoader() : null; } @@ -400,8 +398,8 @@ public AdsLoader getAdsLoader() { * 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. + * the {@link AdViewProvider} when creating the media source to benefit from automatic + * registration. */ @Nullable public AdDisplayContainer getAdDisplayContainer() { @@ -448,7 +446,7 @@ public void skipAd() { } } - // com.google.android.exoplayer2.source.ads.AdsLoader implementation. + // AdsLoader implementation. @Override public void setPlayer(@Nullable Player player) { @@ -576,7 +574,7 @@ public AdsRequest createAdsRequest() { } @Override - public AdsLoader createAdsLoader( + public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) { return ImaSdkFactory.getInstance() .createAdsLoader(context, imaSdkSettings, adDisplayContainer);