Update requested content position for ads on seek

PiperOrigin-RevId: 352011053
This commit is contained in:
andrewlewis 2021-01-15 16:08:38 +00:00 committed by Oliver Woodman
parent d4a84b88b5
commit e869d5dbf9
3 changed files with 76 additions and 9 deletions

View file

@ -215,6 +215,9 @@
ad view group
([#7344](https://github.com/google/ExoPlayer/issues/7344)),
([#8339](https://github.com/google/ExoPlayer/issues/8339)).
* 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.
* FFmpeg extension:
* Link the FFmpeg library statically, saving 350KB in binary size on
average.

View file

@ -1086,7 +1086,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
MediaPeriodId periodId;
long periodPositionUs;
long requestedContentPosition;
long requestedContentPositionUs;
boolean seekPositionAdjusted;
@Nullable
Pair<Object, Long> resolvedSeekPosition =
@ -1105,17 +1105,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
getPlaceholderFirstMediaPeriodPosition(playbackInfo.timeline);
periodId = firstPeriodAndPosition.first;
periodPositionUs = firstPeriodAndPosition.second;
requestedContentPosition = C.TIME_UNSET;
requestedContentPositionUs = C.TIME_UNSET;
seekPositionAdjusted = !playbackInfo.timeline.isEmpty();
} else {
// Update the resolved seek position to take ads into account.
Object periodUid = resolvedSeekPosition.first;
long resolvedContentPosition = resolvedSeekPosition.second;
requestedContentPosition =
seekPosition.windowPositionUs == C.TIME_UNSET ? C.TIME_UNSET : resolvedContentPosition;
long resolvedContentPositionUs = resolvedSeekPosition.second;
requestedContentPositionUs =
seekPosition.windowPositionUs == C.TIME_UNSET ? C.TIME_UNSET : resolvedContentPositionUs;
periodId =
queue.resolveMediaPeriodIdForAds(
playbackInfo.timeline, periodUid, resolvedContentPosition);
playbackInfo.timeline, periodUid, resolvedContentPositionUs);
if (periodId.isAd()) {
playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period);
periodPositionUs =
@ -1124,7 +1124,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
: 0;
seekPositionAdjusted = true;
} else {
periodPositionUs = resolvedContentPosition;
periodPositionUs = resolvedContentPositionUs;
seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
}
}
@ -1175,11 +1175,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* newPeriodId= */ periodId,
/* oldTimeline= */ playbackInfo.timeline,
/* oldPeriodId= */ playbackInfo.periodId,
/* positionForTargetOffsetOverrideUs= */ requestedContentPosition);
/* positionForTargetOffsetOverrideUs= */ requestedContentPositionUs);
}
} finally {
playbackInfo =
handlePositionDiscontinuity(periodId, periodPositionUs, requestedContentPosition);
handlePositionDiscontinuity(periodId, periodPositionUs, requestedContentPositionUs);
if (seekPositionAdjusted) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
}
@ -2242,6 +2242,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
? emptyTrackSelectorResult
: playingPeriodHolder.getTrackSelectorResult();
staticMetadata = extractMetadataFromTrackSelectionArray(trackSelectorResult.selections);
// Ensure the media period queue requested content position matches the new playback info.
if (playingPeriodHolder != null
&& playingPeriodHolder.info.requestedContentPositionUs != contentPositionUs) {
playingPeriodHolder.info =
playingPeriodHolder.info.copyWithRequestedContentPositionUs(contentPositionUs);
}
} else if (!mediaPeriodId.equals(playbackInfo.periodId)) {
// Reset previously kept track info if unprepared and the period changes.
trackGroupArray = TrackGroupArray.EMPTY;

View file

@ -2682,6 +2682,64 @@ public final class ExoPlayerTest {
assertThat(mediaSource.getCreatedMediaPeriods().get(3).adGroupIndex).isEqualTo(C.INDEX_UNSET);
}
@Test
public void seekPastBufferingMidroll_playsAdAndThenContentFromSeekPosition() throws Exception {
long adGroupWindowTimeMs = 1_000;
long seekPositionMs = 95_000;
long contentDurationMs = 100_000;
AdPlaybackState adPlaybackState =
FakeTimeline.createAdPlaybackState(
/* adsPerAdGroup= */ 1,
/* adGroupTimesUs...= */ TimelineWindowDefinition
.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US
+ C.msToUs(adGroupWindowTimeMs));
Timeline timeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ C.msToUs(contentDurationMs),
adPlaybackState));
AtomicBoolean hasCreatedAdMediaPeriod = new AtomicBoolean();
FakeMediaSource mediaSource =
new FakeMediaSource(timeline) {
@Override
public MediaPeriod createPeriod(
MediaPeriodId id, Allocator allocator, long startPositionUs) {
if (id.adGroupIndex == 0) {
hasCreatedAdMediaPeriod.set(true);
}
return super.createPeriod(id, allocator, startPositionUs);
}
};
SimpleExoPlayer player = new TestExoPlayerBuilder(context).build();
player.setMediaSource(mediaSource);
// Throw on the playback thread if the player position reaches a value that is just less than
// seek position. This ensures that playback stops and the assertion on the player position
// below fails, even if a long time passes between detecting the discontinuity and asserting.
player
.createMessage(
(messageType, payload) -> {
throw new IllegalStateException();
})
.setPosition(seekPositionMs - 1)
.send();
player.pause();
player.prepare();
// Block until the midroll has started buffering, then seek after the midroll before playing.
runMainLooperUntil(hasCreatedAdMediaPeriod::get);
player.seekTo(seekPositionMs);
player.play();
// When the ad finishes, the player position should be at or after the requested seek position.
TestPlayerRunHelper.runUntilPositionDiscontinuity(
player, Player.DISCONTINUITY_REASON_AD_INSERTION);
assertThat(player.getCurrentPosition()).isAtLeast(seekPositionMs);
}
@Test
public void repeatedSeeksToUnpreparedPeriodInSameWindowKeepsWindowSequenceNumber()
throws Exception {