mirror of
https://github.com/samsonjs/media.git
synced 2026-04-24 14:37:45 +00:00
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
This commit is contained in:
parent
efcaee563a
commit
f2057156d1
3 changed files with 97 additions and 37 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -450,6 +450,62 @@ public final class ImaAdsLoaderTest {
|
|||
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
||||
}
|
||||
|
||||
@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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue