Commit d62688cf by bachinger Committed by Oliver Woodman

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
parent 683d630e
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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;
}
Object oldPeriodUid = playbackInfo.periodId.periodUid;
boolean playingPeriodChanged = !oldPeriodUid.equals(castNonNull(periodPosition).first);
MediaPeriodId newPeriodId =
playingPeriodChanged ? new MediaPeriodId(periodPosition.first) : playbackInfo.periodId;
long newContentPositionUs = periodPosition.second;
long oldContentPositionUs = C.msToUs(getContentPosition());
if (!oldTimeline.isEmpty()) {
oldContentPositionUs -=
oldTimeline.getPeriodByUid(oldPeriodUid, period).getPositionInWindowUs();
}
if (playingPeriodChanged || newContentPositionUs < oldContentPositionUs) {
checkState(!newPeriodId.isAd());
// The playing period changes or a backwards seek within the playing period occurs.
playbackInfo =
playbackInfo.copyWithNewPosition(
newPeriodId,
/* positionUs= */ newContentPositionUs,
/* requestedContentPositionUs= */ newContentPositionUs,
/* totalBufferedDurationUs= */ 0,
playingPeriodChanged ? TrackGroupArray.EMPTY : playbackInfo.trackGroups,
playingPeriodChanged ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult);
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(newPeriodId);
playbackInfo.bufferedPositionUs = newContentPositionUs;
} else if (newContentPositionUs == oldContentPositionUs) {
// Period position remains unchanged.
int loadingPeriodIndex =
timeline.getIndexOfPeriod(playbackInfo.loadingMediaPeriodId.periodUid);
if (loadingPeriodIndex == C.INDEX_UNSET
|| timeline.getPeriod(loadingPeriodIndex, period).windowIndex
!= timeline.getPeriodByUid(newPeriodId.periodUid, period).windowIndex) {
// Discard periods after the playing period, if the loading period is discarded or the
// 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;
} }
@Nullable
private Pair<Object, Long> getPeriodPositionAfterTimelineChanged(
Timeline oldTimeline, Timeline newTimeline) {
long currentPositionMs = getContentPosition();
if (oldTimeline.isEmpty() || newTimeline.isEmpty()) {
boolean isCleared = !oldTimeline.isEmpty() && newTimeline.isEmpty();
return getPeriodPositionOrMaskWindowPosition(
newTimeline,
isCleared ? C.INDEX_UNSET : getCurrentWindowIndexInternal(),
isCleared ? C.TIME_UNSET : currentPositionMs);
}
int currentWindowIndex = getCurrentWindowIndex();
@Nullable @Nullable
Pair<Object, Long> periodPosition = Pair<Object, Long> oldPeriodPosition =
oldTimeline.getPeriodPosition( oldTimeline.getPeriodPosition(
window, window, period, currentWindowIndex, C.msToUs(currentPositionMs));
period, Object periodUid = castNonNull(oldPeriodPosition).first;
currentWindowIndex, if (newTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) {
C.msToUs(currentPositionMs), // The old period position is still available in the new timeline.
/* defaultPositionProjectionUs= */ 0); return oldPeriodPosition;
Object periodUid = Util.castNonNull(periodPosition).first; }
if (maskingTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) {
// 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);
maskingWindowPositionMs = currentPositionMs;
} else {
// Period uid not found in new timeline. Try to get subsequent period. // Period uid not found in new timeline. Try to get subsequent period.
@Nullable @Nullable
Object nextPeriodUid = Object nextPeriodUid =
ExoPlayerImplInternal.resolveSubsequentPeriod( ExoPlayerImplInternal.resolveSubsequentPeriod(
window, window, period, repeatMode, shuffleModeEnabled, periodUid, oldTimeline, newTimeline);
period,
repeatMode,
shuffleModeEnabled,
periodUid,
oldTimeline,
maskingTimeline);
if (nextPeriodUid != null) { if (nextPeriodUid != null) {
// Set masking to the default position of the window of the subsequent period. // Reset position to the default position of the window of the subsequent period.
maskingWindowIndex = maskingTimeline.getPeriodByUid(nextPeriodUid, period).windowIndex; newTimeline.getPeriodByUid(nextPeriodUid, period);
maskingPeriodIndex = maskingTimeline.getWindow(maskingWindowIndex, window).firstPeriodIndex; return getPeriodPositionOrMaskWindowPosition(
maskingWindowPositionMs = window.getDefaultPositionMs(); newTimeline,
period.windowIndex,
newTimeline.getWindow(period.windowIndex, window).getDefaultPositionMs());
} else { } else {
// Reset if no subsequent period is found. // No subsequent period found and the new timeline is not empty. Use the default position.
maskWithDefaultPosition(maskingTimeline); return getPeriodPositionOrMaskWindowPosition(
newTimeline, /* windowIndex= */ C.INDEX_UNSET, /* windowPositionMs= */ C.TIME_UNSET);
} }
} }
return playbackInfo;
}
private void maskWindowIndexAndPositionForSeek( @Nullable
Timeline timeline, int windowIndex, long positionMs) { private Pair<Object, Long> getPeriodPositionOrMaskWindowPosition(
maskingWindowIndex = windowIndex; Timeline timeline, int windowIndex, long windowPositionMs) {
if (timeline.isEmpty()) { if (timeline.isEmpty()) {
maskingWindowPositionMs = positionMs == C.TIME_UNSET ? 0 : positionMs; // If empty we store the initial seek in the masking variables.
maskingWindowIndex = windowIndex;
maskingWindowPositionMs = windowPositionMs == C.TIME_UNSET ? 0 : windowPositionMs;
maskingPeriodIndex = 0; maskingPeriodIndex = 0;
} else if (windowIndex >= timeline.getWindowCount()) { return null;
// An initial seek now proves to be invalid in the actual timeline.
maskWithDefaultPosition(timeline);
} else {
long windowPositionUs =
positionMs == C.TIME_UNSET
? timeline.getWindow(windowIndex, window).getDefaultPositionUs()
: 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() {
maskingWindowIndex = getCurrentWindowIndexInternal();
maskingPeriodIndex = getCurrentPeriodIndex();
maskingWindowPositionMs = getCurrentPosition();
} }
if (windowIndex == C.INDEX_UNSET || windowIndex >= timeline.getWindowCount()) {
private void maskWithDefaultPosition(Timeline timeline) { // Use default position of timeline if window index still unset or if a previous initial seek
if (timeline.isEmpty()) { // now turns out to be invalid.
resetMaskingPosition(); windowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled);
return; windowPositionMs = timeline.getWindow(windowIndex, window).getDefaultPositionMs();
} }
maskingWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); return timeline.getPeriodPosition(window, period, windowIndex, C.msToUs(windowPositionMs));
timeline.getWindow(maskingWindowIndex, window);
maskingWindowPositionMs = window.getDefaultPositionMs();
maskingPeriodIndex = window.firstPeriodIndex;
}
private void resetMaskingPosition() {
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; ...@@ -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; ...@@ -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;
......
...@@ -32,6 +32,7 @@ import android.content.Context; ...@@ -32,6 +32,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import android.view.Surface; import android.view.Surface;
import android.view.View; import android.view.View;
...@@ -1062,58 +1063,87 @@ public final class ExoPlayerTest { ...@@ -1062,58 +1063,87 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void stopDoesNotResetPosition() throws Exception { public void stop_withoutReset_doesNotResetPosition_correctMasking() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1]; int[] currentWindowIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
long[] totalBufferedDuration = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
final FakeMediaSource mediaSource =
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.pause() .pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ 1000)
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 50)
.stop()
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
positionHolder[0] = player.getCurrentPosition(); currentWindowIndex[0] = player.getCurrentWindowIndex();
currentPosition[0] = player.getCurrentPosition();
bufferedPosition[0] = player.getBufferedPosition();
totalBufferedDuration[0] = player.getTotalBufferedDuration();
player.stop(/* reset= */ false);
currentWindowIndex[1] = player.getCurrentWindowIndex();
currentPosition[1] = player.getCurrentPosition();
bufferedPosition[1] = player.getBufferedPosition();
totalBufferedDuration[1] = player.getTotalBufferedDuration();
}
})
.waitForPlaybackState(Player.STATE_IDLE)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndex[2] = player.getCurrentWindowIndex();
currentPosition[2] = player.getCurrentPosition();
bufferedPosition[2] = player.getBufferedPosition();
totalBufferedDuration[2] = player.getTotalBufferedDuration();
} }
}) })
.build(); .build();
ExoPlayerTestRunner testRunner = ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context) new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline) .setMediaSources(mediaSource, mediaSource)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.build() .build()
.start() .start()
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesSame(placeholderTimeline, timeline);
testRunner.assertTimelineChangeReasonsEqual( testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
testRunner.assertNoPositionDiscontinuities(); testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
assertThat(positionHolder[0]).isAtLeast(50L);
assertThat(currentWindowIndex[0]).isEqualTo(1);
assertThat(currentPosition[0]).isEqualTo(1000);
assertThat(bufferedPosition[0]).isEqualTo(10000);
assertThat(totalBufferedDuration[0]).isEqualTo(9000);
assertThat(currentWindowIndex[1]).isEqualTo(1);
assertThat(currentPosition[1]).isEqualTo(1000);
assertThat(bufferedPosition[1]).isEqualTo(1000);
assertThat(totalBufferedDuration[1]).isEqualTo(0);
assertThat(currentWindowIndex[2]).isEqualTo(1);
assertThat(currentPosition[2]).isEqualTo(1000);
assertThat(bufferedPosition[2]).isEqualTo(1000);
assertThat(totalBufferedDuration[2]).isEqualTo(0);
} }
@Test @Test
public void stopWithoutResetDoesNotResetPosition() throws Exception { public void stop_withoutReset_releasesMediaSource() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1]; final FakeMediaSource mediaSource =
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 50)
.stop(/* reset= */ false) .stop(/* reset= */ false)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
positionHolder[0] = player.getCurrentPosition();
}
})
.build(); .build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context) new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline) .setTimeline(timeline)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
...@@ -1121,89 +1151,167 @@ public final class ExoPlayerTest { ...@@ -1121,89 +1151,167 @@ public final class ExoPlayerTest {
.start() .start()
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesSame(placeholderTimeline, timeline);
testRunner.assertTimelineChangeReasonsEqual( mediaSource.assertReleased();
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
testRunner.assertNoPositionDiscontinuities();
assertThat(positionHolder[0]).isAtLeast(50L);
} }
@Test @Test
public void stopWithResetDoesResetPosition() throws Exception { public void stop_withReset_doesResetPosition_correctMasking() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1]; int[] currentWindowIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
long[] totalBufferedDuration = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
final FakeMediaSource mediaSource =
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.pause() .pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ 1000)
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 50)
.stop(/* reset= */ true)
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
positionHolder[0] = player.getCurrentPosition(); currentWindowIndex[0] = player.getCurrentWindowIndex();
currentPosition[0] = player.getCurrentPosition();
bufferedPosition[0] = player.getBufferedPosition();
totalBufferedDuration[0] = player.getTotalBufferedDuration();
player.stop(/* reset= */ true);
currentWindowIndex[1] = player.getCurrentWindowIndex();
currentPosition[1] = player.getCurrentPosition();
bufferedPosition[1] = player.getBufferedPosition();
totalBufferedDuration[1] = player.getTotalBufferedDuration();
}
})
.waitForPlaybackState(Player.STATE_IDLE)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndex[2] = player.getCurrentWindowIndex();
currentPosition[2] = player.getCurrentPosition();
bufferedPosition[2] = player.getBufferedPosition();
totalBufferedDuration[2] = player.getTotalBufferedDuration();
} }
}) })
.build(); .build();
ExoPlayerTestRunner testRunner = ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context) new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline) .setMediaSources(mediaSource, mediaSource)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.build() .build()
.start() .start()
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS);
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesSame(placeholderTimeline, timeline, Timeline.EMPTY);
testRunner.assertTimelineChangeReasonsEqual( testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE, Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
testRunner.assertNoPositionDiscontinuities(); testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
assertThat(positionHolder[0]).isEqualTo(0);
assertThat(currentWindowIndex[0]).isEqualTo(1);
assertThat(currentPosition[0]).isGreaterThan(0);
assertThat(bufferedPosition[0]).isEqualTo(10000);
assertThat(totalBufferedDuration[0]).isEqualTo(10000 - currentPosition[0]);
assertThat(currentWindowIndex[1]).isEqualTo(0);
assertThat(currentPosition[1]).isEqualTo(0);
assertThat(bufferedPosition[1]).isEqualTo(0);
assertThat(totalBufferedDuration[1]).isEqualTo(0);
assertThat(currentWindowIndex[2]).isEqualTo(0);
assertThat(currentPosition[2]).isEqualTo(0);
assertThat(bufferedPosition[2]).isEqualTo(0);
assertThat(totalBufferedDuration[2]).isEqualTo(0);
} }
@Test @Test
public void stopWithoutResetReleasesMediaSource() throws Exception { public void stop_withReset_releasesMediaSource() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final FakeMediaSource mediaSource = final FakeMediaSource mediaSource =
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT); new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ false) .stop(/* reset= */ true)
.build(); .build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context) new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline) .setTimeline(timeline)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.build() .build()
.start() .start()
.blockUntilActionScheduleFinished(TIMEOUT_MS); .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
mediaSource.assertReleased(); mediaSource.assertReleased();
testRunner.blockUntilEnded(TIMEOUT_MS);
} }
@Test @Test
public void stopWithResetReleasesMediaSource() throws Exception { public void release_correctMasking() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1); Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
int[] currentWindowIndex = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
long[] totalBufferedDuration = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
final FakeMediaSource mediaSource = final FakeMediaSource mediaSource =
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT); new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ 1000)
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ true) .executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndex[0] = player.getCurrentWindowIndex();
currentPosition[0] = player.getCurrentPosition();
bufferedPosition[0] = player.getBufferedPosition();
totalBufferedDuration[0] = player.getTotalBufferedDuration();
player.release();
currentWindowIndex[1] = player.getCurrentWindowIndex();
currentPosition[1] = player.getCurrentPosition();
bufferedPosition[1] = player.getBufferedPosition();
totalBufferedDuration[1] = player.getTotalBufferedDuration();
}
})
.waitForPlaybackState(Player.STATE_IDLE)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndex[2] = player.getCurrentWindowIndex();
currentPosition[2] = player.getCurrentPosition();
bufferedPosition[2] = player.getBufferedPosition();
totalBufferedDuration[2] = player.getTotalBufferedDuration();
}
})
.build(); .build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context) new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline) .setMediaSources(mediaSource, mediaSource)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.build() .build()
.start() .start()
.blockUntilActionScheduleFinished(TIMEOUT_MS); .blockUntilActionScheduleFinished(TIMEOUT_MS);
mediaSource.assertReleased();
testRunner.blockUntilEnded(TIMEOUT_MS); assertThat(currentWindowIndex[0]).isEqualTo(1);
assertThat(currentPosition[0]).isGreaterThan(0);
assertThat(bufferedPosition[0]).isEqualTo(10000);
assertThat(totalBufferedDuration[0]).isEqualTo(10000 - currentPosition[0]);
assertThat(currentWindowIndex[1]).isEqualTo(1);
assertThat(currentPosition[1]).isEqualTo(currentPosition[0]);
assertThat(bufferedPosition[1]).isEqualTo(1000);
assertThat(totalBufferedDuration[1]).isEqualTo(0);
assertThat(currentWindowIndex[2]).isEqualTo(1);
assertThat(currentPosition[2]).isEqualTo(currentPosition[0]);
assertThat(bufferedPosition[2]).isEqualTo(1000);
assertThat(totalBufferedDuration[2]).isEqualTo(0);
} }
@Test @Test
...@@ -3533,26 +3641,34 @@ public final class ExoPlayerTest { ...@@ -3533,26 +3641,34 @@ public final class ExoPlayerTest {
FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline); FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline);
LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2); LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2);
final int[] windowIndex = {C.INDEX_UNSET}; final int[] windowIndex = {C.INDEX_UNSET};
final long[] positionMs = {C.TIME_UNSET}; final long[] positionMs = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.pause() .pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 5000) .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 3000)
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
positionMs[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
//noinspection deprecation
player.prepare(mediaSource); player.prepare(mediaSource);
player.seekTo(/* positionMs= */ 5000); player.seekTo(/* positionMs= */ 7000);
positionMs[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
} }
}) })
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
windowIndex[0] = player.getCurrentWindowIndex(); windowIndex[0] = player.getCurrentWindowIndex();
positionMs[0] = player.getCurrentPosition(); positionMs[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
} }
}) })
.build(); .build();
...@@ -3564,7 +3680,13 @@ public final class ExoPlayerTest { ...@@ -3564,7 +3680,13 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS); .blockUntilActionScheduleFinished(TIMEOUT_MS);
assertThat(windowIndex[0]).isEqualTo(0); assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isAtLeast(5000L); assertThat(positionMs[0]).isAtLeast(3000L);
assertThat(positionMs[1]).isEqualTo(7000L);
assertThat(positionMs[2]).isEqualTo(7000L);
assertThat(bufferedPositions[0]).isAtLeast(3000L);
assertThat(bufferedPositions[1]).isEqualTo(7000L);
assertThat(bufferedPositions[2])
.isEqualTo(fakeTimeline.getWindow(0, new Window()).getDurationMs());
} }
@Test @Test
...@@ -3573,26 +3695,34 @@ public final class ExoPlayerTest { ...@@ -3573,26 +3695,34 @@ public final class ExoPlayerTest {
FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline); FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline);
LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2); LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2);
final int[] windowIndex = {C.INDEX_UNSET}; final int[] windowIndex = {C.INDEX_UNSET};
final long[] positionMs = {C.TIME_UNSET}; final long[] positionMs = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.pause() .pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET) .seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 5000) .playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 3000)
.pause()
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
player.setMediaSource(mediaSource, /* startPositionMs= */ 5000); positionMs[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
player.setMediaSource(mediaSource, /* startPositionMs= */ 7000);
player.prepare(); player.prepare();
positionMs[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
} }
}) })
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
windowIndex[0] = player.getCurrentWindowIndex(); windowIndex[0] = player.getCurrentWindowIndex();
positionMs[0] = player.getCurrentPosition(); positionMs[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
} }
}) })
.build(); .build();
...@@ -3604,167 +3734,979 @@ public final class ExoPlayerTest { ...@@ -3604,167 +3734,979 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS); .blockUntilActionScheduleFinished(TIMEOUT_MS);
assertThat(windowIndex[0]).isEqualTo(0); assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isAtLeast(5000L); assertThat(positionMs[0]).isAtLeast(3000);
assertThat(positionMs[1]).isEqualTo(7000);
assertThat(positionMs[2]).isEqualTo(7000);
assertThat(bufferedPositions[0]).isAtLeast(3000);
assertThat(bufferedPositions[1]).isEqualTo(7000);
assertThat(bufferedPositions[2])
.isEqualTo(fakeTimeline.getWindow(0, new Window()).getDurationMs());
} }
@Test @Test
public void becomingNoisyIgnoredIfBecomingNoisyHandlingIsDisabled() throws Exception { public void seekTo_singlePeriod_correctMaskingPosition() throws Exception {
CountDownLatch becomingNoisyHandlingDisabled = new CountDownLatch(1); final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
CountDownLatch becomingNoisyDelivered = new CountDownLatch(1); final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
PlayerStateGrabber playerStateGrabber = new PlayerStateGrabber(); final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule = final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
new ActionSchedule.Builder(TAG)
.executeRunnable( runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
player.setHandleAudioBecomingNoisy(false); player.seekTo(9000);
becomingNoisyHandlingDisabled.countDown();
// Wait for the broadcast to be delivered from the main thread.
try {
becomingNoisyDelivered.await();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
} }
} },
}) /* pauseWindowIndex= */ 0,
.delay(1) // Handle pending messages on the playback thread. windowIndex,
.executeRunnable(playerStateGrabber) positionMs,
.build(); bufferedPositions,
totalBufferedDuration,
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200));
ExoPlayerTestRunner testRunner = assertThat(windowIndex[0]).isEqualTo(0);
new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start(); assertThat(positionMs[0]).isEqualTo(9000);
becomingNoisyHandlingDisabled.await(); assertThat(bufferedPositions[0]).isEqualTo(9200);
deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); assertThat(totalBufferedDuration[0]).isEqualTo(200);
becomingNoisyDelivered.countDown();
testRunner.blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS); assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(playerStateGrabber.playWhenReady).isTrue(); assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(9200);
assertThat(totalBufferedDuration[1]).isEqualTo(200);
} }
@Test @Test
public void pausesWhenBecomingNoisyIfBecomingNoisyHandlingIsEnabled() throws Exception { public void seekTo_singlePeriod_beyondBufferedData_correctMaskingPosition() throws Exception {
CountDownLatch becomingNoisyHandlingEnabled = new CountDownLatch(1); final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
ActionSchedule actionSchedule = final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
new ActionSchedule.Builder(TAG) final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
.executeRunnable( final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
player.setHandleAudioBecomingNoisy(true); player.seekTo(9200);
becomingNoisyHandlingEnabled.countDown();
} }
}) },
.waitForPlayWhenReady(false) // Becoming noisy should set playWhenReady = false /* pauseWindowIndex= */ 0,
.play() windowIndex,
.build(); positionMs,
bufferedPositions,
totalBufferedDuration,
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200));
ExoPlayerTestRunner testRunner = assertThat(windowIndex[0]).isEqualTo(0);
new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start(); assertThat(positionMs[0]).isEqualTo(9200);
becomingNoisyHandlingEnabled.await(); assertThat(bufferedPositions[0]).isEqualTo(9200);
deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); assertThat(totalBufferedDuration[0]).isEqualTo(0);
// If the player fails to handle becoming noisy, blockUntilActionScheduleFinished will time out assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
// and throw, causing the test to fail. assertThat(positionMs[1]).isEqualTo(positionMs[0]);
testRunner.blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS); assertThat(bufferedPositions[1]).isEqualTo(9200);
assertThat(totalBufferedDuration[1]).isEqualTo(0);
} }
// Disabled until the flag to throw exceptions for [internal: b/144538905] is enabled by default.
@Ignore
@Test @Test
public void loadControlNeverWantsToLoad_throwsIllegalStateException() { public void seekTo_backwardsSinglePeriod_correctMaskingPosition() throws Exception {
LoadControl neverLoadingLoadControl = final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
new DefaultLoadControl() { final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
@Override final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
public boolean shouldContinueLoading( final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) {
return false;
}
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override @Override
public boolean shouldStartPlayback( public void run(SimpleExoPlayer player) {
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) { player.seekTo(1000);
return true;
} }
}; },
/* pauseWindowIndex= */ 0,
// Use chunked data to ensure the player actually needs to continue loading and playing. windowIndex,
FakeAdaptiveDataSet.Factory dataSetFactory = positionMs,
new FakeAdaptiveDataSet.Factory( bufferedPositions,
/* chunkDurationUs= */ 500_000, /* bitratePercentStdDev= */ 10.0); totalBufferedDuration,
MediaSource chunkedMediaSource = createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200));
new FakeAdaptiveMediaSource(
new FakeTimeline(/* windowCount= */ 1),
new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT)),
new FakeChunkSource.Factory(dataSetFactory, new FakeDataSource.Factory()));
ExoPlaybackException exception = assertThat(windowIndex[0]).isEqualTo(0);
assertThrows( assertThat(positionMs[0]).isEqualTo(1000);
ExoPlaybackException.class, assertThat(bufferedPositions[0]).isEqualTo(1000);
() -> assertThat(totalBufferedDuration[0]).isEqualTo(0);
new ExoPlayerTestRunner.Builder(context)
.setLoadControl(neverLoadingLoadControl)
.setMediaSources(chunkedMediaSource)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS));
assertThat(exception.type).isEqualTo(ExoPlaybackException.TYPE_UNEXPECTED);
assertThat(exception.getUnexpectedException()).isInstanceOf(IllegalStateException.class);
} }
@Test @Test
public void public void seekTo_backwardsMultiplePeriods_correctMaskingPosition() throws Exception {
nextLoadPositionExceedingLoadControlMaxBuffer_whileCurrentLoadInProgress_doesNotThrowException() final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
throws Exception { final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
long maxBufferUs = 2 * C.MICROS_PER_SECOND; final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
LoadControl loadControlWithMaxBufferUs = final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
new DefaultLoadControl() {
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override @Override
public boolean shouldContinueLoading( public void run(SimpleExoPlayer player) {
long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) { player.seekTo(0, 1000);
return bufferedDurationUs < maxBufferUs;
} }
},
/* pauseWindowIndex= */ 1,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200));
@Override assertThat(windowIndex[0]).isEqualTo(0);
public boolean shouldStartPlayback( assertThat(positionMs[0]).isEqualTo(1000);
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) { assertThat(bufferedPositions[0]).isEqualTo(1000);
return true; assertThat(totalBufferedDuration[0]).isEqualTo(0);
} }
};
MediaSource mediaSourceWithLoadInProgress = @Test
new FakeMediaSource( public void seekTo_toUnbufferedPeriod_correctMaskingPosition() throws Exception {
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT) { final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
@Override final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
protected FakeMediaPeriod createFakeMediaPeriod( final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
MediaPeriodId id, final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
TrackGroupArray trackGroupArray,
Allocator allocator, runPositionMaskingCapturingActionSchedule(
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, new PlayerRunnable() {
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
@Nullable TransferListener transferListener) {
return new FakeMediaPeriod(
trackGroupArray,
TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
mediaSourceEventDispatcher) {
@Override @Override
public long getBufferedPositionUs() { public void run(SimpleExoPlayer player) {
// Pretend not to have buffered data yet. player.seekTo(2, 1000);
return 0;
} }
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0));
@Override assertThat(windowIndex[0]).isEqualTo(2);
public long getNextLoadPositionUs() { assertThat(positionMs[0]).isEqualTo(1000);
// Set next load position beyond the maxBufferUs configured in the LoadControl. assertThat(bufferedPositions[0]).isEqualTo(1000);
return Long.MAX_VALUE; assertThat(totalBufferedDuration[0]).isEqualTo(0);
assertThat(windowIndex[1]).isEqualTo(2);
assertThat(positionMs[1]).isEqualTo(1000);
assertThat(bufferedPositions[1]).isEqualTo(1000);
assertThat(totalBufferedDuration[1]).isEqualTo(0);
} }
@Test
public void seekTo_toLoadingPeriod_correctMaskingPosition() throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override @Override
public boolean isLoading() { public void run(SimpleExoPlayer player) {
return true; player.seekTo(1, 1000);
} }
}; },
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)));
assertThat(windowIndex[0]).isEqualTo(1);
assertThat(positionMs[0]).isEqualTo(1000);
// TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully
// covered.
// assertThat(bufferedPositions[0]).isEqualTo(10_000);
// assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(10_000);
assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]);
}
@Test
public void seekTo_toLoadingPeriod_withinPartiallyBufferedData_correctMaskingPosition()
throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.seekTo(1, 1000);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000));
assertThat(windowIndex[0]).isEqualTo(1);
assertThat(positionMs[0]).isEqualTo(1000);
// TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully
// covered.
// assertThat(bufferedPositions[0]).isEqualTo(1000);
// assertThat(totalBufferedDuration[0]).isEqualTo(0);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(4000);
assertThat(totalBufferedDuration[1]).isEqualTo(3000);
}
@Test
public void seekTo_toLoadingPeriod_beyondBufferedData_correctMaskingPosition() throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.seekTo(1, 5000);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000));
assertThat(windowIndex[0]).isEqualTo(1);
assertThat(positionMs[0]).isEqualTo(5000);
assertThat(bufferedPositions[0]).isEqualTo(5000);
assertThat(totalBufferedDuration[0]).isEqualTo(0);
assertThat(windowIndex[1]).isEqualTo(1);
assertThat(positionMs[1]).isEqualTo(5000);
assertThat(bufferedPositions[1]).isEqualTo(5000);
assertThat(totalBufferedDuration[1]).isEqualTo(0);
}
@Test
public void seekTo_toInnerFullyBufferedPeriod_correctMaskingPosition() throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.seekTo(1, 5000);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000));
assertThat(windowIndex[0]).isEqualTo(1);
assertThat(positionMs[0]).isEqualTo(5000);
// TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully
// covered.
// assertThat(bufferedPositions[0]).isEqualTo(10_000);
// assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(10_000);
assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]);
}
@Test
public void addMediaSource_withinBufferedPeriods_correctMaskingPosition() throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.addMediaSource(
/* index= */ 1, createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0));
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isAtLeast(8000);
assertThat(bufferedPositions[0]).isEqualTo(10_000);
assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(10_000);
assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]);
}
@Test
public void moveMediaItem_behindLoadingPeriod_correctMaskingPosition() throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.moveMediaItem(/* currentIndex= */ 1, /* newIndex= */ 2);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isAtLeast(8000);
assertThat(bufferedPositions[0]).isEqualTo(10_000);
assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(10_000);
assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]);
}
@Test
public void moveMediaItem_undloadedBehindPlaying_correctMaskingPosition() throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.moveMediaItem(/* currentIndex= */ 3, /* newIndex= */ 1);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isAtLeast(8000);
assertThat(bufferedPositions[0]).isEqualTo(10_000);
assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(10000);
assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]);
}
@Test
public void removeMediaItem_removePlayingWindow_correctMaskingPosition() throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.removeMediaItem(/* index= */ 0);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isEqualTo(0);
// TODO(b/160450903): Verify masking of buffering properties when behaviour in EPII is fully
// covered.
// assertThat(bufferedPositions[0]).isEqualTo(4000);
// assertThat(totalBufferedDuration[0]).isEqualTo(4000);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(4000);
assertThat(totalBufferedDuration[1]).isEqualTo(4000);
}
@Test
public void removeMediaItem_removeLoadingWindow_correctMaskingPosition() throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.removeMediaItem(/* index= */ 2);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isAtLeast(8000);
assertThat(bufferedPositions[0]).isEqualTo(10_000);
assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(10_000);
assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[1]);
}
@Test
public void removeMediaItem_removeInnerFullyBufferedWindow_correctMaskingPosition()
throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.removeMediaItem(/* index= */ 1);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isGreaterThan(8000);
assertThat(bufferedPositions[0]).isEqualTo(10_000);
assertThat(totalBufferedDuration[0]).isEqualTo(10_000 - positionMs[0]);
assertThat(windowIndex[1]).isEqualTo(0);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(10_000);
assertThat(totalBufferedDuration[1]).isEqualTo(10_000 - positionMs[0]);
}
@Test
public void clearMediaItems_correctMaskingPosition() throws Exception {
final int[] windowIndex = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] positionMs = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] bufferedPositions = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] totalBufferedDuration = {C.INDEX_UNSET, C.INDEX_UNSET};
runPositionMaskingCapturingActionSchedule(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.clearMediaItems();
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 4000));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isEqualTo(0);
assertThat(bufferedPositions[0]).isEqualTo(0);
assertThat(totalBufferedDuration[0]).isEqualTo(0);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(bufferedPositions[0]);
assertThat(totalBufferedDuration[1]).isEqualTo(totalBufferedDuration[0]);
}
private void runPositionMaskingCapturingActionSchedule(
PlayerRunnable actionRunnable,
int pauseWindowIndex,
int[] windowIndex,
long[] positionMs,
long[] bufferedPosition,
long[] totalBufferedDuration,
MediaSource... mediaSources)
throws Exception {
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.playUntilPosition(pauseWindowIndex, /* positionMs= */ 8000)
.executeRunnable(actionRunnable)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
windowIndex[0] = player.getCurrentWindowIndex();
positionMs[0] = player.getCurrentPosition();
bufferedPosition[0] = player.getBufferedPosition();
totalBufferedDuration[0] = player.getTotalBufferedDuration();
}
})
.waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
windowIndex[1] = player.getCurrentWindowIndex();
positionMs[1] = player.getCurrentPosition();
bufferedPosition[1] = player.getBufferedPosition();
totalBufferedDuration[1] = player.getTotalBufferedDuration();
}
})
.stop()
.build();
new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSources)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
}
private static FakeMediaSource createPartiallyBufferedMediaSource(long maxBufferedPositionMs) {
int windowOffsetInFirstPeriodUs = 1_000_000;
FakeTimeline fakeTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ 1,
/* isSeekable= */ false,
/* isDynamic= */ false,
/* isLive= */ false,
/* isPlaceholder= */ false,
/* durationUs= */ 10_000_000L,
/* defaultPositionUs= */ 0,
windowOffsetInFirstPeriodUs,
AdPlaybackState.NONE));
return new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
@Nullable TransferListener transferListener) {
FakeMediaPeriod fakeMediaPeriod =
new FakeMediaPeriod(
trackGroupArray,
FakeMediaPeriod.TrackDataFactory.singleSampleWithTimeUs(/* sampleTimeUs= */ 0),
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
/* deferOnPrepared= */ false);
fakeMediaPeriod.setBufferedPositionUs(
windowOffsetInFirstPeriodUs + C.msToUs(maxBufferedPositionMs));
return fakeMediaPeriod;
}
};
}
@Test
public void addMediaSource_whilePlayingAd_correctMasking() throws Exception {
long contentDurationMs = 10_000;
long adDurationMs = 100_000;
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adGroupTimesUs...= */ 0);
adPlaybackState = adPlaybackState.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1);
adPlaybackState =
adPlaybackState.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.EMPTY);
long[][] durationsUs = new long[1][];
durationsUs[0] = new long[] {C.msToUs(adDurationMs)};
adPlaybackState = adPlaybackState.withAdDurationsUs(durationsUs);
Timeline adTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ C.msToUs(contentDurationMs),
adPlaybackState));
FakeMediaSource adsMediaSource = new FakeMediaSource(adTimeline);
int[] windowIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
long[] positionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.INDEX_UNSET};
long[] bufferedPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.INDEX_UNSET};
long[] totalBufferedDurationMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.INDEX_UNSET};
boolean[] isPlayingAd = new boolean[3];
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.waitForIsLoading(true)
.waitForIsLoading(false)
.waitForIsLoading(true)
.waitForIsLoading(false)
.pause()
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.addMediaSource(
/* index= */ 1,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)));
windowIndex[0] = player.getCurrentWindowIndex();
isPlayingAd[0] = player.isPlayingAd();
positionMs[0] = player.getCurrentPosition();
bufferedPositionMs[0] = player.getBufferedPosition();
totalBufferedDurationMs[0] = player.getTotalBufferedDuration();
}
})
.waitForTimelineChanged()
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
windowIndex[1] = player.getCurrentWindowIndex();
isPlayingAd[1] = player.isPlayingAd();
positionMs[1] = player.getCurrentPosition();
bufferedPositionMs[1] = player.getBufferedPosition();
totalBufferedDurationMs[1] = player.getTotalBufferedDuration();
}
})
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 8000)
.waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.addMediaSource(
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)));
windowIndex[2] = player.getCurrentWindowIndex();
isPlayingAd[2] = player.isPlayingAd();
positionMs[2] = player.getCurrentPosition();
bufferedPositionMs[2] = player.getBufferedPosition();
totalBufferedDurationMs[2] = player.getTotalBufferedDuration();
}
})
.play()
.build();
new ExoPlayerTestRunner.Builder(context)
.setMediaSources(
adsMediaSource, new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)))
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(isPlayingAd[0]).isTrue();
assertThat(positionMs[0]).isAtMost(adDurationMs);
assertThat(bufferedPositionMs[0]).isEqualTo(adDurationMs);
assertThat(totalBufferedDurationMs[0]).isEqualTo(adDurationMs - positionMs[0]);
assertThat(windowIndex[1]).isEqualTo(0);
assertThat(isPlayingAd[1]).isTrue();
assertThat(positionMs[1]).isAtMost(adDurationMs);
assertThat(bufferedPositionMs[1]).isEqualTo(adDurationMs);
assertThat(totalBufferedDurationMs[1]).isEqualTo(adDurationMs - positionMs[1]);
assertThat(windowIndex[2]).isEqualTo(0);
assertThat(isPlayingAd[2]).isFalse();
assertThat(positionMs[2]).isGreaterThan(8000);
assertThat(bufferedPositionMs[2]).isEqualTo(contentDurationMs);
assertThat(totalBufferedDurationMs[2]).isEqualTo(contentDurationMs - positionMs[2]);
}
@Test
public void seekTo_whilePlayingAd_correctMasking() throws Exception {
long contentDurationMs = 10_000;
long adDurationMs = 4_000;
AdPlaybackState adPlaybackState = new AdPlaybackState(/* adGroupTimesUs...= */ 0);
adPlaybackState = adPlaybackState.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1);
adPlaybackState =
adPlaybackState.withAdUri(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, Uri.EMPTY);
long[][] durationsUs = new long[1][];
durationsUs[0] = new long[] {C.msToUs(adDurationMs)};
adPlaybackState = adPlaybackState.withAdDurationsUs(durationsUs);
Timeline adTimeline =
new FakeTimeline(
new TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* durationUs= */ C.msToUs(contentDurationMs),
adPlaybackState));
FakeMediaSource adsMediaSource = new FakeMediaSource(adTimeline);
int[] windowIndex = new int[] {C.INDEX_UNSET, C.INDEX_UNSET};
long[] positionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET};
long[] bufferedPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET};
long[] totalBufferedDurationMs = new long[] {C.TIME_UNSET, C.TIME_UNSET};
boolean[] isPlayingAd = new boolean[2];
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.waitForIsLoading(true)
.waitForIsLoading(false)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 8000);
windowIndex[0] = player.getCurrentWindowIndex();
isPlayingAd[0] = player.isPlayingAd();
positionMs[0] = player.getCurrentPosition();
bufferedPositionMs[0] = player.getBufferedPosition();
totalBufferedDurationMs[0] = player.getTotalBufferedDuration();
}
})
.waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
windowIndex[1] = player.getCurrentWindowIndex();
isPlayingAd[1] = player.isPlayingAd();
positionMs[1] = player.getCurrentPosition();
bufferedPositionMs[1] = player.getBufferedPosition();
totalBufferedDurationMs[1] = player.getTotalBufferedDuration();
}
})
.stop()
.build();
new ExoPlayerTestRunner.Builder(context)
.setMediaSources(adsMediaSource)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(isPlayingAd[0]).isTrue();
assertThat(positionMs[0]).isEqualTo(0);
assertThat(bufferedPositionMs[0]).isEqualTo(adDurationMs);
assertThat(totalBufferedDurationMs[0]).isEqualTo(adDurationMs);
assertThat(windowIndex[1]).isEqualTo(0);
assertThat(isPlayingAd[1]).isTrue();
assertThat(positionMs[1]).isEqualTo(0);
assertThat(bufferedPositionMs[1]).isEqualTo(adDurationMs);
assertThat(totalBufferedDurationMs[1]).isEqualTo(adDurationMs);
}
@Test
public void becomingNoisyIgnoredIfBecomingNoisyHandlingIsDisabled() throws Exception {
CountDownLatch becomingNoisyHandlingDisabled = new CountDownLatch(1);
CountDownLatch becomingNoisyDelivered = new CountDownLatch(1);
PlayerStateGrabber playerStateGrabber = new PlayerStateGrabber();
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.setHandleAudioBecomingNoisy(false);
becomingNoisyHandlingDisabled.countDown();
// Wait for the broadcast to be delivered from the main thread.
try {
becomingNoisyDelivered.await();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
})
.delay(1) // Handle pending messages on the playback thread.
.executeRunnable(playerStateGrabber)
.build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start();
becomingNoisyHandlingDisabled.await();
deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
becomingNoisyDelivered.countDown();
testRunner.blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
assertThat(playerStateGrabber.playWhenReady).isTrue();
}
@Test
public void pausesWhenBecomingNoisyIfBecomingNoisyHandlingIsEnabled() throws Exception {
CountDownLatch becomingNoisyHandlingEnabled = new CountDownLatch(1);
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.setHandleAudioBecomingNoisy(true);
becomingNoisyHandlingEnabled.countDown();
}
})
.waitForPlayWhenReady(false) // Becoming noisy should set playWhenReady = false
.play()
.build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start();
becomingNoisyHandlingEnabled.await();
deliverBroadcast(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
// If the player fails to handle becoming noisy, blockUntilActionScheduleFinished will time out
// and throw, causing the test to fail.
testRunner.blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
}
// Disabled until the flag to throw exceptions for [internal: b/144538905] is enabled by default.
@Ignore
@Test
public void loadControlNeverWantsToLoad_throwsIllegalStateException() {
LoadControl neverLoadingLoadControl =
new DefaultLoadControl() {
@Override
public boolean shouldContinueLoading(
long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) {
return false;
}
@Override
public boolean shouldStartPlayback(
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
return true;
}
};
// Use chunked data to ensure the player actually needs to continue loading and playing.
FakeAdaptiveDataSet.Factory dataSetFactory =
new FakeAdaptiveDataSet.Factory(
/* chunkDurationUs= */ 500_000, /* bitratePercentStdDev= */ 10.0);
MediaSource chunkedMediaSource =
new FakeAdaptiveMediaSource(
new FakeTimeline(/* windowCount= */ 1),
new TrackGroupArray(new TrackGroup(ExoPlayerTestRunner.VIDEO_FORMAT)),
new FakeChunkSource.Factory(dataSetFactory, new FakeDataSource.Factory()));
ExoPlaybackException exception =
assertThrows(
ExoPlaybackException.class,
() ->
new ExoPlayerTestRunner.Builder(context)
.setLoadControl(neverLoadingLoadControl)
.setMediaSources(chunkedMediaSource)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS));
assertThat(exception.type).isEqualTo(ExoPlaybackException.TYPE_UNEXPECTED);
assertThat(exception.getUnexpectedException()).isInstanceOf(IllegalStateException.class);
}
@Test
public void
nextLoadPositionExceedingLoadControlMaxBuffer_whileCurrentLoadInProgress_doesNotThrowException()
throws Exception {
long maxBufferUs = 2 * C.MICROS_PER_SECOND;
LoadControl loadControlWithMaxBufferUs =
new DefaultLoadControl() {
@Override
public boolean shouldContinueLoading(
long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) {
return bufferedDurationUs < maxBufferUs;
}
@Override
public boolean shouldStartPlayback(
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
return true;
}
};
MediaSource mediaSourceWithLoadInProgress =
new FakeMediaSource(
new FakeTimeline(/* windowCount= */ 1), ExoPlayerTestRunner.VIDEO_FORMAT) {
@Override
protected FakeMediaPeriod createFakeMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
@Nullable TransferListener transferListener) {
return new FakeMediaPeriod(
trackGroupArray,
TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US,
mediaSourceEventDispatcher) {
@Override
public long getBufferedPositionUs() {
// Pretend not to have buffered data yet.
return 0;
}
@Override
public long getNextLoadPositionUs() {
// Set next load position beyond the maxBufferUs configured in the LoadControl.
return Long.MAX_VALUE;
}
@Override
public boolean isLoading() {
return true;
}
};
} }
}; };
FakeRenderer rendererWaitingForData = FakeRenderer rendererWaitingForData =
...@@ -4536,6 +5478,80 @@ public final class ExoPlayerTest { ...@@ -4536,6 +5478,80 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void setPlayWhenReady_correctPositionMasking() throws Exception {
long[] currentPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
long[] bufferedPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.playUntilPosition(0, 5000)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
currentPositionMs[0] = player.getCurrentPosition();
bufferedPositionMs[0] = player.getBufferedPosition();
player.setPlayWhenReady(true);
currentPositionMs[1] = player.getCurrentPosition();
bufferedPositionMs[1] = player.getBufferedPosition();
player.setPlayWhenReady(false);
currentPositionMs[2] = player.getCurrentPosition();
bufferedPositionMs[2] = player.getBufferedPosition();
}
})
.play()
.build();
new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(currentPositionMs[0]).isAtLeast(5000);
assertThat(currentPositionMs[1]).isEqualTo(currentPositionMs[0]);
assertThat(currentPositionMs[2]).isEqualTo(currentPositionMs[0]);
assertThat(bufferedPositionMs[0]).isGreaterThan(currentPositionMs[0]);
assertThat(bufferedPositionMs[1]).isEqualTo(bufferedPositionMs[0]);
assertThat(bufferedPositionMs[2]).isEqualTo(bufferedPositionMs[0]);
}
@Test
public void setShuffleMode_correctPositionMasking() throws Exception {
long[] currentPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
long[] bufferedPositionMs = new long[] {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.playUntilPosition(0, 5000)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
currentPositionMs[0] = player.getCurrentPosition();
bufferedPositionMs[0] = player.getBufferedPosition();
player.setShuffleModeEnabled(true);
currentPositionMs[1] = player.getCurrentPosition();
bufferedPositionMs[1] = player.getBufferedPosition();
player.setShuffleModeEnabled(false);
currentPositionMs[2] = player.getCurrentPosition();
bufferedPositionMs[2] = player.getBufferedPosition();
}
})
.play()
.build();
new ExoPlayerTestRunner.Builder(context)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(currentPositionMs[0]).isAtLeast(5000);
assertThat(currentPositionMs[1]).isEqualTo(currentPositionMs[0]);
assertThat(currentPositionMs[2]).isEqualTo(currentPositionMs[0]);
assertThat(bufferedPositionMs[0]).isGreaterThan(currentPositionMs[0]);
assertThat(bufferedPositionMs[1]).isEqualTo(bufferedPositionMs[0]);
assertThat(bufferedPositionMs[2]).isEqualTo(bufferedPositionMs[0]);
}
@Test
public void setShuffleOrder_keepsCurrentPosition() throws Exception { public void setShuffleOrder_keepsCurrentPosition() throws Exception {
AtomicLong positionAfterSetShuffleOrder = new AtomicLong(C.TIME_UNSET); AtomicLong positionAfterSetShuffleOrder = new AtomicLong(C.TIME_UNSET);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
...@@ -4866,13 +5882,14 @@ public final class ExoPlayerTest { ...@@ -4866,13 +5882,14 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void setMediaSources_whenEmpty_validInitialSeek_correctMaskingWindowIndex() public void setMediaSources_whenEmpty_validInitialSeek_correctMasking() throws Exception {
throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 2); Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 2);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline); MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object()); Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object());
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline); MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
// Wait for initial seek to be fully handled by internal player. // Wait for initial seek to be fully handled by internal player.
...@@ -4883,9 +5900,13 @@ public final class ExoPlayerTest { ...@@ -4883,9 +5900,13 @@ public final class ExoPlayerTest {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
currentWindowIndices[0] = player.getCurrentWindowIndex(); currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPositions[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
// Increase current window index. // Increase current window index.
player.addMediaSource(/* index= */ 0, secondMediaSource); player.addMediaSource(/* index= */ 0, secondMediaSource);
currentWindowIndices[1] = player.getCurrentWindowIndex(); currentWindowIndices[1] = player.getCurrentWindowIndex();
currentPositions[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
} }
}) })
.prepare() .prepare()
...@@ -4895,11 +5916,13 @@ public final class ExoPlayerTest { ...@@ -4895,11 +5916,13 @@ public final class ExoPlayerTest {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
currentWindowIndices[2] = player.getCurrentWindowIndex(); currentWindowIndices[2] = player.getCurrentWindowIndex();
currentPositions[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
} }
}) })
.build(); .build();
new ExoPlayerTestRunner.Builder(context) new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET) .initialSeek(/* windowIndex= */ 1, 2000)
.setMediaSources(firstMediaSource) .setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.build() .build()
...@@ -4907,16 +5930,19 @@ public final class ExoPlayerTest { ...@@ -4907,16 +5930,19 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
assertArrayEquals(new int[] {1, 2, 2}, currentWindowIndices); assertArrayEquals(new int[] {1, 2, 2}, currentWindowIndices);
assertArrayEquals(new long[] {2000, 2000, 2000}, currentPositions);
assertArrayEquals(new long[] {2000, 2000, 2000}, bufferedPositions);
} }
@Test @Test
public void setMediaSources_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex() public void setMediaSources_whenEmpty_invalidInitialSeek_correctMasking() throws Exception {
throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1); Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline); MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object()); Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object());
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline); MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET}; final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET, C.INDEX_UNSET};
final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
// Wait for initial seek to be fully handled by internal player. // Wait for initial seek to be fully handled by internal player.
...@@ -4927,9 +5953,13 @@ public final class ExoPlayerTest { ...@@ -4927,9 +5953,13 @@ public final class ExoPlayerTest {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
currentWindowIndices[0] = player.getCurrentWindowIndex(); currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPositions[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
// Increase current window index. // Increase current window index.
player.addMediaSource(/* index= */ 0, secondMediaSource); player.addMediaSource(/* index= */ 0, secondMediaSource);
currentWindowIndices[1] = player.getCurrentWindowIndex(); currentWindowIndices[1] = player.getCurrentWindowIndex();
currentPositions[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
} }
}) })
.prepare() .prepare()
...@@ -4939,12 +5969,14 @@ public final class ExoPlayerTest { ...@@ -4939,12 +5969,14 @@ public final class ExoPlayerTest {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
currentWindowIndices[2] = player.getCurrentWindowIndex(); currentWindowIndices[2] = player.getCurrentWindowIndex();
currentPositions[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
} }
}) })
.waitForPlaybackState(Player.STATE_ENDED) .waitForPlaybackState(Player.STATE_ENDED)
.build(); .build();
new ExoPlayerTestRunner.Builder(context) new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET) .initialSeek(/* windowIndex= */ 1, 2000)
.setMediaSources(firstMediaSource) .setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.build() .build()
...@@ -4952,6 +5984,8 @@ public final class ExoPlayerTest { ...@@ -4952,6 +5984,8 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
assertArrayEquals(new int[] {0, 1, 1}, currentWindowIndices); assertArrayEquals(new int[] {0, 1, 1}, currentWindowIndices);
assertArrayEquals(new long[] {0, 0, 0}, currentPositions);
assertArrayEquals(new long[] {0, 0, 0}, bufferedPositions);
} }
@Test @Test
...@@ -5542,10 +6576,47 @@ public final class ExoPlayerTest { ...@@ -5542,10 +6576,47 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void addMediaSources_skipSettingMediaItems_validInitialSeek_correctMaskingWindowIndex() public void addMediaSources_whenEmptyInitialSeek_correctPeriodMasking() throws Exception {
final long[] positions = new long[2];
Arrays.fill(positions, C.TIME_UNSET);
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
// Wait for initial seek to be fully handled by internal player.
.waitForPositionDiscontinuity()
.waitForPendingPlayerCommands()
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
player.addMediaSource(
/* index= */ 0,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)));
positions[0] = player.getCurrentPosition();
positions[1] = player.getBufferedPosition();
}
})
.prepare()
.build();
new ExoPlayerTestRunner.Builder(context)
.skipSettingMediaSources()
.initialSeek(/* windowIndex= */ 0, /* positionMs= */ 2000)
.setActionSchedule(actionSchedule)
.build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertArrayEquals(new long[] {2000, 2000}, positions);
}
@Test
public void addMediaSources_skipSettingMediaItems_validInitialSeek_correctMasking()
throws Exception { throws Exception {
final int[] currentWindowIndices = new int[5]; final int[] currentWindowIndices = new int[5];
Arrays.fill(currentWindowIndices, C.INDEX_UNSET); Arrays.fill(currentWindowIndices, C.INDEX_UNSET);
final long[] currentPositions = new long[3];
Arrays.fill(currentPositions, C.TIME_UNSET);
final long[] bufferedPositions = new long[3];
Arrays.fill(bufferedPositions, C.TIME_UNSET);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
// Wait for initial seek to be fully handled by internal player. // Wait for initial seek to be fully handled by internal player.
...@@ -5556,6 +6627,9 @@ public final class ExoPlayerTest { ...@@ -5556,6 +6627,9 @@ public final class ExoPlayerTest {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
currentWindowIndices[0] = player.getCurrentWindowIndex(); currentWindowIndices[0] = player.getCurrentWindowIndex();
// If the timeline is empty masking variables are used.
currentPositions[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
player.addMediaSource(/* index= */ 0, new ConcatenatingMediaSource()); player.addMediaSource(/* index= */ 0, new ConcatenatingMediaSource());
currentWindowIndices[1] = player.getCurrentWindowIndex(); currentWindowIndices[1] = player.getCurrentWindowIndex();
player.addMediaSource( player.addMediaSource(
...@@ -5566,26 +6640,39 @@ public final class ExoPlayerTest { ...@@ -5566,26 +6640,39 @@ public final class ExoPlayerTest {
/* index= */ 0, /* index= */ 0,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1))); new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)));
currentWindowIndices[3] = player.getCurrentWindowIndex(); currentWindowIndices[3] = player.getCurrentWindowIndex();
// With a non-empty timeline, we mask the periodId in the playback info.
currentPositions[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
} }
}) })
.prepare() .prepare()
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
currentWindowIndices[4] = player.getCurrentWindowIndex(); currentWindowIndices[4] = player.getCurrentWindowIndex();
// Finally original playbackInfo coming from EPII is used.
currentPositions[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
} }
}) })
.build(); .build();
new ExoPlayerTestRunner.Builder(context) new ExoPlayerTestRunner.Builder(context)
.skipSettingMediaSources() .skipSettingMediaSources()
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET) .initialSeek(/* windowIndex= */ 1, 2000)
.setActionSchedule(actionSchedule) .setActionSchedule(actionSchedule)
.build() .build()
.start(/* doPrepare= */ false) .start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
assertArrayEquals(new int[] {1, 1, 1, 2, 2}, currentWindowIndices); assertArrayEquals(new int[] {1, 1, 1, 2, 2}, currentWindowIndices);
assertThat(currentPositions[0]).isEqualTo(2000);
assertThat(currentPositions[1]).isEqualTo(2000);
assertThat(currentPositions[2]).isAtLeast(2000);
assertThat(bufferedPositions[0]).isEqualTo(2000);
assertThat(bufferedPositions[1]).isEqualTo(2000);
assertThat(bufferedPositions[2]).isAtLeast(2000);
} }
@Test @Test
...@@ -5784,13 +6871,14 @@ public final class ExoPlayerTest { ...@@ -5784,13 +6871,14 @@ public final class ExoPlayerTest {
} }
@Test @Test
public void removeMediaItems_currentItemRemoved_correctMaskingWindowIndex() throws Exception { public void removeMediaItems_currentItemRemoved_correctMasking() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1); Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline); MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1); Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline); MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET}; final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET};
final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_BUFFERING) .waitForPlaybackState(Player.STATE_BUFFERING)
...@@ -5801,9 +6889,11 @@ public final class ExoPlayerTest { ...@@ -5801,9 +6889,11 @@ public final class ExoPlayerTest {
// Remove the current item. // Remove the current item.
currentWindowIndices[0] = player.getCurrentWindowIndex(); currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPositions[0] = player.getCurrentPosition(); currentPositions[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
player.removeMediaItem(/* index= */ 1); player.removeMediaItem(/* index= */ 1);
currentWindowIndices[1] = player.getCurrentWindowIndex(); currentWindowIndices[1] = player.getCurrentWindowIndex();
currentPositions[1] = player.getCurrentPosition(); currentPositions[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
} }
}) })
.build(); .build();
...@@ -5817,7 +6907,9 @@ public final class ExoPlayerTest { ...@@ -5817,7 +6907,9 @@ public final class ExoPlayerTest {
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
assertArrayEquals(new int[] {1, 1}, currentWindowIndices); assertArrayEquals(new int[] {1, 1}, currentWindowIndices);
assertThat(currentPositions[0]).isAtLeast(5000L); assertThat(currentPositions[0]).isAtLeast(5000L);
assertThat(bufferedPositions[0]).isAtLeast(5000L);
assertThat(currentPositions[1]).isEqualTo(0); assertThat(currentPositions[1]).isEqualTo(0);
assertThat(bufferedPositions[1]).isAtLeast(0);
} }
@Test @Test
...@@ -5834,6 +6926,10 @@ public final class ExoPlayerTest { ...@@ -5834,6 +6926,10 @@ public final class ExoPlayerTest {
Arrays.fill(currentWindowIndices, C.INDEX_UNSET); Arrays.fill(currentWindowIndices, C.INDEX_UNSET);
final int[] maskingPlaybackStates = new int[4]; final int[] maskingPlaybackStates = new int[4];
Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET); Arrays.fill(maskingPlaybackStates, C.INDEX_UNSET);
final long[] currentPositions = new long[3];
Arrays.fill(currentPositions, C.TIME_UNSET);
final long[] bufferedPositions = new long[3];
Arrays.fill(bufferedPositions, C.TIME_UNSET);
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY) .waitForPlaybackState(Player.STATE_READY)
...@@ -5843,12 +6939,16 @@ public final class ExoPlayerTest { ...@@ -5843,12 +6939,16 @@ public final class ExoPlayerTest {
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
// Expect the current window index to be 2 after seek. // Expect the current window index to be 2 after seek.
currentWindowIndices[0] = player.getCurrentWindowIndex(); currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPositions[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
player.removeMediaItem(/* index= */ 2); player.removeMediaItem(/* index= */ 2);
// Expect the current window index to be 0 // Expect the current window index to be 0
// (default position of timeline after not finding subsequent period). // (default position of timeline after not finding subsequent period).
currentWindowIndices[1] = player.getCurrentWindowIndex(); currentWindowIndices[1] = player.getCurrentWindowIndex();
// Transition to ENDED. // Transition to ENDED.
maskingPlaybackStates[0] = player.getPlaybackState(); maskingPlaybackStates[0] = player.getPlaybackState();
currentPositions[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
} }
}) })
.waitForPlaybackState(Player.STATE_ENDED) .waitForPlaybackState(Player.STATE_ENDED)
...@@ -5864,6 +6964,8 @@ public final class ExoPlayerTest { ...@@ -5864,6 +6964,8 @@ public final class ExoPlayerTest {
currentWindowIndices[3] = player.getCurrentWindowIndex(); currentWindowIndices[3] = player.getCurrentWindowIndex();
// Remains in ENDED. // Remains in ENDED.
maskingPlaybackStates[1] = player.getPlaybackState(); maskingPlaybackStates[1] = player.getPlaybackState();
currentPositions[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
} }
}) })
.waitForTimelineChanged() .waitForTimelineChanged()
...@@ -5932,6 +7034,12 @@ public final class ExoPlayerTest { ...@@ -5932,6 +7034,12 @@ public final class ExoPlayerTest {
}, // buffers after set items with seek }, // buffers after set items with seek
maskingPlaybackStates); maskingPlaybackStates);
assertArrayEquals(new int[] {2, 0, 0, 1, 1, 0, 0, 0, 0}, currentWindowIndices); assertArrayEquals(new int[] {2, 0, 0, 1, 1, 0, 0, 0, 0}, currentWindowIndices);
assertThat(currentPositions[0]).isGreaterThan(0);
assertThat(currentPositions[1]).isEqualTo(0);
assertThat(currentPositions[2]).isEqualTo(0);
assertThat(bufferedPositions[0]).isGreaterThan(0);
assertThat(bufferedPositions[1]).isEqualTo(0);
assertThat(bufferedPositions[2]).isEqualTo(0);
} }
@Test @Test
...@@ -5969,16 +7077,24 @@ public final class ExoPlayerTest { ...@@ -5969,16 +7077,24 @@ public final class ExoPlayerTest {
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline); MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET}; final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET};
final int[] maskingPlaybackState = {C.INDEX_UNSET}; final int[] maskingPlaybackState = {C.INDEX_UNSET};
final long[] currentPosition = {C.TIME_UNSET, C.TIME_UNSET};
final long[] bufferedPosition = {C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule = ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG) new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING) .waitForPlaybackState(Player.STATE_BUFFERING)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 150)
.executeRunnable( .executeRunnable(
new PlayerRunnable() { new PlayerRunnable() {
@Override @Override
public void run(SimpleExoPlayer player) { public void run(SimpleExoPlayer player) {
currentWindowIndices[0] = player.getCurrentWindowIndex(); currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPosition[0] = player.getCurrentPosition();
bufferedPosition[0] = player.getBufferedPosition();
player.clearMediaItems(); player.clearMediaItems();
currentWindowIndices[1] = player.getCurrentWindowIndex(); currentWindowIndices[1] = player.getCurrentWindowIndex();
currentPosition[1] = player.getCurrentPosition();
bufferedPosition[1] = player.getBufferedPosition();
maskingPlaybackState[0] = player.getPlaybackState(); maskingPlaybackState[0] = player.getPlaybackState();
} }
}) })
...@@ -5992,6 +7108,11 @@ public final class ExoPlayerTest { ...@@ -5992,6 +7108,11 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
assertArrayEquals(new int[] {1, 0}, currentWindowIndices); assertArrayEquals(new int[] {1, 0}, currentWindowIndices);
assertThat(currentPosition[0]).isAtLeast(150);
assertThat(currentPosition[1]).isEqualTo(0);
assertThat(bufferedPosition[0]).isAtLeast(150);
assertThat(bufferedPosition[1]).isEqualTo(0);
assertArrayEquals(new int[] {1, 0}, currentWindowIndices);
assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackState); assertArrayEquals(new int[] {Player.STATE_ENDED}, maskingPlaybackState);
} }
......
...@@ -421,7 +421,7 @@ public final class AnalyticsCollectorTest { ...@@ -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 { ...@@ -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 */)
......
...@@ -72,6 +72,7 @@ public class FakeMediaPeriod implements MediaPeriod { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment