Mask periodId and loadingPeriodId

This change masks playbackInfo.periodId and playbackInfo.loadingPeriodId for operations which change these periods (set/add/remove sources and seeks).

Because this masking is reflected in the playbackInfo object, player attributes can be retrieved without the maskingXyz variables in EPI. This has the advantage that the playbackInfo object always reflects the public state of the player even when operations are still pending. The maskingXyz variables in EPI are only required for the deprecated use case of an initial seek in an empty timeline.

PiperOrigin-RevId: 321160092
This commit is contained in:
bachinger 2020-07-14 16:11:20 +01:00 committed by Oliver Woodman
parent 683d630ec6
commit d62688cfc0
4 changed files with 1433 additions and 228 deletions

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Handler; import android.os.Handler;
@ -418,16 +419,18 @@ import java.util.concurrent.TimeoutException;
public void addMediaSources(int index, List<MediaSource> mediaSources) { public void addMediaSources(int index, List<MediaSource> mediaSources) {
Assertions.checkArgument(index >= 0); Assertions.checkArgument(index >= 0);
validateMediaSources(mediaSources, /* mediaSourceReplacement= */ false); validateMediaSources(mediaSources, /* mediaSourceReplacement= */ false);
int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition();
Timeline oldTimeline = getCurrentTimeline(); Timeline oldTimeline = getCurrentTimeline();
pendingOperationAcks++; pendingOperationAcks++;
List<MediaSourceList.MediaSourceHolder> holders = addMediaSourceHolders(index, mediaSources); List<MediaSourceList.MediaSourceHolder> holders = addMediaSourceHolders(index, mediaSources);
PlaybackInfo playbackInfo = Timeline newTimeline = createMaskingTimeline();
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline); PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
playbackInfo,
newTimeline,
getPeriodPositionAfterTimelineChanged(oldTimeline, newTimeline));
internalPlayer.addMediaSources(index, holders, shuffleOrder); internalPlayer.addMediaSources(index, holders, shuffleOrder);
updatePlaybackInfo( updatePlaybackInfo(
playbackInfo, newPlaybackInfo,
/* positionDiscontinuity= */ false, /* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@ -448,18 +451,20 @@ import java.util.concurrent.TimeoutException;
&& fromIndex <= toIndex && fromIndex <= toIndex
&& toIndex <= mediaSourceHolderSnapshots.size() && toIndex <= mediaSourceHolderSnapshots.size()
&& newFromIndex >= 0); && newFromIndex >= 0);
int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition();
Timeline oldTimeline = getCurrentTimeline(); Timeline oldTimeline = getCurrentTimeline();
pendingOperationAcks++; pendingOperationAcks++;
newFromIndex = newFromIndex =
Math.min(newFromIndex, mediaSourceHolderSnapshots.size() - (toIndex - fromIndex)); Math.min(newFromIndex, mediaSourceHolderSnapshots.size() - (toIndex - fromIndex));
Util.moveItems(mediaSourceHolderSnapshots, fromIndex, toIndex, newFromIndex); Util.moveItems(mediaSourceHolderSnapshots, fromIndex, toIndex, newFromIndex);
PlaybackInfo playbackInfo = Timeline newTimeline = createMaskingTimeline();
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline); PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
playbackInfo,
newTimeline,
getPeriodPositionAfterTimelineChanged(oldTimeline, newTimeline));
internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder); internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder);
updatePlaybackInfo( updatePlaybackInfo(
playbackInfo, newPlaybackInfo,
/* positionDiscontinuity= */ false, /* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@ -477,13 +482,18 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void setShuffleOrder(ShuffleOrder shuffleOrder) { public void setShuffleOrder(ShuffleOrder shuffleOrder) {
PlaybackInfo playbackInfo = maskTimeline(); Timeline timeline = createMaskingTimeline();
maskWithCurrentPosition(); PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
playbackInfo,
timeline,
getPeriodPositionOrMaskWindowPosition(
timeline, getCurrentWindowIndex(), getCurrentPosition()));
pendingOperationAcks++; pendingOperationAcks++;
this.shuffleOrder = shuffleOrder; this.shuffleOrder = shuffleOrder;
internalPlayer.setShuffleOrder(shuffleOrder); internalPlayer.setShuffleOrder(shuffleOrder);
updatePlaybackInfo( updatePlaybackInfo(
playbackInfo, newPlaybackInfo,
/* positionDiscontinuity= */ false, /* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@ -521,7 +531,6 @@ import java.util.concurrent.TimeoutException;
&& playbackInfo.playbackSuppressionReason == playbackSuppressionReason) { && playbackInfo.playbackSuppressionReason == playbackSuppressionReason) {
return; return;
} }
maskWithCurrentPosition();
pendingOperationAcks++; pendingOperationAcks++;
PlaybackInfo playbackInfo = PlaybackInfo playbackInfo =
this.playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason); this.playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason);
@ -589,14 +598,18 @@ import java.util.concurrent.TimeoutException;
new ExoPlayerImplInternal.PlaybackInfoUpdate(playbackInfo)); new ExoPlayerImplInternal.PlaybackInfoUpdate(playbackInfo));
return; return;
} }
maskWindowIndexAndPositionForSeek(timeline, windowIndex, positionMs);
@Player.State @Player.State
int newPlaybackState = int newPlaybackState =
getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : Player.STATE_BUFFERING; getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : Player.STATE_BUFFERING;
PlaybackInfo playbackInfo = this.playbackInfo.copyWithPlaybackState(newPlaybackState); PlaybackInfo newPlaybackInfo = this.playbackInfo.copyWithPlaybackState(newPlaybackState);
newPlaybackInfo =
maskTimelineAndPosition(
newPlaybackInfo,
timeline,
getPeriodPositionOrMaskWindowPosition(timeline, windowIndex, positionMs));
internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs)); internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs));
updatePlaybackInfo( updatePlaybackInfo(
playbackInfo, newPlaybackInfo,
/* positionDiscontinuity= */ true, /* positionDiscontinuity= */ true,
/* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK, /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK,
/* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@ -732,7 +745,7 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public int getCurrentPeriodIndex() { public int getCurrentPeriodIndex() {
if (shouldMaskPosition()) { if (playbackInfo.timeline.isEmpty()) {
return maskingPeriodIndex; return maskingPeriodIndex;
} else { } else {
return playbackInfo.timeline.getIndexOfPeriod(playbackInfo.periodId.periodUid); return playbackInfo.timeline.getIndexOfPeriod(playbackInfo.periodId.periodUid);
@ -758,7 +771,7 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public long getCurrentPosition() { public long getCurrentPosition() {
if (shouldMaskPosition()) { if (playbackInfo.timeline.isEmpty()) {
return maskingWindowPositionMs; return maskingWindowPositionMs;
} else if (playbackInfo.periodId.isAd()) { } else if (playbackInfo.periodId.isAd()) {
return C.usToMs(playbackInfo.positionUs); return C.usToMs(playbackInfo.positionUs);
@ -784,7 +797,7 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public boolean isPlayingAd() { public boolean isPlayingAd() {
return !shouldMaskPosition() && playbackInfo.periodId.isAd(); return playbackInfo.periodId.isAd();
} }
@Override @Override
@ -811,7 +824,7 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public long getContentBufferedPosition() { public long getContentBufferedPosition() {
if (shouldMaskPosition()) { if (playbackInfo.timeline.isEmpty()) {
return maskingWindowPositionMs; return maskingWindowPositionMs;
} }
if (playbackInfo.loadingMediaPeriodId.windowSequenceNumber if (playbackInfo.loadingMediaPeriodId.windowSequenceNumber
@ -864,7 +877,7 @@ import java.util.concurrent.TimeoutException;
} }
private int getCurrentWindowIndexInternal() { private int getCurrentWindowIndexInternal() {
if (shouldMaskPosition()) { if (playbackInfo.timeline.isEmpty()) {
return maskingWindowIndex; return maskingWindowIndex;
} else { } else {
return playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period) return playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period)
@ -909,7 +922,8 @@ import java.util.concurrent.TimeoutException;
if (pendingOperationAcks == 0) { if (pendingOperationAcks == 0) {
Timeline newTimeline = playbackInfoUpdate.playbackInfo.timeline; Timeline newTimeline = playbackInfoUpdate.playbackInfo.timeline;
if (!this.playbackInfo.timeline.isEmpty() && newTimeline.isEmpty()) { if (!this.playbackInfo.timeline.isEmpty() && newTimeline.isEmpty()) {
// Update the masking variables, which are used when the timeline becomes empty. // Update the masking variables, which are used when the timeline becomes empty because a
// ConcatenatingMediaSource has been cleared.
resetMaskingPosition(); resetMaskingPosition();
} }
if (!newTimeline.isEmpty()) { if (!newTimeline.isEmpty()) {
@ -938,8 +952,6 @@ import java.util.concurrent.TimeoutException;
removeMediaSourceHolders( removeMediaSourceHolders(
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolderSnapshots.size()); /* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolderSnapshots.size());
resetMaskingPosition(); resetMaskingPosition();
} else {
maskWithCurrentPosition();
} }
Timeline timeline = playbackInfo.timeline; Timeline timeline = playbackInfo.timeline;
MediaPeriodId mediaPeriodId = playbackInfo.periodId; MediaPeriodId mediaPeriodId = playbackInfo.periodId;
@ -1006,8 +1018,7 @@ import java.util.concurrent.TimeoutException;
} }
List<MediaSourceList.MediaSourceHolder> holders = List<MediaSourceList.MediaSourceHolder> holders =
addMediaSourceHolders(/* index= */ 0, mediaSources); addMediaSourceHolders(/* index= */ 0, mediaSources);
PlaybackInfo playbackInfo = maskTimeline(); Timeline timeline = createMaskingTimeline();
Timeline timeline = playbackInfo.timeline;
if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) { if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) {
throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs); throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs);
} }
@ -1019,11 +1030,14 @@ import java.util.concurrent.TimeoutException;
startWindowIndex = currentWindowIndex; startWindowIndex = currentWindowIndex;
startPositionMs = currentPositionMs; startPositionMs = currentPositionMs;
} }
maskWindowIndexAndPositionForSeek( PlaybackInfo newPlaybackInfo =
timeline, startWindowIndex == C.INDEX_UNSET ? 0 : startWindowIndex, startPositionMs); maskTimelineAndPosition(
playbackInfo,
timeline,
getPeriodPositionOrMaskWindowPosition(timeline, startWindowIndex, startPositionMs));
// Mask the playback state. // Mask the playback state.
int maskingPlaybackState = playbackInfo.playbackState; int maskingPlaybackState = newPlaybackInfo.playbackState;
if (startWindowIndex != C.INDEX_UNSET && playbackInfo.playbackState != STATE_IDLE) { if (startWindowIndex != C.INDEX_UNSET && newPlaybackInfo.playbackState != STATE_IDLE) {
// Position reset to startWindowIndex (results in pending initial seek). // Position reset to startWindowIndex (results in pending initial seek).
if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) { if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) {
// Setting an empty timeline or invalid seek transitions to ended. // Setting an empty timeline or invalid seek transitions to ended.
@ -1032,11 +1046,11 @@ import java.util.concurrent.TimeoutException;
maskingPlaybackState = STATE_BUFFERING; maskingPlaybackState = STATE_BUFFERING;
} }
} }
playbackInfo = playbackInfo.copyWithPlaybackState(maskingPlaybackState); newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(maskingPlaybackState);
internalPlayer.setMediaSources( internalPlayer.setMediaSources(
holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder); holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder);
updatePlaybackInfo( updatePlaybackInfo(
playbackInfo, newPlaybackInfo,
/* positionDiscontinuity= */ false, /* positionDiscontinuity= */ false,
/* ignored */ Player.DISCONTINUITY_REASON_INTERNAL, /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@ -1064,26 +1078,29 @@ import java.util.concurrent.TimeoutException;
Assertions.checkArgument( Assertions.checkArgument(
fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolderSnapshots.size()); fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolderSnapshots.size());
int currentWindowIndex = getCurrentWindowIndex(); int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition();
Timeline oldTimeline = getCurrentTimeline(); Timeline oldTimeline = getCurrentTimeline();
int currentMediaSourceCount = mediaSourceHolderSnapshots.size(); int currentMediaSourceCount = mediaSourceHolderSnapshots.size();
pendingOperationAcks++; pendingOperationAcks++;
removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex); removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex);
PlaybackInfo playbackInfo = Timeline newTimeline = createMaskingTimeline();
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline); PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
playbackInfo,
newTimeline,
getPeriodPositionAfterTimelineChanged(oldTimeline, newTimeline));
// Player transitions to STATE_ENDED if the current index is part of the removed tail. // Player transitions to STATE_ENDED if the current index is part of the removed tail.
final boolean transitionsToEnded = final boolean transitionsToEnded =
playbackInfo.playbackState != STATE_IDLE newPlaybackInfo.playbackState != STATE_IDLE
&& playbackInfo.playbackState != STATE_ENDED && newPlaybackInfo.playbackState != STATE_ENDED
&& fromIndex < toIndex && fromIndex < toIndex
&& toIndex == currentMediaSourceCount && toIndex == currentMediaSourceCount
&& currentWindowIndex >= playbackInfo.timeline.getWindowCount(); && currentWindowIndex >= newPlaybackInfo.timeline.getWindowCount();
if (transitionsToEnded) { if (transitionsToEnded) {
playbackInfo = playbackInfo.copyWithPlaybackState(STATE_ENDED); newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(STATE_ENDED);
} }
internalPlayer.removeMediaSources(fromIndex, toIndex, shuffleOrder); internalPlayer.removeMediaSources(fromIndex, toIndex, shuffleOrder);
updatePlaybackInfo( updatePlaybackInfo(
playbackInfo, newPlaybackInfo,
/* positionDiscontinuity= */ false, /* positionDiscontinuity= */ false,
/* ignored */ Player.DISCONTINUITY_REASON_INTERNAL, /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
@ -1131,107 +1148,163 @@ import java.util.concurrent.TimeoutException;
} }
} }
private PlaybackInfo maskTimeline() { private Timeline createMaskingTimeline() {
return playbackInfo.copyWithTimeline( return new PlaylistTimeline(mediaSourceHolderSnapshots, shuffleOrder);
mediaSourceHolderSnapshots.isEmpty()
? Timeline.EMPTY
: new PlaylistTimeline(mediaSourceHolderSnapshots, shuffleOrder));
} }
private PlaybackInfo maskTimelineAndWindowIndex( private PlaybackInfo maskTimelineAndPosition(
int currentWindowIndex, long currentPositionMs, Timeline oldTimeline) { PlaybackInfo playbackInfo, Timeline timeline, @Nullable Pair<Object, Long> periodPosition) {
PlaybackInfo playbackInfo = maskTimeline(); Assertions.checkArgument(timeline.isEmpty() || periodPosition != null);
Timeline maskingTimeline = playbackInfo.timeline; Timeline oldTimeline = playbackInfo.timeline;
if (oldTimeline.isEmpty()) { // Mask the timeline.
// The index is the default index or was set by a seek in the empty old timeline. playbackInfo = playbackInfo.copyWithTimeline(timeline);
maskingWindowIndex = currentWindowIndex;
if (!maskingTimeline.isEmpty() && currentWindowIndex >= maskingTimeline.getWindowCount()) { if (timeline.isEmpty()) {
// The seek is not valid in the new timeline. // Reset periodId and loadingPeriodId.
maskWithDefaultPosition(maskingTimeline); MediaPeriodId dummyMediaPeriodId = PlaybackInfo.getDummyPeriodForEmptyTimeline();
} playbackInfo =
playbackInfo.copyWithNewPosition(
dummyMediaPeriodId,
/* positionUs= */ C.msToUs(maskingWindowPositionMs),
/* requestedContentPositionUs= */ C.msToUs(maskingWindowPositionMs),
/* totalBufferedDurationUs= */ 0,
TrackGroupArray.EMPTY,
emptyTrackSelectorResult);
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(dummyMediaPeriodId);
playbackInfo.bufferedPositionUs = playbackInfo.positionUs;
return playbackInfo; return playbackInfo;
} }
@Nullable
Pair<Object, Long> periodPosition = Object oldPeriodUid = playbackInfo.periodId.periodUid;
oldTimeline.getPeriodPosition( boolean playingPeriodChanged = !oldPeriodUid.equals(castNonNull(periodPosition).first);
window, MediaPeriodId newPeriodId =
period, playingPeriodChanged ? new MediaPeriodId(periodPosition.first) : playbackInfo.periodId;
currentWindowIndex, long newContentPositionUs = periodPosition.second;
C.msToUs(currentPositionMs), long oldContentPositionUs = C.msToUs(getContentPosition());
/* defaultPositionProjectionUs= */ 0); if (!oldTimeline.isEmpty()) {
Object periodUid = Util.castNonNull(periodPosition).first; oldContentPositionUs -=
if (maskingTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) { oldTimeline.getPeriodByUid(oldPeriodUid, period).getPositionInWindowUs();
// Get the window index of the current period that exists in the new timeline also. }
maskingWindowIndex = maskingTimeline.getPeriodByUid(periodUid, period).windowIndex;
maskingPeriodIndex = maskingTimeline.getIndexOfPeriod(periodUid); if (playingPeriodChanged || newContentPositionUs < oldContentPositionUs) {
maskingWindowPositionMs = currentPositionMs; checkState(!newPeriodId.isAd());
} else { // The playing period changes or a backwards seek within the playing period occurs.
// Period uid not found in new timeline. Try to get subsequent period. playbackInfo =
@Nullable playbackInfo.copyWithNewPosition(
Object nextPeriodUid = newPeriodId,
ExoPlayerImplInternal.resolveSubsequentPeriod( /* positionUs= */ newContentPositionUs,
window, /* requestedContentPositionUs= */ newContentPositionUs,
period, /* totalBufferedDurationUs= */ 0,
repeatMode, playingPeriodChanged ? TrackGroupArray.EMPTY : playbackInfo.trackGroups,
shuffleModeEnabled, playingPeriodChanged ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult);
periodUid, playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(newPeriodId);
oldTimeline, playbackInfo.bufferedPositionUs = newContentPositionUs;
maskingTimeline); } else if (newContentPositionUs == oldContentPositionUs) {
if (nextPeriodUid != null) { // Period position remains unchanged.
// Set masking to the default position of the window of the subsequent period. int loadingPeriodIndex =
maskingWindowIndex = maskingTimeline.getPeriodByUid(nextPeriodUid, period).windowIndex; timeline.getIndexOfPeriod(playbackInfo.loadingMediaPeriodId.periodUid);
maskingPeriodIndex = maskingTimeline.getWindow(maskingWindowIndex, window).firstPeriodIndex; if (loadingPeriodIndex == C.INDEX_UNSET
maskingWindowPositionMs = window.getDefaultPositionMs(); || timeline.getPeriod(loadingPeriodIndex, period).windowIndex
} else { != timeline.getPeriodByUid(newPeriodId.periodUid, period).windowIndex) {
// Reset if no subsequent period is found. // Discard periods after the playing period, if the loading period is discarded or the
maskWithDefaultPosition(maskingTimeline); // playing and loading period are not in the same window.
timeline.getPeriodByUid(newPeriodId.periodUid, period);
long maskedBufferedPositionUs =
newPeriodId.isAd()
? period.getAdDurationUs(newPeriodId.adGroupIndex, newPeriodId.adIndexInAdGroup)
: period.durationUs;
playbackInfo =
playbackInfo.copyWithNewPosition(
newPeriodId,
/* positionUs= */ playbackInfo.positionUs,
/* requestedContentPositionUs= */ playbackInfo.positionUs,
/* totalBufferedDurationUs= */ maskedBufferedPositionUs - playbackInfo.positionUs,
playbackInfo.trackGroups,
playbackInfo.trackSelectorResult);
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(newPeriodId);
playbackInfo.bufferedPositionUs = maskedBufferedPositionUs;
} }
} else {
checkState(!newPeriodId.isAd());
// A forward seek within the playing period (timeline did not change).
long maskedTotalBufferedDurationUs =
Math.max(
0,
playbackInfo.totalBufferedDurationUs - (newContentPositionUs - oldContentPositionUs));
long maskedBufferedPositionUs = playbackInfo.bufferedPositionUs;
if (playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId)) {
maskedBufferedPositionUs = newContentPositionUs + maskedTotalBufferedDurationUs;
}
playbackInfo =
playbackInfo.copyWithNewPosition(
newPeriodId,
/* positionUs= */ newContentPositionUs,
/* requestedContentPositionUs= */ newContentPositionUs,
maskedTotalBufferedDurationUs,
playbackInfo.trackGroups,
playbackInfo.trackSelectorResult);
playbackInfo.bufferedPositionUs = maskedBufferedPositionUs;
} }
return playbackInfo; return playbackInfo;
} }
private void maskWindowIndexAndPositionForSeek( @Nullable
Timeline timeline, int windowIndex, long positionMs) { private Pair<Object, Long> getPeriodPositionAfterTimelineChanged(
maskingWindowIndex = windowIndex; Timeline oldTimeline, Timeline newTimeline) {
if (timeline.isEmpty()) { long currentPositionMs = getContentPosition();
maskingWindowPositionMs = positionMs == C.TIME_UNSET ? 0 : positionMs; if (oldTimeline.isEmpty() || newTimeline.isEmpty()) {
maskingPeriodIndex = 0; boolean isCleared = !oldTimeline.isEmpty() && newTimeline.isEmpty();
} else if (windowIndex >= timeline.getWindowCount()) { return getPeriodPositionOrMaskWindowPosition(
// An initial seek now proves to be invalid in the actual timeline. newTimeline,
maskWithDefaultPosition(timeline); isCleared ? C.INDEX_UNSET : getCurrentWindowIndexInternal(),
isCleared ? C.TIME_UNSET : currentPositionMs);
}
int currentWindowIndex = getCurrentWindowIndex();
@Nullable
Pair<Object, Long> oldPeriodPosition =
oldTimeline.getPeriodPosition(
window, period, currentWindowIndex, C.msToUs(currentPositionMs));
Object periodUid = castNonNull(oldPeriodPosition).first;
if (newTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) {
// The old period position is still available in the new timeline.
return oldPeriodPosition;
}
// Period uid not found in new timeline. Try to get subsequent period.
@Nullable
Object nextPeriodUid =
ExoPlayerImplInternal.resolveSubsequentPeriod(
window, period, repeatMode, shuffleModeEnabled, periodUid, oldTimeline, newTimeline);
if (nextPeriodUid != null) {
// Reset position to the default position of the window of the subsequent period.
newTimeline.getPeriodByUid(nextPeriodUid, period);
return getPeriodPositionOrMaskWindowPosition(
newTimeline,
period.windowIndex,
newTimeline.getWindow(period.windowIndex, window).getDefaultPositionMs());
} else { } else {
long windowPositionUs = // No subsequent period found and the new timeline is not empty. Use the default position.
positionMs == C.TIME_UNSET return getPeriodPositionOrMaskWindowPosition(
? timeline.getWindow(windowIndex, window).getDefaultPositionUs() newTimeline, /* windowIndex= */ C.INDEX_UNSET, /* windowPositionMs= */ C.TIME_UNSET);
: C.msToUs(positionMs);
Pair<Object, Long> periodUidAndPosition =
timeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
maskingWindowPositionMs = C.usToMs(windowPositionUs);
maskingPeriodIndex = timeline.getIndexOfPeriod(periodUidAndPosition.first);
} }
} }
private void maskWithCurrentPosition() { @Nullable
maskingWindowIndex = getCurrentWindowIndexInternal(); private Pair<Object, Long> getPeriodPositionOrMaskWindowPosition(
maskingPeriodIndex = getCurrentPeriodIndex(); Timeline timeline, int windowIndex, long windowPositionMs) {
maskingWindowPositionMs = getCurrentPosition();
}
private void maskWithDefaultPosition(Timeline timeline) {
if (timeline.isEmpty()) { if (timeline.isEmpty()) {
resetMaskingPosition(); // If empty we store the initial seek in the masking variables.
return; maskingWindowIndex = windowIndex;
maskingWindowPositionMs = windowPositionMs == C.TIME_UNSET ? 0 : windowPositionMs;
maskingPeriodIndex = 0;
return null;
} }
maskingWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); if (windowIndex == C.INDEX_UNSET || windowIndex >= timeline.getWindowCount()) {
timeline.getWindow(maskingWindowIndex, window); // Use default position of timeline if window index still unset or if a previous initial seek
maskingWindowPositionMs = window.getDefaultPositionMs(); // now turns out to be invalid.
maskingPeriodIndex = window.firstPeriodIndex; windowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled);
} windowPositionMs = timeline.getWindow(windowIndex, window).getDefaultPositionMs();
}
private void resetMaskingPosition() { return timeline.getPeriodPosition(window, period, windowIndex, C.msToUs(windowPositionMs));
maskingWindowIndex = C.INDEX_UNSET;
maskingWindowPositionMs = 0;
maskingPeriodIndex = 0;
} }
private void notifyListeners(ListenerInvocation listenerInvocation) { private void notifyListeners(ListenerInvocation listenerInvocation) {
@ -1251,6 +1324,12 @@ import java.util.concurrent.TimeoutException;
} }
} }
private void resetMaskingPosition() {
maskingWindowIndex = C.INDEX_UNSET;
maskingWindowPositionMs = 0;
maskingPeriodIndex = 0;
}
private long periodPositionUsToWindowPositionMs(MediaPeriodId periodId, long positionUs) { private long periodPositionUsToWindowPositionMs(MediaPeriodId periodId, long positionUs) {
long positionMs = C.usToMs(positionUs); long positionMs = C.usToMs(positionUs);
playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period);
@ -1258,10 +1337,6 @@ import java.util.concurrent.TimeoutException;
return positionMs; return positionMs;
} }
private boolean shouldMaskPosition() {
return playbackInfo.timeline.isEmpty() || pendingOperationAcks > 0;
}
private final class PlaybackUpdateListenerImpl private final class PlaybackUpdateListenerImpl
implements ExoPlayerImplInternal.PlaybackUpdateListener, Handler.Callback { implements ExoPlayerImplInternal.PlaybackUpdateListener, Handler.Callback {
private static final int MSG_PLAYBACK_INFO_CHANGED = 0; private static final int MSG_PLAYBACK_INFO_CHANGED = 0;

View File

@ -421,7 +421,7 @@ public final class AnalyticsCollectorTest {
assertThat(loadingEvents).hasSize(4); assertThat(loadingEvents).hasSize(4);
assertThat(loadingEvents).containsAtLeast(period0, period0).inOrder(); assertThat(loadingEvents).containsAtLeast(period0, period0).inOrder();
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
.containsExactly(period0, period1) .containsExactly(period0, period1, period1)
.inOrder(); .inOrder();
assertThat(listener.getEvents(EVENT_LOAD_STARTED)) assertThat(listener.getEvents(EVENT_LOAD_STARTED))
.containsExactly( .containsExactly(
@ -887,7 +887,7 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_LOADING_CHANGED)) assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
.containsExactly(period0Seq0, period0Seq0, period0Seq0, period0Seq0); .containsExactly(period0Seq0, period0Seq0, period0Seq0, period0Seq0);
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED)) assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
.containsExactly(period0Seq0, period0Seq1) .containsExactly(period0Seq0, period0Seq1, period0Seq1)
.inOrder(); .inOrder();
assertThat(listener.getEvents(EVENT_LOAD_STARTED)) assertThat(listener.getEvents(EVENT_LOAD_STARTED))
.containsExactly(WINDOW_0 /* manifest */, period0Seq0 /* media */, period1Seq1 /* media */) .containsExactly(WINDOW_0 /* manifest */, period0Seq0 /* media */, period1Seq1 /* media */)

View File

@ -72,6 +72,7 @@ public class FakeMediaPeriod implements MediaPeriod {
private boolean prepared; private boolean prepared;
private long seekOffsetUs; private long seekOffsetUs;
private long discontinuityPositionUs; private long discontinuityPositionUs;
private long bufferedPositionUs;
/** /**
* Constructs a FakeMediaPeriod with a single sample for each track in {@code trackGroupArray}. * Constructs a FakeMediaPeriod with a single sample for each track in {@code trackGroupArray}.
@ -149,6 +150,7 @@ public class FakeMediaPeriod implements MediaPeriod {
this.drmEventDispatcher = drmEventDispatcher; this.drmEventDispatcher = drmEventDispatcher;
this.deferOnPrepared = deferOnPrepared; this.deferOnPrepared = deferOnPrepared;
this.trackDataFactory = trackDataFactory; this.trackDataFactory = trackDataFactory;
this.bufferedPositionUs = C.TIME_END_OF_SOURCE;
discontinuityPositionUs = C.TIME_UNSET; discontinuityPositionUs = C.TIME_UNSET;
sampleStreams = new ArrayList<>(); sampleStreams = new ArrayList<>();
fakePreparationLoadTaskId = LoadEventInfo.getNewId(); fakePreparationLoadTaskId = LoadEventInfo.getNewId();
@ -283,7 +285,11 @@ public class FakeMediaPeriod implements MediaPeriod {
@Override @Override
public long getBufferedPositionUs() { public long getBufferedPositionUs() {
assertThat(prepared).isTrue(); assertThat(prepared).isTrue();
return C.TIME_END_OF_SOURCE; return bufferedPositionUs;
}
public void setBufferedPositionUs(long bufferedPositionUs) {
this.bufferedPositionUs = bufferedPositionUs;
} }
@Override @Override
@ -293,6 +299,9 @@ public class FakeMediaPeriod implements MediaPeriod {
for (SampleStream sampleStream : sampleStreams) { for (SampleStream sampleStream : sampleStreams) {
seekSampleStream(sampleStream, seekPositionUs); seekSampleStream(sampleStream, seekPositionUs);
} }
if (bufferedPositionUs != C.TIME_END_OF_SOURCE && seekPositionUs > bufferedPositionUs) {
bufferedPositionUs = seekPositionUs;
}
return seekPositionUs; return seekPositionUs;
} }