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,