diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index 953021fe6f..0d77624a7b 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -95,6 +95,11 @@ import java.util.Locale; + getStateString(state) + "]"); } + @Override + public void onRepeatModeChanged(@ExoPlayer.RepeatMode int repeatMode) { + Log.d(TAG, "repeatMode [" + getRepeatModeString(repeatMode) + "]"); + } + @Override public void onPositionDiscontinuity() { Log.d(TAG, "positionDiscontinuity"); @@ -461,4 +466,12 @@ import java.util.Locale; return enabled ? "[X]" : "[ ]"; } + private static String getRepeatModeString(@ExoPlayer.RepeatMode int repeatMode) { + switch (repeatMode) { + case ExoPlayer.REPEAT_MODE_OFF: + return "OFF"; + default: + return "?"; + } + } } diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 2542f23e95..333b3bc42c 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -424,6 +424,11 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay updateButtonVisibilities(); } + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } + @Override public void onPositionDiscontinuity() { if (needRetrySource) { diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index 21f01f0cca..a49ae073ef 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -126,6 +126,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase { } } + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } + private void releasePlayerAndQuitLooper() { player.release(); Looper.myLooper().quit(); diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index 263934d982..76e19b0ebe 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -126,6 +126,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase { } } + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } + private void releasePlayerAndQuitLooper() { player.release(); Looper.myLooper().quit(); diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index 2647776b74..669d77cdeb 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -158,6 +158,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase { } } + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } + private void releasePlayerAndQuitLooper() { player.release(); Looper.myLooper().quit(); diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index 2c10bfe6a0..00eba6b52b 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -340,6 +340,11 @@ public final class ExoPlayerTest extends TestCase { } } + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } + @Override public void onTimelineChanged(Timeline timeline, Object manifest) { sourceInfos.add(Pair.create(timeline, manifest)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index be04924753..6327ebd9c2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -154,6 +154,13 @@ public interface ExoPlayer { */ void onPlayerStateChanged(boolean playWhenReady, int playbackState); + /** + * Called when the value of {@link #getRepeatMode()} changes. + * + * @param repeatMode The {@link RepeatMode} used for playback. + */ + void onRepeatModeChanged(@RepeatMode int repeatMode); + /** * Called when an error occurs. The playback state will transition to {@link #STATE_IDLE} * immediately after this method is called. The player instance can still be used, and @@ -262,7 +269,7 @@ public interface ExoPlayer { */ @Retention(RetentionPolicy.SOURCE) @IntDef({REPEAT_MODE_OFF}) - @interface RepeatMode {} + public @interface RepeatMode {} /** * Normal playback without repetition. */ @@ -328,6 +335,20 @@ public interface ExoPlayer { */ boolean getPlayWhenReady(); + /** + * Sets the {@link RepeatMode} to be used for playback. + * + * @param repeatMode A repeat mode. + */ + void setRepeatMode(@RepeatMode int repeatMode); + + /** + * Returns the current {@link RepeatMode} used for playback. + * + * @return The current repeat mode. + */ + @RepeatMode int getRepeatMode(); + /** * Whether the player is currently loading the source. * 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 cb0958a3b1..94c43167d8 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 @@ -51,6 +51,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private boolean tracksSelected; private boolean playWhenReady; + @RepeatMode int repeatMode; private int playbackState; private int pendingSeekAcks; private int pendingPrepareAcks; @@ -83,6 +84,7 @@ import java.util.concurrent.CopyOnWriteArraySet; this.renderers = Assertions.checkNotNull(renderers); this.trackSelector = Assertions.checkNotNull(trackSelector); this.playWhenReady = false; + this.repeatMode = REPEAT_MODE_OFF; this.playbackState = STATE_IDLE; this.listeners = new CopyOnWriteArraySet<>(); emptyTrackSelections = new TrackSelectionArray(new TrackSelection[renderers.length]); @@ -101,7 +103,7 @@ import java.util.concurrent.CopyOnWriteArraySet; }; playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0, 0); internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady, - eventHandler, playbackInfo, this); + repeatMode, eventHandler, playbackInfo, this); } @Override @@ -164,6 +166,22 @@ import java.util.concurrent.CopyOnWriteArraySet; return playWhenReady; } + @Override + public void setRepeatMode(@RepeatMode int repeatMode) { + if (this.repeatMode != repeatMode) { + this.repeatMode = repeatMode; + internalPlayer.setRepeatMode(repeatMode); + for (EventListener listener : listeners) { + listener.onRepeatModeChanged(repeatMode); + } + } + } + + @Override + public @RepeatMode int getRepeatMode() { + return repeatMode; + } + @Override public boolean isLoading() { return isLoading; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 2410e19f04..b8b1314504 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -112,6 +112,7 @@ import java.io.IOException; private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 9; private static final int MSG_TRACK_SELECTION_INVALIDATED = 10; private static final int MSG_CUSTOM = 11; + private static final int MSG_SET_REPEAT_MODE = 12; private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10; @@ -155,6 +156,7 @@ import java.io.IOException; private boolean rebuffering; private boolean isLoading; private int state; + private @ExoPlayer.RepeatMode int repeatMode; private int customMessagesSent; private int customMessagesProcessed; private long elapsedRealtimeUs; @@ -170,12 +172,13 @@ import java.io.IOException; private Timeline timeline; public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, - LoadControl loadControl, boolean playWhenReady, Handler eventHandler, - PlaybackInfo playbackInfo, ExoPlayer player) { + LoadControl loadControl, boolean playWhenReady, @ExoPlayer.RepeatMode int repeatMode, + Handler eventHandler, PlaybackInfo playbackInfo, ExoPlayer player) { this.renderers = renderers; this.trackSelector = trackSelector; this.loadControl = loadControl; this.playWhenReady = playWhenReady; + this.repeatMode = repeatMode; this.eventHandler = eventHandler; this.state = ExoPlayer.STATE_IDLE; this.playbackInfo = playbackInfo; @@ -210,6 +213,10 @@ import java.io.IOException; handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget(); } + public void setRepeatMode(@ExoPlayer.RepeatMode int repeatMode) { + handler.obtainMessage(MSG_SET_REPEAT_MODE, repeatMode, 0).sendToTarget(); + } + public void seekTo(Timeline timeline, int windowIndex, long positionUs) { handler.obtainMessage(MSG_SEEK_TO, new SeekPosition(timeline, windowIndex, positionUs)) .sendToTarget(); @@ -304,6 +311,10 @@ import java.io.IOException; setPlayWhenReadyInternal(msg.arg1 != 0); return true; } + case MSG_SET_REPEAT_MODE: { + setRepeatModeInternal(msg.arg1); + return true; + } case MSG_DO_SOME_WORK: { doSomeWork(); return true; @@ -411,6 +422,10 @@ import java.io.IOException; } } + private void setRepeatModeInternal(@ExoPlayer.RepeatMode int repeatMode) { + this.repeatMode = repeatMode; + } + private void startRenderers() throws ExoPlaybackException { rebuffering = false; standaloneMediaClock.start(); @@ -959,8 +974,7 @@ import java.io.IOException; while (periodHolder.next != null) { MediaPeriodHolder previousPeriodHolder = periodHolder; periodHolder = periodHolder.next; - periodIndex = timeline.getNextPeriodIndex(periodIndex, period, window, - ExoPlayer.REPEAT_MODE_OFF); + periodIndex = timeline.getNextPeriodIndex(periodIndex, period, window, repeatMode); boolean isLastPeriod = isLastPeriod(periodIndex); timeline.getPeriod(periodIndex, period, true); if (periodHolder.uid.equals(period.uid)) { @@ -1022,8 +1036,7 @@ import java.io.IOException; int newPeriodIndex = C.INDEX_UNSET; int maxIterations = oldTimeline.getPeriodCount(); for (int i = 0; i < maxIterations && newPeriodIndex == C.INDEX_UNSET; i++) { - oldPeriodIndex = oldTimeline.getNextPeriodIndex(oldPeriodIndex, period, window, - ExoPlayer.REPEAT_MODE_OFF); + oldPeriodIndex = oldTimeline.getNextPeriodIndex(oldPeriodIndex, period, window, repeatMode); newPeriodIndex = newTimeline.getIndexOfPeriod( oldTimeline.getPeriod(oldPeriodIndex, period, true).uid); } @@ -1033,7 +1046,7 @@ import java.io.IOException; private boolean isLastPeriod(int periodIndex) { int windowIndex = timeline.getPeriod(periodIndex, period).windowIndex; return !timeline.getWindow(windowIndex, window).isDynamic - && timeline.isLastPeriod(periodIndex, period, window, ExoPlayer.REPEAT_MODE_OFF); + && timeline.isLastPeriod(periodIndex, period, window, repeatMode); } /** @@ -1247,7 +1260,7 @@ import java.io.IOException; return; } newLoadingPeriodIndex = timeline.getNextPeriodIndex(loadingPeriodHolder.index, period, window, - ExoPlayer.REPEAT_MODE_OFF); + repeatMode); } if (newLoadingPeriodIndex >= timeline.getPeriodCount()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 6094513913..8dcd390033 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -514,6 +514,16 @@ public class SimpleExoPlayer implements ExoPlayer { return player.getPlayWhenReady(); } + @Override + public @RepeatMode int getRepeatMode() { + return player.getRepeatMode(); + } + + @Override + public void setRepeatMode(@RepeatMode int repeatMode) { + player.setRepeatMode(repeatMode); + } + @Override public boolean isLoading() { return player.isLoading(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java index 38c7a5be9c..68fa6a8cc9 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java @@ -86,6 +86,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe updateAndPost(); } + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } + @Override public void onPositionDiscontinuity() { updateAndPost(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index baeada098a..ae5b7c8b13 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -529,10 +529,9 @@ public class PlaybackControlView extends FrameLayout { int windowIndex = player.getCurrentWindowIndex(); timeline.getWindow(windowIndex, window); isSeekable = window.isSeekable; - enablePrevious = !timeline.isFirstWindow(windowIndex, ExoPlayer.REPEAT_MODE_OFF) + enablePrevious = !timeline.isFirstWindow(windowIndex, player.getRepeatMode()) || isSeekable || !window.isDynamic; - enableNext = !timeline.isLastWindow(windowIndex, ExoPlayer.REPEAT_MODE_OFF) - || window.isDynamic; + enableNext = !timeline.isLastWindow(windowIndex, player.getRepeatMode()) || window.isDynamic; if (timeline.getPeriod(player.getCurrentPeriodIndex(), period).isAd) { // Always hide player controls during ads. hide(); @@ -682,8 +681,7 @@ public class PlaybackControlView extends FrameLayout { } int windowIndex = player.getCurrentWindowIndex(); timeline.getWindow(windowIndex, window); - int previousWindowIndex = timeline.getPreviousWindowIndex(windowIndex, - ExoPlayer.REPEAT_MODE_OFF); + int previousWindowIndex = timeline.getPreviousWindowIndex(windowIndex, player.getRepeatMode()); if (previousWindowIndex != C.INDEX_UNSET && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS || (window.isDynamic && !window.isSeekable))) { @@ -699,7 +697,7 @@ public class PlaybackControlView extends FrameLayout { return; } int windowIndex = player.getCurrentWindowIndex(); - int nextWindowIndex = timeline.getNextWindowIndex(windowIndex, ExoPlayer.REPEAT_MODE_OFF); + int nextWindowIndex = timeline.getNextWindowIndex(windowIndex, player.getRepeatMode()); if (nextWindowIndex != C.INDEX_UNSET) { seekTo(nextWindowIndex, C.TIME_UNSET); } else if (timeline.getWindow(windowIndex, window, false).isDynamic) { @@ -908,6 +906,11 @@ public class PlaybackControlView extends FrameLayout { updateProgress(); } + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } + @Override public void onPositionDiscontinuity() { updateNavigation(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index fce05f5bc4..5219778109 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -806,6 +806,11 @@ public final class SimpleExoPlayerView extends FrameLayout { maybeShowController(false); } + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } + @Override public void onPlayerError(ExoPlaybackException e) { // Do nothing. diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java index f48318687d..50791d5c83 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java @@ -213,6 +213,11 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen this.playing = playing; } + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } + @Override public final void onPlayerError(ExoPlaybackException error) { playerWasPrepared = true;