Allow playback for ads buffered to end

`ImaAdsLoader` only loads ad media URLs once playback of the preceding ad (if
any) has started, and this behavior is likely to be similar for other ad loader
implementations due to limits on how long before an ad plays it is meant to be
loaded. This is problematic for very short ads followed by an ad because the ad
will load to the end but load control may not allow playback to start due to
the total buffered duration being low.

Fix this by allowing playback to start regardless of load control if we are
waiting for an ad media period to prepare.

An alternative fix would be to fake the ad progress in the `ImaAdsLoader` to
trigger loading the next ad, but this would only allow one ad to load ahead (so
the problem would remain for two short ads in a row followed by another ad).

Issue: #8492
PiperOrigin-RevId: 353600088
This commit is contained in:
andrewlewis 2021-01-25 09:42:23 +00:00 committed by Ian Baker
parent 6a900ab11b
commit c37f757854
3 changed files with 57 additions and 2 deletions

View file

@ -252,6 +252,9 @@
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)).
* Fix a regression that caused a short ad followed by another ad to be
skipped due to playback being stuck buffering waiting for the second ad
to load ([#8492](https://github.com/google/ExoPlayer/issues/8492)).
* FFmpeg extension:
* Link the FFmpeg library statically, saving 350KB in binary size on
average.

View file

@ -1729,8 +1729,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
: C.TIME_UNSET;
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
return bufferedToEnd
boolean isBufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
// Ad loader implementations may only load ad media once playback has nearly reached the ad, but
// it is possible for playback to be stuck buffering waiting for this. Therefore, we start
// playback regardless of buffered duration if we are waiting for an ad media period to prepare.
boolean isAdPendingPreparation = loadingHolder.info.id.isAd() && !loadingHolder.prepared;
return isBufferedToEnd
|| isAdPendingPreparation
|| loadControl.shouldStartPlayback(
getTotalBufferedDurationUs(),
mediaClock.getPlaybackParameters().speed,

View file

@ -4816,6 +4816,53 @@ public final class ExoPlayerTest {
.blockUntilEnded(TIMEOUT_MS);
}
@Test
public void shortAdFollowedByUnpreparedAd_playbackDoesNotGetStuck() throws Exception {
AdPlaybackState adPlaybackState =
FakeTimeline.createAdPlaybackState(/* adsPerAdGroup= */ 2, /* adGroupTimesUs...= */ 0);
long shortAdDurationMs = 1_000;
adPlaybackState =
adPlaybackState.withAdDurationsUs(new long[][] {{shortAdDurationMs, shortAdDurationMs}});
Timeline timeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ C.msToUs(10000),
adPlaybackState));
// Simulate the second ad not being prepared.
FakeMediaSource mediaSource =
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected MediaPeriod createMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
@Nullable TransferListener transferListener) {
return new FakeMediaPeriod(
trackGroupArray,
allocator,
FakeMediaPeriod.TrackDataFactory.singleSampleWithTimeUs(0),
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
/* deferOnPrepared= */ id.adIndexInAdGroup == 1);
}
};
SimpleExoPlayer player = new TestExoPlayerBuilder(context).build();
player.setMediaSource(mediaSource);
player.prepare();
player.play();
// The player is not stuck in the buffering state.
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_READY);
}
@Test
public void moveMediaItem() throws Exception {
TimelineWindowDefinition firstWindowDefinition =