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 bd339ce1a6f..90d2901efc3 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 @@ -143,6 +143,7 @@ private final BiMap adInfoByAdMediaInfo; private final AdDisplayContainer adDisplayContainer; private final AdsLoader adsLoader; + private final Runnable adLoadTimeoutRunnable; @Nullable private Object pendingAdRequestContext; @Nullable private Player player; @@ -256,6 +257,7 @@ public AdTagLoader( contentDurationMs = C.TIME_UNSET; timeline = Timeline.EMPTY; adPlaybackState = AdPlaybackState.NONE; + adLoadTimeoutRunnable = this::handleAdLoadTimeout; if (adViewGroup != null) { adDisplayContainer = imaFactory.createAdDisplayContainer(adViewGroup, /* player= */ componentListener); @@ -488,7 +490,7 @@ public void onPlaybackStateChanged(@Player.State int playbackState) { if (playbackState == Player.STATE_BUFFERING && !player.isPlayingAd() - && isWaitingForAdToLoad()) { + && isWaitingForFirstAdToPreload()) { waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime(); } else if (playbackState == Player.STATE_READY) { waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET; @@ -780,7 +782,7 @@ private void resumeContentInternal() { * Returns whether this instance is expecting the first ad in an the upcoming ad group to load * within the {@link ImaUtil.Configuration#adPreloadTimeoutMs preload timeout}. */ - private boolean isWaitingForAdToLoad() { + private boolean isWaitingForFirstAdToPreload() { @Nullable Player player = this.player; if (player == null) { return false; @@ -802,6 +804,23 @@ private boolean isWaitingForAdToLoad() { return timeUntilAdMs < configuration.adPreloadTimeoutMs; } + private boolean isWaitingForCurrentAdToLoad() { + @Nullable Player player = this.player; + if (player == null) { + return false; + } + int adGroupIndex = player.getCurrentAdGroupIndex(); + if (adGroupIndex == C.INDEX_UNSET) { + return false; + } + AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); + int adIndexInAdGroup = player.getCurrentAdIndexInAdGroup(); + if (adGroup.count == C.LENGTH_UNSET || adGroup.count <= adIndexInAdGroup) { + return true; + } + return adGroup.states[adIndexInAdGroup] == AdPlaybackState.AD_STATE_UNAVAILABLE; + } + private void handlePlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) { if (playingAd && imaAdState == IMA_AD_STATE_PLAYING) { if (!bufferingAd && playbackState == Player.STATE_BUFFERING) { @@ -892,6 +911,10 @@ private void handleTimelineOrPositionChanged() { } } } + if (isWaitingForCurrentAdToLoad()) { + handler.removeCallbacks(adLoadTimeoutRunnable); + handler.postDelayed(adLoadTimeoutRunnable, configuration.adPreloadTimeoutMs); + } } private void loadAdInternal(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) { @@ -918,6 +941,12 @@ private void loadAdInternal(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) { // timeout after its media load timeout. return; } + if (player != null + && player.getCurrentAdGroupIndex() == adGroupIndex + && player.getCurrentAdIndexInAdGroup() == adIndexInAdGroup) { + // Loaded ad info the player is currently waiting for. + handler.removeCallbacks(adLoadTimeoutRunnable); + } // The ad count may increase on successive loads of ads in the same ad pod, for example, due to // separate requests for ad tags with multiple ads within the ad pod completing after an earlier @@ -1063,6 +1092,12 @@ private void handleAdGroupLoadError(Exception error) { } } + private void handleAdLoadTimeout() { + // IMA got stuck and didn't load an ad in time, so skip the entire group. + handleAdGroupLoadError(new IOException("Ad loading timed out")); + maybeNotifyPendingAdLoadError(); + } + private void markAdGroupInErrorStateAndClearPendingContentPosition(int adGroupIndex) { // Update the ad playback state so all ads in the ad group are in the error state. AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); @@ -1334,7 +1369,7 @@ public VideoProgressUpdate getContentProgress() { } else if (pendingContentPositionMs != C.TIME_UNSET && player != null && player.getPlaybackState() == Player.STATE_BUFFERING - && isWaitingForAdToLoad()) { + && isWaitingForFirstAdToPreload()) { // Prepare to timeout the load of an ad for the pending seek operation. waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime(); }