Commit f2057156 by andrewlewis Committed by kim-vde

Time out ad preloading for initial seek

The IMA SDK currently notifies `CONTENT_RESUME_REQUESTED` then
`CONTENT_PAUSE_REQUESTED` quickly afterwards when playing an ad for an initial
seek. This triggered the logic to skip VPAID ads added for Issue: #7832,
causing the ad to be skipped.

This change reverts the fix for that issue and extends the ad preload timeout
logic to cover the case of an initial seek as well. Incompatible VPAID ads will
still be skipped but only after the preload delay (this seems fine given that
they are documented not to be supported, and we are just making the failure
mode less bad on a best-effort basis!).

Issue: #8428
Issue: #7832
PiperOrigin-RevId: 353011270
parent efcaee56
......@@ -238,6 +238,11 @@
* Fix a bug that could cause the next content position played after a seek
to snap back to the cue point of the preceding ad, rather than the
requested content position.
* Fix a regression that caused an ad group to be skipped after an initial
seek to a non-zero position. Unsupported VPAID ads will still be
skipped but only after the preload timeout rather than instantly
([#8428](https://github.com/google/ExoPlayer/issues/8428)),
([#7832](https://github.com/google/ExoPlayer/issues/7832)).
* FFmpeg extension:
* Link the FFmpeg library statically, saving 350KB in binary size on
average.
......
......@@ -451,25 +451,10 @@ import java.util.Map;
return;
}
if (playbackState == Player.STATE_BUFFERING && !player.isPlayingAd()) {
// Check whether we are waiting for an ad to preload.
int adGroupIndex = getLoadingAdGroupIndex();
if (adGroupIndex == C.INDEX_UNSET) {
return;
}
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
if (adGroup.count != C.LENGTH_UNSET
&& adGroup.count != 0
&& adGroup.states[0] != AdPlaybackState.AD_STATE_UNAVAILABLE) {
// An ad is available already so we must be buffering for some other reason.
return;
}
long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]);
long contentPositionMs = getContentPeriodPositionMs(player, timeline, period);
long timeUntilAdMs = adGroupTimeMs - contentPositionMs;
if (timeUntilAdMs < configuration.adPreloadTimeoutMs) {
waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
}
if (playbackState == Player.STATE_BUFFERING
&& !player.isPlayingAd()
&& isWaitingForAdToLoad()) {
waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
} else if (playbackState == Player.STATE_READY) {
waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET;
}
......@@ -759,27 +744,35 @@ import java.util.Map;
if (imaAdInfo != null) {
adPlaybackState = adPlaybackState.withSkippedAdGroup(imaAdInfo.adGroupIndex);
updateAdPlaybackState();
} else {
// Mark any ads for the current/reported player position that haven't loaded as being in the
// error state, to force resuming content. This includes VPAID ads that never load.
long playerPositionUs;
if (player != null) {
playerPositionUs = C.msToUs(getContentPeriodPositionMs(player, timeline, period));
} else if (!VideoProgressUpdate.VIDEO_TIME_NOT_READY.equals(lastContentProgress)) {
// Playback is backgrounded so use the last reported content position.
playerPositionUs = C.msToUs(lastContentProgress.getCurrentTimeMs());
} else {
return;
}
int adGroupIndex =
adPlaybackState.getAdGroupIndexForPositionUs(
playerPositionUs, C.msToUs(contentDurationMs));
if (adGroupIndex != C.INDEX_UNSET) {
markAdGroupInErrorStateAndClearPendingContentPosition(adGroupIndex);
}
}
}
/**
* 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() {
@Nullable Player player = this.player;
if (player == null) {
return false;
}
int adGroupIndex = getLoadingAdGroupIndex();
if (adGroupIndex == C.INDEX_UNSET) {
return false;
}
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
if (adGroup.count != C.LENGTH_UNSET
&& adGroup.count != 0
&& adGroup.states[0] != AdPlaybackState.AD_STATE_UNAVAILABLE) {
// An ad is available already.
return false;
}
long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]);
long contentPositionMs = getContentPeriodPositionMs(player, timeline, period);
long timeUntilAdMs = adGroupTimeMs - contentPositionMs;
return timeUntilAdMs < configuration.adPreloadTimeoutMs;
}
private void handlePlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
if (playingAd && imaAdState == IMA_AD_STATE_PLAYING) {
if (!bufferingAd && playbackState == Player.STATE_BUFFERING) {
......@@ -1305,6 +1298,12 @@ import java.util.Map;
handleAdGroupLoadError(new IOException("Ad preloading timed out"));
maybeNotifyPendingAdLoadError();
}
} else if (pendingContentPositionMs != C.TIME_UNSET
&& player != null
&& player.getPlaybackState() == Player.STATE_BUFFERING
&& isWaitingForAdToLoad()) {
// Prepare to timeout the load of an ad for the pending seek operation.
waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
}
return videoProgressUpdate;
......
......@@ -451,6 +451,62 @@ public final class ImaAdsLoaderTest {
}
@Test
public void startPlaybackAfterMidroll_doesNotSkipMidroll() {
// Simulate an ad at 2 seconds, and starting playback with an initial seek position at the ad.
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
long adGroupTimeUs =
adGroupPositionInWindowUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs));
// Start ad loading while still buffering and simulate the calls from the IMA SDK to resume then
// immediately pause content playback.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
contentProgressProvider.getContentProgress();
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
contentProgressProvider.getContentProgress();
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, /* ad= */ null));
contentProgressProvider.getContentProgress();
assertThat(getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
}
@Test
public void startPlaybackAfterMidroll_withAdNotPreloadingAfterTimeout_hasErrorAdGroup() {
// Simulate an ad at 2 seconds, and starting playback with an initial seek position at the ad.
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
long adGroupTimeUs =
adGroupPositionInWindowUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs));
// Start ad loading while still buffering and poll progress without the ad loading.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
contentProgressProvider.getContentProgress();
ShadowSystemClock.advanceBy(Duration.ofSeconds(5));
contentProgressProvider.getContentProgress();
assertThat(getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
}
@Test
public void bufferingDuringAd_callsOnBuffering() {
// Load the preroll ad.
imaAdsLoader.start(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment