Add timeout for ad to load.

In some cases, the IMA SDK fails to call the expected loadAd
event to load the next ad to play. This is (potentially) the
only remaining case where playback can get stuck due to missing
calls from IMA as the player doesn't even have a MediaSource at
this stage and is only waiting for IMA to provide the ad URL.

We can reuse the existing adPreloadTimeoutMs that was added for
a similar purpose (when preloading the first ad in the group).
The JavaDoc matches this purpose as well and the default timeout
is appropriate since we expect to get the loadAd call immediately.

Issue: google/ExoPlayer#10510
PiperOrigin-RevId: 466953617
This commit is contained in:
tonihei 2022-08-11 14:59:21 +00:00 committed by Marc Baechinger
parent 4c4f7d73b4
commit b125d45a63
2 changed files with 42 additions and 3 deletions

View File

@ -46,6 +46,10 @@
* RTSP:
* Add H263 fragmented packet handling
([#119](https://github.com/androidx/media/pull/119)).
* IMA:
* Add timeout for loading ad information to handle cases where the IMA SDK
gets stuck loading an ad
([#10510](https://github.com/google/ExoPlayer/issues/10510)).
### 1.0.0-beta02 (2022-07-21)

View File

@ -143,6 +143,7 @@ import java.util.Map;
private final BiMap<AdMediaInfo, AdInfo> 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 @@ import java.util.Map;
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 @@ import java.util.Map;
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 @@ import java.util.Map;
* 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 @@ import java.util.Map;
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 @@ import java.util.Map;
}
}
}
if (isWaitingForCurrentAdToLoad()) {
handler.removeCallbacks(adLoadTimeoutRunnable);
handler.postDelayed(adLoadTimeoutRunnable, configuration.adPreloadTimeoutMs);
}
}
private void loadAdInternal(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) {
@ -918,6 +941,12 @@ import java.util.Map;
// 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 @@ import java.util.Map;
}
}
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 @@ import java.util.Map;
} 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();
}