From 4dddad831ab03e41ca6993b8757d1a3218bf9655 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 27 Jan 2020 13:52:28 +0000 Subject: [PATCH] Keep requested content position even for content periods. We currently only keep the requested next content start position while we are playing ads. However, we should also keep at least before a content period is fully prepared to not loose the information about the user intent. PiperOrigin-RevId: 291705752 --- .../android/exoplayer2/ExoPlayerImpl.java | 10 +- .../exoplayer2/ExoPlayerImplInternal.java | 128 ++++++++++++------ .../android/exoplayer2/MediaPeriodInfo.java | 30 ++-- .../android/exoplayer2/MediaPeriodQueue.java | 39 ++++-- .../android/exoplayer2/PlaybackInfo.java | 36 ++--- .../exoplayer2/MediaPeriodQueueTest.java | 17 ++- 6 files changed, 165 insertions(+), 95 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 53cffce12c..af11bcd868 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -691,9 +691,9 @@ import java.util.concurrent.TimeoutException; public long getContentPosition() { if (isPlayingAd()) { playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); - return playbackInfo.contentPositionUs == C.TIME_UNSET + return playbackInfo.requestedContentPositionUs == C.TIME_UNSET ? playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDefaultPositionMs() - : period.getPositionInWindowMs() + C.usToMs(playbackInfo.contentPositionUs); + : period.getPositionInWindowMs() + C.usToMs(playbackInfo.requestedContentPositionUs); } else { return getCurrentPosition(); } @@ -824,18 +824,18 @@ import java.util.concurrent.TimeoutException; } Timeline timeline = playbackInfo.timeline; MediaPeriodId mediaPeriodId = playbackInfo.periodId; - long contentPositionUs = playbackInfo.contentPositionUs; + long requestedContentPositionUs = playbackInfo.requestedContentPositionUs; long positionUs = playbackInfo.positionUs; if (clearPlaylist) { timeline = Timeline.EMPTY; mediaPeriodId = PlaybackInfo.getDummyPeriodForEmptyTimeline(); - contentPositionUs = C.TIME_UNSET; + requestedContentPositionUs = C.TIME_UNSET; positionUs = 0; } return new PlaybackInfo( timeline, mediaPeriodId, - contentPositionUs, + requestedContentPositionUs, playbackState, resetError ? null : playbackInfo.playbackError, /* isLoading= */ false, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 9f9fed6abf..12a3aafda0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -689,7 +689,8 @@ import java.util.concurrent.atomic.AtomicBoolean; /* forceDisableRenderers= */ true, /* forceBufferingState= */ false); if (newPositionUs != playbackInfo.positionUs) { - playbackInfo = copyWithNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs); + playbackInfo = + copyWithNewPosition(periodId, newPositionUs, playbackInfo.requestedContentPositionUs); if (sendDiscontinuity) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } @@ -729,7 +730,9 @@ import java.util.concurrent.atomic.AtomicBoolean; if (discontinuityPositionUs != playbackInfo.positionUs) { playbackInfo = copyWithNewPosition( - playbackInfo.periodId, discontinuityPositionUs, playbackInfo.contentPositionUs); + playbackInfo.periodId, + discontinuityPositionUs, + playbackInfo.requestedContentPositionUs); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } } else { @@ -863,7 +866,7 @@ import java.util.concurrent.atomic.AtomicBoolean; MediaPeriodId periodId; long periodPositionUs; - long contentPositionUs; + long requestedContentPosition; boolean seekPositionAdjusted; @Nullable Pair resolvedSeekPosition = @@ -878,17 +881,21 @@ import java.util.concurrent.atomic.AtomicBoolean; if (resolvedSeekPosition == null) { // The seek position was valid for the timeline that it was performed into, but the // timeline has changed or is not ready and a suitable seek position could not be resolved. - Pair firstPeriodAndPosition = getDummyFirstMediaPeriodPosition(); + Pair firstPeriodAndPosition = + getDummyFirstMediaPeriodPosition(playbackInfo.timeline); periodId = firstPeriodAndPosition.first; periodPositionUs = firstPeriodAndPosition.second; - contentPositionUs = C.TIME_UNSET; + requestedContentPosition = C.TIME_UNSET; seekPositionAdjusted = true; } else { // Update the resolved seek position to take ads into account. Object periodUid = resolvedSeekPosition.first; - contentPositionUs = resolvedSeekPosition.second; + long resolvedContentPosition = resolvedSeekPosition.second; + requestedContentPosition = + seekPosition.windowPositionUs == C.TIME_UNSET ? C.TIME_UNSET : resolvedContentPosition; periodId = - queue.resolveMediaPeriodIdForAds(playbackInfo.timeline, periodUid, contentPositionUs); + queue.resolveMediaPeriodIdForAds( + playbackInfo.timeline, periodUid, resolvedContentPosition); if (periodId.isAd()) { playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); periodPositionUs = @@ -897,7 +904,7 @@ import java.util.concurrent.atomic.AtomicBoolean; : 0; seekPositionAdjusted = true; } else { - periodPositionUs = resolvedSeekPosition.second; + periodPositionUs = resolvedContentPosition; seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET; } } @@ -942,7 +949,7 @@ import java.util.concurrent.atomic.AtomicBoolean; periodPositionUs = newPeriodPositionUs; } } finally { - playbackInfo = copyWithNewPosition(periodId, periodPositionUs, contentPositionUs); + playbackInfo = copyWithNewPosition(periodId, periodPositionUs, requestedContentPosition); if (seekPositionAdjusted) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT); } @@ -1125,8 +1132,6 @@ import java.util.concurrent.atomic.AtomicBoolean; } enabledRenderers = new Renderer[0]; - queue.clear(); - shouldContinueLoading = false; Timeline timeline = playbackInfo.timeline; if (clearPlaylist) { timeline = playlist.clear(/* shuffleOrder= */ null); @@ -1135,26 +1140,34 @@ import java.util.concurrent.atomic.AtomicBoolean; } pendingMessages.clear(); nextPendingMessageIndex = 0; + resetPosition = true; } MediaPeriodId mediaPeriodId = playbackInfo.periodId; long startPositionUs = playbackInfo.positionUs; - long contentPositionUs = playbackInfo.contentPositionUs; + long requestedContentPositionUs = + shouldUseRequestedContentPosition(playbackInfo, period, window) + ? playbackInfo.requestedContentPositionUs + : playbackInfo.positionUs; boolean resetTrackInfo = clearPlaylist; if (resetPosition) { pendingInitialSeekPosition = null; - Pair firstPeriodAndPosition = getDummyFirstMediaPeriodPosition(); + Pair firstPeriodAndPosition = getDummyFirstMediaPeriodPosition(timeline); mediaPeriodId = firstPeriodAndPosition.first; startPositionUs = firstPeriodAndPosition.second; - contentPositionUs = C.TIME_UNSET; + requestedContentPositionUs = C.TIME_UNSET; if (!mediaPeriodId.equals(playbackInfo.periodId)) { resetTrackInfo = true; } } + + queue.clear(); + shouldContinueLoading = false; + playbackInfo = new PlaybackInfo( timeline, mediaPeriodId, - contentPositionUs, + requestedContentPositionUs, playbackInfo.playbackState, resetError ? null : playbackInfo.playbackError, /* isLoading= */ false, @@ -1169,21 +1182,21 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - private Pair getDummyFirstMediaPeriodPosition() { - if (playbackInfo.timeline.isEmpty()) { + private Pair getDummyFirstMediaPeriodPosition(Timeline timeline) { + if (timeline.isEmpty()) { return Pair.create(PlaybackInfo.getDummyPeriodForEmptyTimeline(), 0L); } - int firstWindowIndex = playbackInfo.timeline.getFirstWindowIndex(shuffleModeEnabled); + int firstWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); Pair firstPeriodAndPosition = - playbackInfo.timeline.getPeriodPosition( + timeline.getPeriodPosition( window, period, firstWindowIndex, /* windowPositionUs= */ C.TIME_UNSET); // Add ad metadata if any and propagate the window sequence number to new period id. MediaPeriodId firstPeriodId = queue.resolveMediaPeriodIdForAds( - playbackInfo.timeline, firstPeriodAndPosition.first, /* positionUs= */ 0); + timeline, firstPeriodAndPosition.first, /* positionUs= */ 0); long positionUs = firstPeriodAndPosition.second; if (firstPeriodId.isAd()) { - playbackInfo.timeline.getPeriodByUid(firstPeriodId.periodUid, period); + timeline.getPeriodByUid(firstPeriodId.periodUid, period); positionUs = firstPeriodId.adIndexInAdGroup == period.getFirstAdIndexToPlay(firstPeriodId.adGroupIndex) ? period.getAdResumePositionUs() @@ -1408,7 +1421,7 @@ import java.util.concurrent.atomic.AtomicBoolean; newTrackSelectorResult, playbackInfo.positionUs, recreateStreams, streamResetFlags); playbackInfo = copyWithNewPosition( - playbackInfo.periodId, periodPositionUs, playbackInfo.contentPositionUs); + playbackInfo.periodId, periodPositionUs, playbackInfo.requestedContentPositionUs); if (playbackInfo.playbackState != Player.STATE_ENDED && periodPositionUs != playbackInfo.positionUs) { playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); @@ -1533,11 +1546,11 @@ import java.util.concurrent.atomic.AtomicBoolean; window, period); MediaPeriodId newPeriodId = positionUpdate.periodId; - long newContentPositionUs = positionUpdate.contentPositionUs; + long newRequestedContentPositionUs = positionUpdate.requestedContentPositionUs; boolean forceBufferingState = positionUpdate.forceBufferingState; long newPositionUs = positionUpdate.periodPositionUs; - boolean isPlaybackPositionUnchanged = - playbackInfo.periodId.equals(newPeriodId) && newPositionUs == playbackInfo.positionUs; + boolean periodPositionChanged = + !playbackInfo.periodId.equals(newPeriodId) || newPositionUs != playbackInfo.positionUs; try { if (positionUpdate.endPlayback) { @@ -1551,7 +1564,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /* clearPlaylist= */ false, /* resetError= */ true); } - if (isPlaybackPositionUnchanged) { + if (!periodPositionChanged) { // We can keep the current playing period. Update the rest of the queued periods. if (!queue.updateQueuedPeriods( timeline, rendererPositionUs, getMaxRendererReadPositionUs())) { @@ -1572,8 +1585,10 @@ import java.util.concurrent.atomic.AtomicBoolean; newPositionUs = seekToPeriodPosition(newPeriodId, newPositionUs, forceBufferingState); } } finally { - if (!isPlaybackPositionUnchanged) { - playbackInfo = copyWithNewPosition(newPeriodId, newPositionUs, newContentPositionUs); + if (periodPositionChanged + || newRequestedContentPositionUs != playbackInfo.requestedContentPositionUs) { + playbackInfo = + copyWithNewPosition(newPeriodId, newPositionUs, newRequestedContentPositionUs); } playbackInfo = playbackInfo.copyWithTimeline(timeline); resolvePendingMessagePositions(); @@ -1751,7 +1766,7 @@ import java.util.concurrent.atomic.AtomicBoolean; copyWithNewPosition( newPlayingPeriodHolder.info.id, newPlayingPeriodHolder.info.startPositionUs, - newPlayingPeriodHolder.info.contentPositionUs); + newPlayingPeriodHolder.info.requestedContentPositionUs); int discontinuityReason = oldPlayingPeriodHolder.info.isLastInTimelinePeriod ? Player.DISCONTINUITY_REASON_PERIOD_TRANSITION @@ -1823,7 +1838,9 @@ import java.util.concurrent.atomic.AtomicBoolean; enablePlayingPeriodRenderers(); playbackInfo = copyWithNewPosition( - playbackInfo.periodId, playbackInfo.positionUs, playbackInfo.contentPositionUs); + playbackInfo.periodId, + playbackInfo.positionUs, + playbackInfo.requestedContentPositionUs); } maybeContinueLoading(); } @@ -2076,14 +2093,18 @@ import java.util.concurrent.atomic.AtomicBoolean; return new PositionUpdateForPlaylistChange( PlaybackInfo.getDummyPeriodForEmptyTimeline(), /* periodPositionUs= */ 0, - /* contentPositionUs= */ C.TIME_UNSET, + /* requestedContentPositionUs= */ C.TIME_UNSET, /* forceBufferingState= */ false, /* endPlayback= */ true); } MediaPeriodId oldPeriodId = playbackInfo.periodId; Object newPeriodUid = oldPeriodId.periodUid; + boolean shouldUseRequestedContentPosition = + shouldUseRequestedContentPosition(playbackInfo, period, window); long oldContentPositionUs = - oldPeriodId.isAd() ? playbackInfo.contentPositionUs : playbackInfo.positionUs; + shouldUseRequestedContentPosition + ? playbackInfo.requestedContentPositionUs + : playbackInfo.positionUs; long newContentPositionUs = oldContentPositionUs; int startAtDefaultPositionWindowIndex = C.INDEX_UNSET; boolean forceBufferingState = false; @@ -2141,10 +2162,21 @@ import java.util.concurrent.atomic.AtomicBoolean; startAtDefaultPositionWindowIndex = timeline.getPeriodByUid(subsequentPeriodUid, period).windowIndex; } - } else if (oldContentPositionUs == C.TIME_UNSET) { - // We previously set the content position to be the default position of the current window. - // Re-resolve the period uid and position in case they changed since last time. - startAtDefaultPositionWindowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex; + } else if (shouldUseRequestedContentPosition) { + // We previously requested a content position, but haven't used it yet. Re-resolve the + // requested window position to the period uid and position in case they changed. + if (oldContentPositionUs == C.TIME_UNSET) { + startAtDefaultPositionWindowIndex = + timeline.getPeriodByUid(newPeriodUid, period).windowIndex; + } else { + playbackInfo.timeline.getPeriodByUid(oldPeriodId.periodUid, period); + long windowPositionUs = oldContentPositionUs + period.getPositionInWindowUs(); + int windowIndex = timeline.getPeriodByUid(newPeriodUid, period).windowIndex; + Pair periodPosition = + timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs); + newPeriodUid = periodPosition.first; + newContentPositionUs = periodPosition.second; + } } // Set period uid for default positions and resolve position for ad resolution. @@ -2158,15 +2190,12 @@ import java.util.concurrent.atomic.AtomicBoolean; /* windowPositionUs= */ C.TIME_UNSET); newPeriodUid = defaultPosition.first; contentPositionForAdResolutionUs = defaultPosition.second; + newContentPositionUs = C.TIME_UNSET; } // Ensure ad insertion metadata is up to date. MediaPeriodId periodIdWithAds = queue.resolveMediaPeriodIdForAds(timeline, newPeriodUid, contentPositionForAdResolutionUs); - if (!periodIdWithAds.isAd() && newContentPositionUs == C.TIME_UNSET) { - // We are not going to play an ad, so use resolved content position. - newContentPositionUs = contentPositionForAdResolutionUs; - } boolean oldAndNewPeriodIdAreSame = oldPeriodId.periodUid.equals(newPeriodUid) && !oldPeriodId.isAd() @@ -2193,6 +2222,19 @@ import java.util.concurrent.atomic.AtomicBoolean; newPeriodId, periodPositionUs, newContentPositionUs, forceBufferingState, endPlayback); } + private static boolean shouldUseRequestedContentPosition( + PlaybackInfo playbackInfo, Timeline.Period period, Timeline.Window window) { + // Only use the actual position as content position if it's not an ad and we already have + // prepared media information. Otherwise use the requested position. + MediaPeriodId periodId = playbackInfo.periodId; + Timeline timeline = playbackInfo.timeline; + return periodId.isAd() + || timeline.isEmpty() + || timeline.getWindow( + timeline.getPeriodByUid(periodId.periodUid, period).windowIndex, window) + .isPlaceholder; + } + /** * Converts a {@link SeekPosition} into the corresponding (periodUid, periodPositionUs) for the * internal timeline. @@ -2332,19 +2374,19 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final class PositionUpdateForPlaylistChange { public final MediaPeriodId periodId; public final long periodPositionUs; - public final long contentPositionUs; + public final long requestedContentPositionUs; public final boolean forceBufferingState; public final boolean endPlayback; public PositionUpdateForPlaylistChange( MediaPeriodId periodId, long periodPositionUs, - long contentPositionUs, + long requestedContentPositionUs, boolean forceBufferingState, boolean endPlayback) { this.periodId = periodId; this.periodPositionUs = periodPositionUs; - this.contentPositionUs = contentPositionUs; + this.requestedContentPositionUs = requestedContentPositionUs; this.forceBufferingState = forceBufferingState; this.endPlayback = endPlayback; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java index 2733df7ba6..c686371ad8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodInfo.java @@ -28,11 +28,13 @@ import com.google.android.exoplayer2.util.Util; /** The start position of the media to play within the media period, in microseconds. */ public final long startPositionUs; /** - * If this is an ad, the position to play in the next content media period. {@link C#TIME_UNSET} - * if this is not an ad or the next content media period should be played from its default - * position. + * The requested next start position for the current timeline period, in microseconds, or {@link + * C#TIME_UNSET} if the period was requested to start at its default position. + * + *

Note that if {@link #id} refers to an ad, this is the requested start position for the + * suspended content. */ - public final long contentPositionUs; + public final long requestedContentPositionUs; /** * The end position to which the media period's content is clipped in order to play a following ad * group, in microseconds, or {@link C#TIME_UNSET} if there is no following ad group or if this @@ -60,14 +62,14 @@ import com.google.android.exoplayer2.util.Util; MediaPeriodInfo( MediaPeriodId id, long startPositionUs, - long contentPositionUs, + long requestedContentPositionUs, long endPositionUs, long durationUs, boolean isLastInTimelinePeriod, boolean isFinal) { this.id = id; this.startPositionUs = startPositionUs; - this.contentPositionUs = contentPositionUs; + this.requestedContentPositionUs = requestedContentPositionUs; this.endPositionUs = endPositionUs; this.durationUs = durationUs; this.isLastInTimelinePeriod = isLastInTimelinePeriod; @@ -84,7 +86,7 @@ import com.google.android.exoplayer2.util.Util; : new MediaPeriodInfo( id, startPositionUs, - contentPositionUs, + requestedContentPositionUs, endPositionUs, durationUs, isLastInTimelinePeriod, @@ -92,16 +94,16 @@ import com.google.android.exoplayer2.util.Util; } /** - * Returns a copy of this instance with the content position set to the specified value. May - * return the same instance if nothing changed. + * Returns a copy of this instance with the requested content position set to the specified value. + * May return the same instance if nothing changed. */ - public MediaPeriodInfo copyWithContentPositionUs(long contentPositionUs) { - return contentPositionUs == this.contentPositionUs + public MediaPeriodInfo copyWithRequestedContentPositionUs(long requestedContentPositionUs) { + return requestedContentPositionUs == this.requestedContentPositionUs ? this : new MediaPeriodInfo( id, startPositionUs, - contentPositionUs, + requestedContentPositionUs, endPositionUs, durationUs, isLastInTimelinePeriod, @@ -118,7 +120,7 @@ import com.google.android.exoplayer2.util.Util; } MediaPeriodInfo that = (MediaPeriodInfo) o; return startPositionUs == that.startPositionUs - && contentPositionUs == that.contentPositionUs + && requestedContentPositionUs == that.requestedContentPositionUs && endPositionUs == that.endPositionUs && durationUs == that.durationUs && isLastInTimelinePeriod == that.isLastInTimelinePeriod @@ -131,7 +133,7 @@ import com.google.android.exoplayer2.util.Util; int result = 17; result = 31 * result + id.hashCode(); result = 31 * result + (int) startPositionUs; - result = 31 * result + (int) contentPositionUs; + result = 31 * result + (int) requestedContentPositionUs; result = 31 * result + (int) endPositionUs; result = 31 * result + (int) durationUs; result = 31 * result + (isLastInTimelinePeriod ? 1 : 0); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java index f72e83adb5..d595564744 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java @@ -146,8 +146,8 @@ import com.google.android.exoplayer2.util.Assertions; TrackSelectorResult emptyTrackSelectorResult) { long rendererPositionOffsetUs = loading == null - ? (info.id.isAd() && info.contentPositionUs != C.TIME_UNSET - ? info.contentPositionUs + ? (info.id.isAd() && info.requestedContentPositionUs != C.TIME_UNSET + ? info.requestedContentPositionUs : 0) : (loading.getRendererOffset() + loading.info.durationUs - info.startPositionUs); MediaPeriodHolder newPeriodHolder = @@ -314,8 +314,11 @@ import com.google.android.exoplayer2.util.Assertions; } } - // Use new period info, but keep old content position. - periodHolder.info = newPeriodInfo.copyWithContentPositionUs(oldPeriodInfo.contentPositionUs); + // Use the new period info, but keep the old requested content position to avoid overriding it + // by the default content position generated in getFollowingMediaPeriodInfo. + periodHolder.info = + newPeriodInfo.copyWithRequestedContentPositionUs( + oldPeriodInfo.requestedContentPositionUs); if (!areDurationsCompatible(oldPeriodInfo.durationUs, newPeriodInfo.durationUs)) { // The period duration changed. Remove all subsequent periods and check whether we read @@ -361,7 +364,7 @@ import com.google.android.exoplayer2.util.Assertions; return new MediaPeriodInfo( id, info.startPositionUs, - info.contentPositionUs, + info.requestedContentPositionUs, info.endPositionUs, durationUs, isLastInPeriod, @@ -534,7 +537,7 @@ import com.google.android.exoplayer2.util.Assertions; return getMediaPeriodInfo( playbackInfo.timeline, playbackInfo.periodId, - playbackInfo.contentPositionUs, + playbackInfo.requestedContentPositionUs, playbackInfo.positionUs); } @@ -631,11 +634,11 @@ import com.google.android.exoplayer2.util.Assertions; currentPeriodId.periodUid, adGroupIndex, nextAdIndexInAdGroup, - mediaPeriodInfo.contentPositionUs, + mediaPeriodInfo.requestedContentPositionUs, currentPeriodId.windowSequenceNumber); } else { // Play content from the ad group position. - long startPositionUs = mediaPeriodInfo.contentPositionUs; + long startPositionUs = mediaPeriodInfo.requestedContentPositionUs; if (startPositionUs == C.TIME_UNSET) { // If we're transitioning from an ad group to content starting from its default position, // project the start position forward as if this were a transition to a new window. @@ -656,6 +659,7 @@ import com.google.android.exoplayer2.util.Assertions; timeline, currentPeriodId.periodUid, startPositionUs, + mediaPeriodInfo.requestedContentPositionUs, currentPeriodId.windowSequenceNumber); } } else { @@ -667,6 +671,7 @@ import com.google.android.exoplayer2.util.Assertions; timeline, currentPeriodId.periodUid, /* startPositionUs= */ mediaPeriodInfo.durationUs, + /* requestedContentPositionUs= */ mediaPeriodInfo.durationUs, currentPeriodId.windowSequenceNumber); } int adIndexInAdGroup = period.getFirstAdIndexToPlay(nextAdGroupIndex); @@ -683,7 +688,7 @@ import com.google.android.exoplayer2.util.Assertions; } private MediaPeriodInfo getMediaPeriodInfo( - Timeline timeline, MediaPeriodId id, long contentPositionUs, long startPositionUs) { + Timeline timeline, MediaPeriodId id, long requestedContentPositionUs, long startPositionUs) { timeline.getPeriodByUid(id.periodUid, period); if (id.isAd()) { if (!period.isAdAvailable(id.adGroupIndex, id.adIndexInAdGroup)) { @@ -694,11 +699,15 @@ import com.google.android.exoplayer2.util.Assertions; id.periodUid, id.adGroupIndex, id.adIndexInAdGroup, - contentPositionUs, + requestedContentPositionUs, id.windowSequenceNumber); } else { return getMediaPeriodInfoForContent( - timeline, id.periodUid, startPositionUs, id.windowSequenceNumber); + timeline, + id.periodUid, + startPositionUs, + requestedContentPositionUs, + id.windowSequenceNumber); } } @@ -730,7 +739,11 @@ import com.google.android.exoplayer2.util.Assertions; } private MediaPeriodInfo getMediaPeriodInfoForContent( - Timeline timeline, Object periodUid, long startPositionUs, long windowSequenceNumber) { + Timeline timeline, + Object periodUid, + long startPositionUs, + long requestedContentPositionUs, + long windowSequenceNumber) { int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, nextAdGroupIndex); boolean isLastInPeriod = isLastInPeriod(id); @@ -746,7 +759,7 @@ import com.google.android.exoplayer2.util.Assertions; return new MediaPeriodInfo( id, startPositionUs, - /* contentPositionUs= */ C.TIME_UNSET, + requestedContentPositionUs, endPositionUs, durationUs, isLastInPeriod, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 72f4dab5ea..e8c4c6bf7a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -38,12 +38,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; /** The {@link MediaPeriodId} of the currently playing media period in the {@link #timeline}. */ public final MediaPeriodId periodId; /** - * If {@link #periodId} refers to an ad, the position of the suspended content relative to the - * start of the associated period in the {@link #timeline}, in microseconds. {@link C#TIME_UNSET} - * if {@link #periodId} does not refer to an ad or if the suspended content should be played from - * its default position. + * The requested next start position for the current period in the {@link #timeline}, in + * microseconds, or {@link C#TIME_UNSET} if the period was requested to start at its default + * position. + * + *

Note that if {@link #periodId} refers to an ad, this is the requested start position for the + * suspended content. */ - public final long contentPositionUs; + public final long requestedContentPositionUs; /** The current playback state. One of the {@link Player}.STATE_ constants. */ @Player.State public final int playbackState; /** The current playback error, or null if this is not an error state. */ @@ -102,7 +104,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; * * @param timeline See {@link #timeline}. * @param periodId See {@link #periodId}. - * @param contentPositionUs See {@link #contentPositionUs}. + * @param requestedContentPositionUs See {@link #requestedContentPositionUs}. * @param playbackState See {@link #playbackState}. * @param isLoading See {@link #isLoading}. * @param trackGroups See {@link #trackGroups}. @@ -115,7 +117,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; public PlaybackInfo( Timeline timeline, MediaPeriodId periodId, - long contentPositionUs, + long requestedContentPositionUs, @Player.State int playbackState, @Nullable ExoPlaybackException playbackError, boolean isLoading, @@ -127,7 +129,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; long positionUs) { this.timeline = timeline; this.periodId = periodId; - this.contentPositionUs = contentPositionUs; + this.requestedContentPositionUs = requestedContentPositionUs; this.playbackState = playbackState; this.playbackError = playbackError; this.isLoading = isLoading; @@ -149,8 +151,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; * * @param periodId New playing media period. See {@link #periodId}. * @param positionUs New position. See {@link #positionUs}. - * @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored - * if {@code periodId.isAd()} is true. + * @param requestedContentPositionUs New requested content position. See {@link + * #requestedContentPositionUs}. * @param totalBufferedDurationUs New buffered duration. See {@link #totalBufferedDurationUs}. * @param trackGroups The track groups for the new position. See {@link #trackGroups}. * @param trackSelectorResult The track selector result for the new position. See {@link @@ -161,14 +163,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; public PlaybackInfo copyWithNewPosition( MediaPeriodId periodId, long positionUs, - long contentPositionUs, + long requestedContentPositionUs, long totalBufferedDurationUs, TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { return new PlaybackInfo( timeline, periodId, - periodId.isAd() ? contentPositionUs : C.TIME_UNSET, + requestedContentPositionUs, playbackState, playbackError, isLoading, @@ -191,7 +193,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; return new PlaybackInfo( timeline, periodId, - contentPositionUs, + requestedContentPositionUs, playbackState, playbackError, isLoading, @@ -214,7 +216,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; return new PlaybackInfo( timeline, periodId, - contentPositionUs, + requestedContentPositionUs, playbackState, playbackError, isLoading, @@ -237,7 +239,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; return new PlaybackInfo( timeline, periodId, - contentPositionUs, + requestedContentPositionUs, playbackState, playbackError, isLoading, @@ -260,7 +262,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; return new PlaybackInfo( timeline, periodId, - contentPositionUs, + requestedContentPositionUs, playbackState, playbackError, isLoading, @@ -283,7 +285,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; return new PlaybackInfo( timeline, periodId, - contentPositionUs, + requestedContentPositionUs, playbackState, playbackError, isLoading, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index 6ff5d66809..bd185089ff 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -76,6 +76,7 @@ public final class MediaPeriodQueueTest { setupTimeline(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, /* isLast= */ true, @@ -86,10 +87,11 @@ public final class MediaPeriodQueueTest { public void getNextMediaPeriodInfo_withPrerollAd_returnsCorrectMediaPeriodInfos() { setupTimeline(/* adGroupTimesUs...= */ 0); setAdGroupLoaded(/* adGroupIndex= */ 0); - assertNextMediaPeriodInfoIsAd(/* adGroupIndex= */ 0, /* contentPositionUs= */ 0); + assertNextMediaPeriodInfoIsAd(/* adGroupIndex= */ 0, /* contentPositionUs= */ C.TIME_UNSET); advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, /* isLast= */ true, @@ -101,6 +103,7 @@ public final class MediaPeriodQueueTest { setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, SECOND_AD_START_TIME_US); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ FIRST_AD_START_TIME_US, /* durationUs= */ FIRST_AD_START_TIME_US, /* isLast= */ false, @@ -114,6 +117,7 @@ public final class MediaPeriodQueueTest { advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ FIRST_AD_START_TIME_US, + /* requestedContentPositionUs= */ FIRST_AD_START_TIME_US, /* endPositionUs= */ SECOND_AD_START_TIME_US, /* durationUs= */ SECOND_AD_START_TIME_US, /* isLast= */ false, @@ -125,6 +129,7 @@ public final class MediaPeriodQueueTest { advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ SECOND_AD_START_TIME_US, + /* requestedContentPositionUs= */ SECOND_AD_START_TIME_US, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, /* isLast= */ true, @@ -136,6 +141,7 @@ public final class MediaPeriodQueueTest { setupTimeline(/* adGroupTimesUs...= */ FIRST_AD_START_TIME_US, C.TIME_END_OF_SOURCE); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ FIRST_AD_START_TIME_US, /* durationUs= */ FIRST_AD_START_TIME_US, /* isLast= */ false, @@ -147,6 +153,7 @@ public final class MediaPeriodQueueTest { advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ FIRST_AD_START_TIME_US, + /* requestedContentPositionUs= */ FIRST_AD_START_TIME_US, /* endPositionUs= */ C.TIME_END_OF_SOURCE, /* durationUs= */ CONTENT_DURATION_US, /* isLast= */ false, @@ -158,6 +165,7 @@ public final class MediaPeriodQueueTest { advance(); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ CONTENT_DURATION_US, + /* requestedContentPositionUs= */ CONTENT_DURATION_US, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, /* isLast= */ true, @@ -169,6 +177,7 @@ public final class MediaPeriodQueueTest { setupTimeline(/* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, /* endPositionUs= */ C.TIME_END_OF_SOURCE, /* durationUs= */ CONTENT_DURATION_US, /* isLast= */ false, @@ -177,6 +186,7 @@ public final class MediaPeriodQueueTest { setAdGroupFailedToLoad(/* adGroupIndex= */ 0); assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( /* startPositionUs= */ CONTENT_DURATION_US, + /* requestedContentPositionUs= */ CONTENT_DURATION_US, /* endPositionUs= */ C.TIME_UNSET, /* durationUs= */ CONTENT_DURATION_US, /* isLast= */ true, @@ -343,7 +353,7 @@ public final class MediaPeriodQueueTest { new PlaybackInfo( timeline, mediaPeriodQueue.resolveMediaPeriodIdForAds(timeline, periodUid, /* positionUs= */ 0), - /* contentPositionUs= */ 0, + /* requestedContentPositionUs= */ C.TIME_UNSET, Player.STATE_READY, /* playbackError= */ null, /* isLoading= */ false, @@ -428,6 +438,7 @@ public final class MediaPeriodQueueTest { private void assertGetNextMediaPeriodInfoReturnsContentMediaPeriod( long startPositionUs, + long requestedContentPositionUs, long endPositionUs, long durationUs, boolean isLast, @@ -437,7 +448,7 @@ public final class MediaPeriodQueueTest { new MediaPeriodInfo( new MediaPeriodId(periodUid, /* windowSequenceNumber= */ 0, nextAdGroupIndex), startPositionUs, - /* contentPositionUs= */ C.TIME_UNSET, + requestedContentPositionUs, endPositionUs, durationUs, /* isLastInTimelinePeriod= */ isLast,