diff --git a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java index a3105192e5..e77a654b5b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/BasePlayer.java @@ -21,6 +21,7 @@ import static java.lang.Math.min; import androidx.annotation.Nullable; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.ForOverride; import java.util.List; /** Abstract base {@link Player} which implements common implementation independent methods. */ @@ -185,7 +186,12 @@ public abstract class BasePlayer implements Player { @Override public final void seekToPreviousMediaItem() { int previousMediaItemIndex = getPreviousMediaItemIndex(); - if (previousMediaItemIndex != C.INDEX_UNSET) { + if (previousMediaItemIndex == C.INDEX_UNSET) { + return; + } + if (previousMediaItemIndex == getCurrentMediaItemIndex()) { + repeatCurrentMediaItem(); + } else { seekToDefaultPosition(previousMediaItemIndex); } } @@ -252,7 +258,12 @@ public abstract class BasePlayer implements Player { @Override public final void seekToNextMediaItem() { int nextMediaItemIndex = getNextMediaItemIndex(); - if (nextMediaItemIndex != C.INDEX_UNSET) { + if (nextMediaItemIndex == C.INDEX_UNSET) { + return; + } + if (nextMediaItemIndex == getCurrentMediaItemIndex()) { + repeatCurrentMediaItem(); + } else { seekToDefaultPosition(nextMediaItemIndex); } } @@ -424,6 +435,17 @@ public abstract class BasePlayer implements Player { : timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); } + /** + * Repeat the current media item. + * + *
The default implementation seeks to the default position in the current item, which can be
+ * overridden for additional handling.
+ */
+ @ForOverride
+ protected void repeatCurrentMediaItem() {
+ seekToDefaultPosition();
+ }
+
private @RepeatMode int getRepeatModeForNavigation() {
@RepeatMode int repeatMode = getRepeatMode();
return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
index cca1d55292..c2b9bb4a8f 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
@@ -531,7 +531,8 @@ import java.util.concurrent.TimeoutException;
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* ignored */ C.TIME_UNSET,
- /* ignored */ C.INDEX_UNSET);
+ /* ignored */ C.INDEX_UNSET,
+ /* repeatCurrentMediaItem= */ false);
}
@Override
@@ -652,7 +653,8 @@ import java.util.concurrent.TimeoutException;
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* ignored */ C.TIME_UNSET,
- /* ignored */ C.INDEX_UNSET);
+ /* ignored */ C.INDEX_UNSET,
+ /* repeatCurrentMediaItem= */ false);
}
@Override
@@ -670,7 +672,8 @@ import java.util.concurrent.TimeoutException;
positionDiscontinuity,
DISCONTINUITY_REASON_REMOVE,
/* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo),
- /* ignored */ C.INDEX_UNSET);
+ /* ignored */ C.INDEX_UNSET,
+ /* repeatCurrentMediaItem= */ false);
}
@Override
@@ -700,7 +703,8 @@ import java.util.concurrent.TimeoutException;
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* ignored */ C.TIME_UNSET,
- /* ignored */ C.INDEX_UNSET);
+ /* ignored */ C.INDEX_UNSET,
+ /* repeatCurrentMediaItem= */ false);
}
@Override
@@ -724,7 +728,8 @@ import java.util.concurrent.TimeoutException;
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* ignored */ C.TIME_UNSET,
- /* ignored */ C.INDEX_UNSET);
+ /* ignored */ C.INDEX_UNSET,
+ /* repeatCurrentMediaItem= */ false);
}
@Override
@@ -803,47 +808,17 @@ import java.util.concurrent.TimeoutException;
return playbackInfo.isLoading;
}
+ @Override
+ protected void repeatCurrentMediaItem() {
+ verifyApplicationThread();
+ seekToInternal(
+ getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET, /* repeatMediaItem= */ true);
+ }
+
@Override
public void seekTo(int mediaItemIndex, long positionMs) {
verifyApplicationThread();
- analyticsCollector.notifySeekStarted();
- Timeline timeline = playbackInfo.timeline;
- if (mediaItemIndex < 0
- || (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) {
- throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs);
- }
- pendingOperationAcks++;
- if (isPlayingAd()) {
- // TODO: Investigate adding support for seeking during ads. This is complicated to do in
- // general because the midroll ad preceding the seek destination must be played before the
- // content position can be played, if a different ad is playing at the moment.
- Log.w(TAG, "seekTo ignored because an ad is playing");
- ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate =
- new ExoPlayerImplInternal.PlaybackInfoUpdate(this.playbackInfo);
- playbackInfoUpdate.incrementPendingOperationAcks(1);
- playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate);
- return;
- }
- @Player.State
- int newPlaybackState =
- getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : STATE_BUFFERING;
- int oldMaskingMediaItemIndex = getCurrentMediaItemIndex();
- PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState);
- newPlaybackInfo =
- maskTimelineAndPosition(
- newPlaybackInfo,
- timeline,
- maskWindowPositionMsOrGetPeriodPositionUs(timeline, mediaItemIndex, positionMs));
- internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs));
- updatePlaybackInfo(
- newPlaybackInfo,
- /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
- /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
- /* seekProcessed= */ true,
- /* positionDiscontinuity= */ true,
- /* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK,
- /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo),
- oldMaskingMediaItemIndex);
+ seekToInternal(mediaItemIndex, positionMs, /* repeatMediaItem= */ false);
}
@Override
@@ -884,7 +859,8 @@ import java.util.concurrent.TimeoutException;
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* ignored */ C.TIME_UNSET,
- /* ignored */ C.INDEX_UNSET);
+ /* ignored */ C.INDEX_UNSET,
+ /* repeatCurrentMediaItem= */ false);
}
@Override
@@ -1733,7 +1709,8 @@ import java.util.concurrent.TimeoutException;
positionDiscontinuity,
DISCONTINUITY_REASON_REMOVE,
/* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(playbackInfo),
- /* ignored */ C.INDEX_UNSET);
+ /* ignored */ C.INDEX_UNSET,
+ /* repeatCurrentMediaItem= */ false);
}
private int getCurrentWindowIndexInternal() {
@@ -1815,7 +1792,8 @@ import java.util.concurrent.TimeoutException;
positionDiscontinuity,
pendingDiscontinuityReason,
discontinuityWindowStartPositionUs,
- /* ignored */ C.INDEX_UNSET);
+ /* ignored */ C.INDEX_UNSET,
+ /* repeatCurrentMediaItem= */ false);
}
}
@@ -1829,7 +1807,8 @@ import java.util.concurrent.TimeoutException;
boolean positionDiscontinuity,
@DiscontinuityReason int positionDiscontinuityReason,
long discontinuityWindowStartPositionUs,
- int oldMaskingMediaItemIndex) {
+ int oldMaskingMediaItemIndex,
+ boolean repeatCurrentMediaItem) {
// Assign playback info immediately such that all getters return the right values, but keep
// snapshot of previous and new state so that listener invocations are triggered correctly.
@@ -1837,13 +1816,15 @@ import java.util.concurrent.TimeoutException;
PlaybackInfo newPlaybackInfo = playbackInfo;
this.playbackInfo = playbackInfo;
+ boolean timelineChanged = !previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline);
Pair