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;
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.Util.castNonNull;
import android.annotation.SuppressLint;
import android.os.Handler;
......@@ -418,16 +419,18 @@ import java.util.concurrent.TimeoutException;
public void addMediaSources(int index, List<MediaSource> mediaSources) {
Assertions.checkArgument(index >= 0);
validateMediaSources(mediaSources, /* mediaSourceReplacement= */ false);
int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition();
Timeline oldTimeline = getCurrentTimeline();
pendingOperationAcks++;
List<MediaSourceList.MediaSourceHolder> holders = addMediaSourceHolders(index, mediaSources);
PlaybackInfo playbackInfo =
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
Timeline newTimeline = createMaskingTimeline();
PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
playbackInfo,
newTimeline,
getPeriodPositionAfterTimelineChanged(oldTimeline, newTimeline));
internalPlayer.addMediaSources(index, holders, shuffleOrder);
updatePlaybackInfo(
playbackInfo,
newPlaybackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
......@@ -448,18 +451,20 @@ import java.util.concurrent.TimeoutException;
&& fromIndex <= toIndex
&& toIndex <= mediaSourceHolderSnapshots.size()
&& newFromIndex >= 0);
int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition();
Timeline oldTimeline = getCurrentTimeline();
pendingOperationAcks++;
newFromIndex =
Math.min(newFromIndex, mediaSourceHolderSnapshots.size() - (toIndex - fromIndex));
Util.moveItems(mediaSourceHolderSnapshots, fromIndex, toIndex, newFromIndex);
PlaybackInfo playbackInfo =
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
Timeline newTimeline = createMaskingTimeline();
PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
playbackInfo,
newTimeline,
getPeriodPositionAfterTimelineChanged(oldTimeline, newTimeline));
internalPlayer.moveMediaSources(fromIndex, toIndex, newFromIndex, shuffleOrder);
updatePlaybackInfo(
playbackInfo,
newPlaybackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
......@@ -477,13 +482,18 @@ import java.util.concurrent.TimeoutException;
@Override
public void setShuffleOrder(ShuffleOrder shuffleOrder) {
PlaybackInfo playbackInfo = maskTimeline();
maskWithCurrentPosition();
Timeline timeline = createMaskingTimeline();
PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
playbackInfo,
timeline,
getPeriodPositionOrMaskWindowPosition(
timeline, getCurrentWindowIndex(), getCurrentPosition()));
pendingOperationAcks++;
this.shuffleOrder = shuffleOrder;
internalPlayer.setShuffleOrder(shuffleOrder);
updatePlaybackInfo(
playbackInfo,
newPlaybackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
......@@ -521,7 +531,6 @@ import java.util.concurrent.TimeoutException;
&& playbackInfo.playbackSuppressionReason == playbackSuppressionReason) {
return;
}
maskWithCurrentPosition();
pendingOperationAcks++;
PlaybackInfo playbackInfo =
this.playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason);
......@@ -589,14 +598,18 @@ import java.util.concurrent.TimeoutException;
new ExoPlayerImplInternal.PlaybackInfoUpdate(playbackInfo));
return;
}
maskWindowIndexAndPositionForSeek(timeline, windowIndex, positionMs);
@Player.State
int newPlaybackState =
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));
updatePlaybackInfo(
playbackInfo,
newPlaybackInfo,
/* positionDiscontinuity= */ true,
/* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK,
/* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
......@@ -732,7 +745,7 @@ import java.util.concurrent.TimeoutException;
@Override
public int getCurrentPeriodIndex() {
if (shouldMaskPosition()) {
if (playbackInfo.timeline.isEmpty()) {
return maskingPeriodIndex;
} else {
return playbackInfo.timeline.getIndexOfPeriod(playbackInfo.periodId.periodUid);
......@@ -758,7 +771,7 @@ import java.util.concurrent.TimeoutException;
@Override
public long getCurrentPosition() {
if (shouldMaskPosition()) {
if (playbackInfo.timeline.isEmpty()) {
return maskingWindowPositionMs;
} else if (playbackInfo.periodId.isAd()) {
return C.usToMs(playbackInfo.positionUs);
......@@ -784,7 +797,7 @@ import java.util.concurrent.TimeoutException;
@Override
public boolean isPlayingAd() {
return !shouldMaskPosition() && playbackInfo.periodId.isAd();
return playbackInfo.periodId.isAd();
}
@Override
......@@ -811,7 +824,7 @@ import java.util.concurrent.TimeoutException;
@Override
public long getContentBufferedPosition() {
if (shouldMaskPosition()) {
if (playbackInfo.timeline.isEmpty()) {
return maskingWindowPositionMs;
}
if (playbackInfo.loadingMediaPeriodId.windowSequenceNumber
......@@ -864,7 +877,7 @@ import java.util.concurrent.TimeoutException;
}
private int getCurrentWindowIndexInternal() {
if (shouldMaskPosition()) {
if (playbackInfo.timeline.isEmpty()) {
return maskingWindowIndex;
} else {
return playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period)
......@@ -909,7 +922,8 @@ import java.util.concurrent.TimeoutException;
if (pendingOperationAcks == 0) {
Timeline newTimeline = playbackInfoUpdate.playbackInfo.timeline;
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();
}
if (!newTimeline.isEmpty()) {
......@@ -938,8 +952,6 @@ import java.util.concurrent.TimeoutException;
removeMediaSourceHolders(
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolderSnapshots.size());
resetMaskingPosition();
} else {
maskWithCurrentPosition();
}
Timeline timeline = playbackInfo.timeline;
MediaPeriodId mediaPeriodId = playbackInfo.periodId;
......@@ -1006,8 +1018,7 @@ import java.util.concurrent.TimeoutException;
}
List<MediaSourceList.MediaSourceHolder> holders =
addMediaSourceHolders(/* index= */ 0, mediaSources);
PlaybackInfo playbackInfo = maskTimeline();
Timeline timeline = playbackInfo.timeline;
Timeline timeline = createMaskingTimeline();
if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) {
throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs);
}
......@@ -1019,11 +1030,14 @@ import java.util.concurrent.TimeoutException;
startWindowIndex = currentWindowIndex;
startPositionMs = currentPositionMs;
}
maskWindowIndexAndPositionForSeek(
timeline, startWindowIndex == C.INDEX_UNSET ? 0 : startWindowIndex, startPositionMs);
PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
playbackInfo,
timeline,
getPeriodPositionOrMaskWindowPosition(timeline, startWindowIndex, startPositionMs));
// Mask the playback state.
int maskingPlaybackState = playbackInfo.playbackState;
if (startWindowIndex != C.INDEX_UNSET && playbackInfo.playbackState != STATE_IDLE) {
int maskingPlaybackState = newPlaybackInfo.playbackState;
if (startWindowIndex != C.INDEX_UNSET && newPlaybackInfo.playbackState != STATE_IDLE) {
// Position reset to startWindowIndex (results in pending initial seek).
if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) {
// Setting an empty timeline or invalid seek transitions to ended.
......@@ -1032,11 +1046,11 @@ import java.util.concurrent.TimeoutException;
maskingPlaybackState = STATE_BUFFERING;
}
}
playbackInfo = playbackInfo.copyWithPlaybackState(maskingPlaybackState);
newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(maskingPlaybackState);
internalPlayer.setMediaSources(
holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder);
updatePlaybackInfo(
playbackInfo,
newPlaybackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ Player.DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
......@@ -1064,26 +1078,29 @@ import java.util.concurrent.TimeoutException;
Assertions.checkArgument(
fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolderSnapshots.size());
int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition();
Timeline oldTimeline = getCurrentTimeline();
int currentMediaSourceCount = mediaSourceHolderSnapshots.size();
pendingOperationAcks++;
removeMediaSourceHolders(fromIndex, /* toIndexExclusive= */ toIndex);
PlaybackInfo playbackInfo =
maskTimelineAndWindowIndex(currentWindowIndex, currentPositionMs, oldTimeline);
Timeline newTimeline = createMaskingTimeline();
PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition(
playbackInfo,
newTimeline,
getPeriodPositionAfterTimelineChanged(oldTimeline, newTimeline));
// Player transitions to STATE_ENDED if the current index is part of the removed tail.
final boolean transitionsToEnded =
playbackInfo.playbackState != STATE_IDLE
&& playbackInfo.playbackState != STATE_ENDED
newPlaybackInfo.playbackState != STATE_IDLE
&& newPlaybackInfo.playbackState != STATE_ENDED
&& fromIndex < toIndex
&& toIndex == currentMediaSourceCount
&& currentWindowIndex >= playbackInfo.timeline.getWindowCount();
&& currentWindowIndex >= newPlaybackInfo.timeline.getWindowCount();
if (transitionsToEnded) {
playbackInfo = playbackInfo.copyWithPlaybackState(STATE_ENDED);
newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(STATE_ENDED);
}
internalPlayer.removeMediaSources(fromIndex, toIndex, shuffleOrder);
updatePlaybackInfo(
playbackInfo,
newPlaybackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ Player.DISCONTINUITY_REASON_INTERNAL,
/* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
......@@ -1131,107 +1148,163 @@ import java.util.concurrent.TimeoutException;
}
}
private PlaybackInfo maskTimeline() {
return playbackInfo.copyWithTimeline(
mediaSourceHolderSnapshots.isEmpty()
? Timeline.EMPTY
: new PlaylistTimeline(mediaSourceHolderSnapshots, shuffleOrder));
private Timeline createMaskingTimeline() {
return new PlaylistTimeline(mediaSourceHolderSnapshots, shuffleOrder);
}
private PlaybackInfo maskTimelineAndWindowIndex(
int currentWindowIndex, long currentPositionMs, Timeline oldTimeline) {
PlaybackInfo playbackInfo = maskTimeline();
Timeline maskingTimeline = playbackInfo.timeline;
if (oldTimeline.isEmpty()) {
// The index is the default index or was set by a seek in the empty old timeline.
maskingWindowIndex = currentWindowIndex;
if (!maskingTimeline.isEmpty() && currentWindowIndex >= maskingTimeline.getWindowCount()) {
// The seek is not valid in the new timeline.
maskWithDefaultPosition(maskingTimeline);
}
private PlaybackInfo maskTimelineAndPosition(
PlaybackInfo playbackInfo, Timeline timeline, @Nullable Pair<Object, Long> periodPosition) {
Assertions.checkArgument(timeline.isEmpty() || periodPosition != null);
Timeline oldTimeline = playbackInfo.timeline;
// Mask the timeline.
playbackInfo = playbackInfo.copyWithTimeline(timeline);
if (timeline.isEmpty()) {
// Reset periodId and loadingPeriodId.
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;
}
@Nullable
Pair<Object, Long> periodPosition =
oldTimeline.getPeriodPosition(
window,
period,
currentWindowIndex,
C.msToUs(currentPositionMs),
/* defaultPositionProjectionUs= */ 0);
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;
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 {
// Period uid not found in new timeline. Try to get subsequent period.
@Nullable
Object nextPeriodUid =
ExoPlayerImplInternal.resolveSubsequentPeriod(
window,
period,
repeatMode,
shuffleModeEnabled,
periodUid,
oldTimeline,
maskingTimeline);
if (nextPeriodUid != null) {
// Set masking to the default position of the window of the subsequent period.
maskingWindowIndex = maskingTimeline.getPeriodByUid(nextPeriodUid, period).windowIndex;
maskingPeriodIndex = maskingTimeline.getWindow(maskingWindowIndex, window).firstPeriodIndex;
maskingWindowPositionMs = window.getDefaultPositionMs();
} else {
// Reset if no subsequent period is found.
maskWithDefaultPosition(maskingTimeline);
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;
}
private void maskWindowIndexAndPositionForSeek(
Timeline timeline, int windowIndex, long positionMs) {
maskingWindowIndex = windowIndex;
if (timeline.isEmpty()) {
maskingWindowPositionMs = positionMs == C.TIME_UNSET ? 0 : positionMs;
maskingPeriodIndex = 0;
} else if (windowIndex >= timeline.getWindowCount()) {
// An initial seek now proves to be invalid in the actual timeline.
maskWithDefaultPosition(timeline);
@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
Pair<Object, Long> oldPeriodPosition =
oldTimeline.getPeriodPosition(
window, period, currentWindowIndex, C.msToUs(currentPositionMs));
Object periodUid = castNonNull(oldPeriodPosition).first;
if (newTimeline.getIndexOfPeriod(periodUid) != C.INDEX_UNSET) {
// The old period position is still available in the new timeline.
return oldPeriodPosition;
}
// Period uid not found in new timeline. Try to get subsequent period.
@Nullable
Object nextPeriodUid =
ExoPlayerImplInternal.resolveSubsequentPeriod(
window, period, repeatMode, shuffleModeEnabled, periodUid, oldTimeline, newTimeline);
if (nextPeriodUid != null) {
// Reset position to the default position of the window of the subsequent period.
newTimeline.getPeriodByUid(nextPeriodUid, period);
return getPeriodPositionOrMaskWindowPosition(
newTimeline,
period.windowIndex,
newTimeline.getWindow(period.windowIndex, window).getDefaultPositionMs());
} else {
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);
// No subsequent period found and the new timeline is not empty. Use the default position.
return getPeriodPositionOrMaskWindowPosition(
newTimeline, /* windowIndex= */ C.INDEX_UNSET, /* windowPositionMs= */ C.TIME_UNSET);
}
}
private void maskWithCurrentPosition() {
maskingWindowIndex = getCurrentWindowIndexInternal();
maskingPeriodIndex = getCurrentPeriodIndex();
maskingWindowPositionMs = getCurrentPosition();
}
private void maskWithDefaultPosition(Timeline timeline) {
@Nullable
private Pair<Object, Long> getPeriodPositionOrMaskWindowPosition(
Timeline timeline, int windowIndex, long windowPositionMs) {
if (timeline.isEmpty()) {
resetMaskingPosition();
return;
// If empty we store the initial seek in the masking variables.
maskingWindowIndex = windowIndex;
maskingWindowPositionMs = windowPositionMs == C.TIME_UNSET ? 0 : windowPositionMs;
maskingPeriodIndex = 0;
return null;
}
maskingWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled);
timeline.getWindow(maskingWindowIndex, window);
maskingWindowPositionMs = window.getDefaultPositionMs();
maskingPeriodIndex = window.firstPeriodIndex;
}
private void resetMaskingPosition() {
maskingWindowIndex = C.INDEX_UNSET;
maskingWindowPositionMs = 0;
maskingPeriodIndex = 0;
if (windowIndex == C.INDEX_UNSET || windowIndex >= timeline.getWindowCount()) {
// Use default position of timeline if window index still unset or if a previous initial seek
// now turns out to be invalid.
windowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled);
windowPositionMs = timeline.getWindow(windowIndex, window).getDefaultPositionMs();
}
return timeline.getPeriodPosition(window, period, windowIndex, C.msToUs(windowPositionMs));
}
private void notifyListeners(ListenerInvocation listenerInvocation) {
......@@ -1251,6 +1324,12 @@ import java.util.concurrent.TimeoutException;
}
}
private void resetMaskingPosition() {
maskingWindowIndex = C.INDEX_UNSET;
maskingWindowPositionMs = 0;
maskingPeriodIndex = 0;
}
private long periodPositionUsToWindowPositionMs(MediaPeriodId periodId, long positionUs) {
long positionMs = C.usToMs(positionUs);
playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period);
......@@ -1258,10 +1337,6 @@ import java.util.concurrent.TimeoutException;
return positionMs;
}
private boolean shouldMaskPosition() {
return playbackInfo.timeline.isEmpty() || pendingOperationAcks > 0;
}
private final class PlaybackUpdateListenerImpl
implements ExoPlayerImplInternal.PlaybackUpdateListener, Handler.Callback {
private static final int MSG_PLAYBACK_INFO_CHANGED = 0;
......
......@@ -32,6 +32,7 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Looper;
import android.view.Surface;
import android.view.View;
......@@ -1062,148 +1063,255 @@ public final class ExoPlayerTest {
}
@Test
public void stopDoesNotResetPosition() throws Exception {
public void stop_withoutReset_doesNotResetPosition_correctMasking() throws Exception {
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 =
new ActionSchedule.Builder(TAG)
.pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ 1000)
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 50)
.stop()
.executeRunnable(
new PlayerRunnable() {
@Override
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();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setMediaSources(mediaSource, mediaSource)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesSame(placeholderTimeline, timeline);
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
testRunner.assertNoPositionDiscontinuities();
assertThat(positionHolder[0]).isAtLeast(50L);
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
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
public void stopWithoutResetDoesNotResetPosition() throws Exception {
public void stop_withoutReset_releasesMediaSource() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final long[] positionHolder = new long[1];
final FakeMediaSource mediaSource =
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 50)
.stop(/* reset= */ false)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
positionHolder[0] = player.getCurrentPosition();
}
})
.build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesSame(placeholderTimeline, timeline);
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
testRunner.assertNoPositionDiscontinuities();
assertThat(positionHolder[0]).isAtLeast(50L);
new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
mediaSource.assertReleased();
}
@Test
public void stopWithResetDoesResetPosition() throws Exception {
public void stop_withReset_doesResetPosition_correctMasking() throws Exception {
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 =
new ActionSchedule.Builder(TAG)
.pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ 1000)
.waitForPlaybackState(Player.STATE_READY)
.playUntilPosition(/* windowIndex= */ 0, /* positionMs= */ 50)
.stop(/* reset= */ true)
.executeRunnable(
new PlayerRunnable() {
@Override
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();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setMediaSources(mediaSource, mediaSource)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
testRunner.assertTimelinesSame(placeholderTimeline, timeline, Timeline.EMPTY);
.blockUntilActionScheduleFinished(TIMEOUT_MS);
testRunner.assertTimelineChangeReasonsEqual(
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE,
Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
testRunner.assertNoPositionDiscontinuities();
assertThat(positionHolder[0]).isEqualTo(0);
testRunner.assertPositionDiscontinuityReasonsEqual(Player.DISCONTINUITY_REASON_SEEK);
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
public void stopWithoutResetReleasesMediaSource() throws Exception {
public void stop_withReset_releasesMediaSource() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
final FakeMediaSource mediaSource =
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
.stop(/* reset= */ false)
.stop(/* reset= */ true)
.build();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS);
new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
mediaSource.assertReleased();
testRunner.blockUntilEnded(TIMEOUT_MS);
}
@Test
public void stopWithResetReleasesMediaSource() throws Exception {
public void release_correctMasking() throws Exception {
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 =
new FakeMediaSource(timeline, ExoPlayerTestRunner.VIDEO_FORMAT);
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ 1000)
.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();
ExoPlayerTestRunner testRunner =
new ExoPlayerTestRunner.Builder(context)
.setTimeline(timeline)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(TIMEOUT_MS);
mediaSource.assertReleased();
testRunner.blockUntilEnded(TIMEOUT_MS);
new ExoPlayerTestRunner.Builder(context)
.setMediaSources(mediaSource, mediaSource)
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilActionScheduleFinished(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
......@@ -3533,26 +3641,34 @@ public final class ExoPlayerTest {
FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline);
LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2);
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 =
new ActionSchedule.Builder(TAG)
.pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 5000)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 3000)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
positionMs[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
//noinspection deprecation
player.prepare(mediaSource);
player.seekTo(/* positionMs= */ 5000);
player.seekTo(/* positionMs= */ 7000);
positionMs[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
}
})
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
windowIndex[0] = player.getCurrentWindowIndex();
positionMs[0] = player.getCurrentPosition();
positionMs[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
}
})
.build();
......@@ -3564,7 +3680,13 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS);
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
......@@ -3573,26 +3695,34 @@ public final class ExoPlayerTest {
FakeMediaSource mediaSource = new FakeMediaSource(fakeTimeline);
LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource, 2);
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 =
new ActionSchedule.Builder(TAG)
.pause()
.seek(/* windowIndex= */ 1, /* positionMs= */ C.TIME_UNSET)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 5000)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 3000)
.pause()
.executeRunnable(
new PlayerRunnable() {
@Override
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();
positionMs[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
}
})
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
windowIndex[0] = player.getCurrentWindowIndex();
positionMs[0] = player.getCurrentPosition();
positionMs[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
}
})
.build();
......@@ -3604,7 +3734,819 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS);
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
public void seekTo_singlePeriod_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(9000);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isEqualTo(9000);
assertThat(bufferedPositions[0]).isEqualTo(9200);
assertThat(totalBufferedDuration[0]).isEqualTo(200);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(9200);
assertThat(totalBufferedDuration[1]).isEqualTo(200);
}
@Test
public void seekTo_singlePeriod_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(9200);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isEqualTo(9200);
assertThat(bufferedPositions[0]).isEqualTo(9200);
assertThat(totalBufferedDuration[0]).isEqualTo(0);
assertThat(windowIndex[1]).isEqualTo(windowIndex[0]);
assertThat(positionMs[1]).isEqualTo(positionMs[0]);
assertThat(bufferedPositions[1]).isEqualTo(9200);
assertThat(totalBufferedDuration[1]).isEqualTo(0);
}
@Test
public void seekTo_backwardsSinglePeriod_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(1000);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isEqualTo(1000);
assertThat(bufferedPositions[0]).isEqualTo(1000);
assertThat(totalBufferedDuration[0]).isEqualTo(0);
}
@Test
public void seekTo_backwardsMultiplePeriods_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(0, 1000);
}
},
/* pauseWindowIndex= */ 1,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 9200));
assertThat(windowIndex[0]).isEqualTo(0);
assertThat(positionMs[0]).isEqualTo(1000);
assertThat(bufferedPositions[0]).isEqualTo(1000);
assertThat(totalBufferedDuration[0]).isEqualTo(0);
}
@Test
public void seekTo_toUnbufferedPeriod_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(2, 1000);
}
},
/* pauseWindowIndex= */ 0,
windowIndex,
positionMs,
bufferedPositions,
totalBufferedDuration,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)),
createPartiallyBufferedMediaSource(/* maxBufferedPositionMs= */ 0));
assertThat(windowIndex[0]).isEqualTo(2);
assertThat(positionMs[0]).isEqualTo(1000);
assertThat(bufferedPositions[0]).isEqualTo(1000);
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
public void run(SimpleExoPlayer player) {
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
......@@ -4536,6 +5478,80 @@ public final class ExoPlayerTest {
}
@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 {
AtomicLong positionAfterSetShuffleOrder = new AtomicLong(C.TIME_UNSET);
ActionSchedule actionSchedule =
......@@ -4866,13 +5882,14 @@ public final class ExoPlayerTest {
}
@Test
public void setMediaSources_whenEmpty_validInitialSeek_correctMaskingWindowIndex()
throws Exception {
public void setMediaSources_whenEmpty_validInitialSeek_correctMasking() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 2);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object());
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
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 =
new ActionSchedule.Builder(TAG)
// Wait for initial seek to be fully handled by internal player.
......@@ -4883,9 +5900,13 @@ public final class ExoPlayerTest {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPositions[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
// Increase current window index.
player.addMediaSource(/* index= */ 0, secondMediaSource);
currentWindowIndices[1] = player.getCurrentWindowIndex();
currentPositions[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
}
})
.prepare()
......@@ -4895,11 +5916,13 @@ public final class ExoPlayerTest {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndices[2] = player.getCurrentWindowIndex();
currentPositions[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
}
})
.build();
new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET)
.initialSeek(/* windowIndex= */ 1, 2000)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
.build()
......@@ -4907,16 +5930,19 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertArrayEquals(new int[] {1, 2, 2}, currentWindowIndices);
assertArrayEquals(new long[] {2000, 2000, 2000}, currentPositions);
assertArrayEquals(new long[] {2000, 2000, 2000}, bufferedPositions);
}
@Test
public void setMediaSources_whenEmpty_invalidInitialSeek_correctMaskingWindowIndex()
throws Exception {
public void setMediaSources_whenEmpty_invalidInitialSeek_correctMasking() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1, new Object());
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
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 =
new ActionSchedule.Builder(TAG)
// Wait for initial seek to be fully handled by internal player.
......@@ -4927,9 +5953,13 @@ public final class ExoPlayerTest {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPositions[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
// Increase current window index.
player.addMediaSource(/* index= */ 0, secondMediaSource);
currentWindowIndices[1] = player.getCurrentWindowIndex();
currentPositions[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
}
})
.prepare()
......@@ -4939,12 +5969,14 @@ public final class ExoPlayerTest {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndices[2] = player.getCurrentWindowIndex();
currentPositions[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
}
})
.waitForPlaybackState(Player.STATE_ENDED)
.build();
new ExoPlayerTestRunner.Builder(context)
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET)
.initialSeek(/* windowIndex= */ 1, 2000)
.setMediaSources(firstMediaSource)
.setActionSchedule(actionSchedule)
.build()
......@@ -4952,6 +5984,8 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
assertArrayEquals(new int[] {0, 1, 1}, currentWindowIndices);
assertArrayEquals(new long[] {0, 0, 0}, currentPositions);
assertArrayEquals(new long[] {0, 0, 0}, bufferedPositions);
}
@Test
......@@ -5542,10 +6576,47 @@ public final class ExoPlayerTest {
}
@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 {
final int[] currentWindowIndices = new int[5];
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 =
new ActionSchedule.Builder(TAG)
// Wait for initial seek to be fully handled by internal player.
......@@ -5556,6 +6627,9 @@ public final class ExoPlayerTest {
@Override
public void run(SimpleExoPlayer player) {
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());
currentWindowIndices[1] = player.getCurrentWindowIndex();
player.addMediaSource(
......@@ -5566,26 +6640,39 @@ public final class ExoPlayerTest {
/* index= */ 0,
new FakeMediaSource(new FakeTimeline(/* windowCount= */ 1)));
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()
.waitForPlaybackState(Player.STATE_READY)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndices[4] = player.getCurrentWindowIndex();
// Finally original playbackInfo coming from EPII is used.
currentPositions[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
}
})
.build();
new ExoPlayerTestRunner.Builder(context)
.skipSettingMediaSources()
.initialSeek(/* windowIndex= */ 1, C.TIME_UNSET)
.initialSeek(/* windowIndex= */ 1, 2000)
.setActionSchedule(actionSchedule)
.build()
.start(/* doPrepare= */ false)
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
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
......@@ -5784,13 +6871,14 @@ public final class ExoPlayerTest {
}
@Test
public void removeMediaItems_currentItemRemoved_correctMaskingWindowIndex() throws Exception {
public void removeMediaItems_currentItemRemoved_correctMasking() throws Exception {
Timeline firstTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource firstMediaSource = new FakeMediaSource(firstTimeline);
Timeline secondTimeline = new FakeTimeline(/* windowCount= */ 1);
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, C.INDEX_UNSET};
final long[] currentPositions = {C.TIME_UNSET, C.TIME_UNSET};
final long[] bufferedPositions = {C.TIME_UNSET, C.TIME_UNSET};
ActionSchedule actionSchedule =
new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_BUFFERING)
......@@ -5801,9 +6889,11 @@ public final class ExoPlayerTest {
// Remove the current item.
currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPositions[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
player.removeMediaItem(/* index= */ 1);
currentWindowIndices[1] = player.getCurrentWindowIndex();
currentPositions[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
}
})
.build();
......@@ -5817,7 +6907,9 @@ public final class ExoPlayerTest {
.blockUntilEnded(TIMEOUT_MS);
assertArrayEquals(new int[] {1, 1}, currentWindowIndices);
assertThat(currentPositions[0]).isAtLeast(5000L);
assertThat(bufferedPositions[0]).isAtLeast(5000L);
assertThat(currentPositions[1]).isEqualTo(0);
assertThat(bufferedPositions[1]).isAtLeast(0);
}
@Test
......@@ -5834,6 +6926,10 @@ public final class ExoPlayerTest {
Arrays.fill(currentWindowIndices, C.INDEX_UNSET);
final int[] maskingPlaybackStates = new int[4];
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 =
new ActionSchedule.Builder(TAG)
.waitForPlaybackState(Player.STATE_READY)
......@@ -5843,12 +6939,16 @@ public final class ExoPlayerTest {
public void run(SimpleExoPlayer player) {
// Expect the current window index to be 2 after seek.
currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPositions[0] = player.getCurrentPosition();
bufferedPositions[0] = player.getBufferedPosition();
player.removeMediaItem(/* index= */ 2);
// Expect the current window index to be 0
// (default position of timeline after not finding subsequent period).
currentWindowIndices[1] = player.getCurrentWindowIndex();
// Transition to ENDED.
maskingPlaybackStates[0] = player.getPlaybackState();
currentPositions[1] = player.getCurrentPosition();
bufferedPositions[1] = player.getBufferedPosition();
}
})
.waitForPlaybackState(Player.STATE_ENDED)
......@@ -5864,6 +6964,8 @@ public final class ExoPlayerTest {
currentWindowIndices[3] = player.getCurrentWindowIndex();
// Remains in ENDED.
maskingPlaybackStates[1] = player.getPlaybackState();
currentPositions[2] = player.getCurrentPosition();
bufferedPositions[2] = player.getBufferedPosition();
}
})
.waitForTimelineChanged()
......@@ -5932,6 +7034,12 @@ public final class ExoPlayerTest {
}, // buffers after set items with seek
maskingPlaybackStates);
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
......@@ -5969,16 +7077,24 @@ public final class ExoPlayerTest {
MediaSource secondMediaSource = new FakeMediaSource(secondTimeline);
final int[] currentWindowIndices = {C.INDEX_UNSET, 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 =
new ActionSchedule.Builder(TAG)
.pause()
.waitForPlaybackState(Player.STATE_BUFFERING)
.playUntilPosition(/* windowIndex= */ 1, /* positionMs= */ 150)
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
currentWindowIndices[0] = player.getCurrentWindowIndex();
currentPosition[0] = player.getCurrentPosition();
bufferedPosition[0] = player.getBufferedPosition();
player.clearMediaItems();
currentWindowIndices[1] = player.getCurrentWindowIndex();
currentPosition[1] = player.getCurrentPosition();
bufferedPosition[1] = player.getBufferedPosition();
maskingPlaybackState[0] = player.getPlaybackState();
}
})
......@@ -5992,6 +7108,11 @@ public final class ExoPlayerTest {
.blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS);
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);
}
......
......@@ -421,7 +421,7 @@ public final class AnalyticsCollectorTest {
assertThat(loadingEvents).hasSize(4);
assertThat(loadingEvents).containsAtLeast(period0, period0).inOrder();
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
.containsExactly(period0, period1)
.containsExactly(period0, period1, period1)
.inOrder();
assertThat(listener.getEvents(EVENT_LOAD_STARTED))
.containsExactly(
......@@ -887,7 +887,7 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_LOADING_CHANGED))
.containsExactly(period0Seq0, period0Seq0, period0Seq0, period0Seq0);
assertThat(listener.getEvents(EVENT_TRACKS_CHANGED))
.containsExactly(period0Seq0, period0Seq1)
.containsExactly(period0Seq0, period0Seq1, period0Seq1)
.inOrder();
assertThat(listener.getEvents(EVENT_LOAD_STARTED))
.containsExactly(WINDOW_0 /* manifest */, period0Seq0 /* media */, period1Seq1 /* media */)
......
......@@ -72,6 +72,7 @@ public class FakeMediaPeriod implements MediaPeriod {
private boolean prepared;
private long seekOffsetUs;
private long discontinuityPositionUs;
private long bufferedPositionUs;
/**
* Constructs a FakeMediaPeriod with a single sample for each track in {@code trackGroupArray}.
......@@ -149,6 +150,7 @@ public class FakeMediaPeriod implements MediaPeriod {
this.drmEventDispatcher = drmEventDispatcher;
this.deferOnPrepared = deferOnPrepared;
this.trackDataFactory = trackDataFactory;
this.bufferedPositionUs = C.TIME_END_OF_SOURCE;
discontinuityPositionUs = C.TIME_UNSET;
sampleStreams = new ArrayList<>();
fakePreparationLoadTaskId = LoadEventInfo.getNewId();
......@@ -283,7 +285,11 @@ public class FakeMediaPeriod implements MediaPeriod {
@Override
public long getBufferedPositionUs() {
assertThat(prepared).isTrue();
return C.TIME_END_OF_SOURCE;
return bufferedPositionUs;
}
public void setBufferedPositionUs(long bufferedPositionUs) {
this.bufferedPositionUs = bufferedPositionUs;
}
@Override
......@@ -293,6 +299,9 @@ public class FakeMediaPeriod implements MediaPeriod {
for (SampleStream sampleStream : sampleStreams) {
seekSampleStream(sampleStream, seekPositionUs);
}
if (bufferedPositionUs != C.TIME_END_OF_SOURCE && seekPositionUs > bufferedPositionUs) {
bufferedPositionUs = 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