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 0d77624a7b..686718f9e0 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
@@ -470,6 +470,10 @@ import java.util.Locale;
switch (repeatMode) {
case ExoPlayer.REPEAT_MODE_OFF:
return "OFF";
+ case ExoPlayer.REPEAT_MODE_ONE:
+ return "ONE";
+ case ExoPlayer.REPEAT_MODE_ALL:
+ return "ALL";
default:
return "?";
}
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 6327ebd9c2..8e16de5fca 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
@@ -268,12 +268,20 @@ public interface ExoPlayer {
* Repeat modes for playback.
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef({REPEAT_MODE_OFF})
+ @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL})
public @interface RepeatMode {}
/**
* Normal playback without repetition.
*/
int REPEAT_MODE_OFF = 0;
+ /**
+ * "Repeat One" mode to repeat the currently playing window infinitely.
+ */
+ int REPEAT_MODE_ONE = 1;
+ /**
+ * "Repeat All" mode to repeat the entire timeline infinitely.
+ */
+ int REPEAT_MODE_ALL = 2;
/**
* Register a listener to receive events from the player. The listener's methods will be called on
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 ae5b7c8b13..337cb47b1e 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
@@ -18,13 +18,17 @@ package com.google.android.exoplayer2.ui;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.os.SystemClock;
+import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
@@ -35,6 +39,8 @@ import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Formatter;
import java.util.Locale;
@@ -70,6 +76,14 @@ import java.util.Locale;
*
Default: {@link #DEFAULT_FAST_FORWARD_MS}
*
*
+ * {@code repeat_toggle_modes} - A flagged enumeration value specifying which repeat
+ * mode toggle options are enabled. Valid values are: {@code none}, {@code one},
+ * {@code all}, or {@code one|all}.
+ *
+ * - Corresponding method: {@link #setRepeatToggleModes(int)}
+ * - Default: {@link #DEFAULT_REPEAT_TOGGLE_MODES}
+ *
+ *
* {@code controller_layout_id} - Specifies the id of the layout to be inflated. See
* below for more details.
*
@@ -117,6 +131,11 @@ import java.util.Locale;
* - Type: {@link View}
*
*
+ * {@code exo_repeat_toggle} - The repeat toggle button.
+ *
+ * - Type: {@link View}
+ *
+ *
* {@code exo_position} - Text view displaying the current playback position.
*
* - Type: {@link TextView}
@@ -189,6 +208,14 @@ public class PlaybackControlView extends FrameLayout {
*/
boolean dispatchSeekTo(ExoPlayer player, int windowIndex, long positionMs);
+ /**
+ * Dispatches a {@link ExoPlayer#setRepeatMode(int)} operation.
+ *
+ * @param player The player to which the operation should be dispatched.
+ * @param repeatMode The repeat mode.
+ * @return True if the operation was dispatched. False if suppressed.
+ */
+ boolean dispatchSetRepeatMode(ExoPlayer player, @ExoPlayer.RepeatMode int repeatMode);
}
/**
@@ -209,11 +236,38 @@ public class PlaybackControlView extends FrameLayout {
return true;
}
+ @Override
+ public boolean dispatchSetRepeatMode(ExoPlayer player, @ExoPlayer.RepeatMode int repeatMode) {
+ player.setRepeatMode(repeatMode);
+ return true;
+ }
+
};
+ /**
+ * Set of repeat toggle modes. Can be combined using bit-wise operations.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {REPEAT_TOGGLE_MODE_NONE, REPEAT_TOGGLE_MODE_ONE,
+ REPEAT_TOGGLE_MODE_ALL})
+ public @interface RepeatToggleModes {}
+ /**
+ * All repeat mode buttons disabled.
+ */
+ public static final int REPEAT_TOGGLE_MODE_NONE = 0;
+ /**
+ * "Repeat One" button enabled.
+ */
+ public static final int REPEAT_TOGGLE_MODE_ONE = 1;
+ /**
+ * "Repeat All" button enabled.
+ */
+ public static final int REPEAT_TOGGLE_MODE_ALL = 2;
+
public static final int DEFAULT_FAST_FORWARD_MS = 15000;
public static final int DEFAULT_REWIND_MS = 5000;
public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000;
+ public static final @RepeatToggleModes int DEFAULT_REPEAT_TOGGLE_MODES = REPEAT_TOGGLE_MODE_NONE;
/**
* The maximum number of windows that can be shown in a multi-window time bar.
@@ -229,6 +283,7 @@ public class PlaybackControlView extends FrameLayout {
private final View pauseButton;
private final View fastForwardButton;
private final View rewindButton;
+ private final ImageView repeatToggleButton;
private final TextView durationView;
private final TextView positionView;
private final TimeBar timeBar;
@@ -237,6 +292,13 @@ public class PlaybackControlView extends FrameLayout {
private final Timeline.Period period;
private final Timeline.Window window;
+ private final Drawable repeatOffButtonDrawable;
+ private final Drawable repeatOneButtonDrawable;
+ private final Drawable repeatAllButtonDrawable;
+ private final String repeatOffButtonContentDescription;
+ private final String repeatOneButtonContentDescription;
+ private final String repeatAllButtonContentDescription;
+
private ExoPlayer player;
private ControlDispatcher controlDispatcher;
private VisibilityListener visibilityListener;
@@ -248,6 +310,7 @@ public class PlaybackControlView extends FrameLayout {
private int rewindMs;
private int fastForwardMs;
private int showTimeoutMs;
+ private @RepeatToggleModes int repeatToggleModes;
private long hideAtMs;
private long[] adBreakTimesMs;
@@ -280,6 +343,7 @@ public class PlaybackControlView extends FrameLayout {
rewindMs = DEFAULT_REWIND_MS;
fastForwardMs = DEFAULT_FAST_FORWARD_MS;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
+ repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.PlaybackControlView, 0, 0);
@@ -290,6 +354,7 @@ public class PlaybackControlView extends FrameLayout {
showTimeoutMs = a.getInt(R.styleable.PlaybackControlView_show_timeout, showTimeoutMs);
controllerLayoutId = a.getResourceId(R.styleable.PlaybackControlView_controller_layout_id,
controllerLayoutId);
+ repeatToggleModes = getRepeatToggleModes(a, repeatToggleModes);
} finally {
a.recycle();
}
@@ -335,6 +400,26 @@ public class PlaybackControlView extends FrameLayout {
if (fastForwardButton != null) {
fastForwardButton.setOnClickListener(componentListener);
}
+ repeatToggleButton = (ImageView) findViewById(R.id.exo_repeat_toggle);
+ if (repeatToggleButton != null) {
+ repeatToggleButton.setOnClickListener(componentListener);
+ }
+ Resources resources = context.getResources();
+ repeatOffButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_off);
+ repeatOneButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_one);
+ repeatAllButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_all);
+ repeatOffButtonContentDescription = resources.getString(
+ R.string.exo_controls_repeat_off_description);
+ repeatOneButtonContentDescription = resources.getString(
+ R.string.exo_controls_repeat_one_description);
+ repeatAllButtonContentDescription = resources.getString(
+ R.string.exo_controls_repeat_all_description);
+ }
+
+ @SuppressWarnings("ResourceType")
+ private static @RepeatToggleModes int getRepeatToggleModes(TypedArray a,
+ @RepeatToggleModes int repeatToggleModes) {
+ return a.getInt(R.styleable.PlaybackControlView_repeat_toggle_modes, repeatToggleModes);
}
/**
@@ -440,6 +525,37 @@ public class PlaybackControlView extends FrameLayout {
this.showTimeoutMs = showTimeoutMs;
}
+ /**
+ * Returns which repeat toggle modes are enabled.
+ *
+ * @return The currently enabled {@link RepeatToggleModes}.
+ */
+ public @RepeatToggleModes int getRepeatToggleModes() {
+ return repeatToggleModes;
+ }
+
+ /**
+ * Sets which repeat toggle modes are enabled.
+ *
+ * @param repeatToggleModes A set of {@link RepeatToggleModes}.
+ */
+ public void setRepeatToggleModes(@RepeatToggleModes int repeatToggleModes) {
+ this.repeatToggleModes = repeatToggleModes;
+ if (player != null) {
+ @ExoPlayer.RepeatMode int currentMode = player.getRepeatMode();
+ if (repeatToggleModes == REPEAT_TOGGLE_MODE_NONE
+ && currentMode != ExoPlayer.REPEAT_MODE_OFF) {
+ controlDispatcher.dispatchSetRepeatMode(player, ExoPlayer.REPEAT_MODE_OFF);
+ } else if (repeatToggleModes == REPEAT_TOGGLE_MODE_ONE
+ && currentMode == ExoPlayer.REPEAT_MODE_ALL) {
+ controlDispatcher.dispatchSetRepeatMode(player, ExoPlayer.REPEAT_MODE_ONE);
+ } else if (repeatToggleModes == REPEAT_TOGGLE_MODE_ALL
+ && currentMode == ExoPlayer.REPEAT_MODE_ONE) {
+ controlDispatcher.dispatchSetRepeatMode(player, ExoPlayer.REPEAT_MODE_ALL);
+ }
+ }
+ }
+
/**
* Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will
* be automatically hidden after this duration of time has elapsed without user input.
@@ -494,6 +610,7 @@ public class PlaybackControlView extends FrameLayout {
private void updateAll() {
updatePlayPauseButton();
updateNavigation();
+ updateRepeatModeButton();
updateProgress();
}
@@ -546,6 +663,31 @@ public class PlaybackControlView extends FrameLayout {
}
}
+ private void updateRepeatModeButton() {
+ if (!isVisible() || !isAttachedToWindow) {
+ return;
+ }
+ if (repeatToggleModes == REPEAT_TOGGLE_MODE_NONE) {
+ repeatToggleButton.setVisibility(View.GONE);
+ return;
+ }
+ switch (player.getRepeatMode()) {
+ case ExoPlayer.REPEAT_MODE_OFF:
+ repeatToggleButton.setImageDrawable(repeatOffButtonDrawable);
+ repeatToggleButton.setContentDescription(repeatOffButtonContentDescription);
+ break;
+ case ExoPlayer.REPEAT_MODE_ONE:
+ repeatToggleButton.setImageDrawable(repeatOneButtonDrawable);
+ repeatToggleButton.setContentDescription(repeatOneButtonContentDescription);
+ break;
+ case ExoPlayer.REPEAT_MODE_ALL:
+ repeatToggleButton.setImageDrawable(repeatAllButtonDrawable);
+ repeatToggleButton.setContentDescription(repeatAllButtonContentDescription);
+ break;
+ }
+ repeatToggleButton.setVisibility(View.VISIBLE);
+ }
+
private void updateTimeBarMode() {
if (player == null) {
return;
@@ -705,6 +847,30 @@ public class PlaybackControlView extends FrameLayout {
}
}
+ private @ExoPlayer.RepeatMode int getNextRepeatMode() {
+ @ExoPlayer.RepeatMode int currentMode = player.getRepeatMode();
+ for (int offset = 1; offset <= 2; offset++) {
+ @ExoPlayer.RepeatMode int proposedMode = (currentMode + offset) % 3;
+ if (isRepeatModeEnabled(proposedMode)) {
+ return proposedMode;
+ }
+ }
+ return currentMode;
+ }
+
+ private boolean isRepeatModeEnabled(@ExoPlayer.RepeatMode int repeatMode) {
+ switch (repeatMode) {
+ case ExoPlayer.REPEAT_MODE_OFF:
+ return true;
+ case ExoPlayer.REPEAT_MODE_ONE:
+ return (repeatToggleModes & REPEAT_TOGGLE_MODE_ONE) != 0;
+ case ExoPlayer.REPEAT_MODE_ALL:
+ return (repeatToggleModes & REPEAT_TOGGLE_MODE_ALL) != 0;
+ default:
+ return false;
+ }
+ }
+
private void rewind() {
if (rewindMs <= 0) {
return;
@@ -908,7 +1074,8 @@ public class PlaybackControlView extends FrameLayout {
@Override
public void onRepeatModeChanged(int repeatMode) {
- // Do nothing.
+ updateRepeatModeButton();
+ updateNavigation();
}
@Override
@@ -959,6 +1126,8 @@ public class PlaybackControlView extends FrameLayout {
controlDispatcher.dispatchSetPlayWhenReady(player, true);
} else if (pauseButton == view) {
controlDispatcher.dispatchSetPlayWhenReady(player, false);
+ } else if (repeatToggleButton == view) {
+ controlDispatcher.dispatchSetRepeatMode(player, getNextRepeatMode());
}
}
hideAfterTimeout();
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 5219778109..5cbfb638a5 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
@@ -591,6 +591,16 @@ public final class SimpleExoPlayerView extends FrameLayout {
controller.setFastForwardIncrementMs(fastForwardMs);
}
+ /**
+ * Sets which repeat toggle modes are enabled.
+ *
+ * @param repeatToggleModes A set of {@link PlaybackControlView.RepeatToggleModes}.
+ */
+ public void setRepeatToggleModes(@PlaybackControlView.RepeatToggleModes int repeatToggleModes) {
+ Assertions.checkState(controller != null);
+ controller.setRepeatToggleModes(repeatToggleModes);
+ }
+
/**
* Sets whether the time bar should show all windows, as opposed to just the current one.
*
diff --git a/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_repeat_all.xml b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_repeat_all.xml
new file mode 100644
index 0000000000..dad37fa1f0
--- /dev/null
+++ b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_repeat_all.xml
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_repeat_off.xml b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_repeat_off.xml
new file mode 100644
index 0000000000..132eae0d76
--- /dev/null
+++ b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_repeat_off.xml
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_repeat_one.xml b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_repeat_one.xml
new file mode 100644
index 0000000000..d51010566a
--- /dev/null
+++ b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_repeat_one.xml
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/library/ui/src/main/res/drawable-hdpi/exo_controls_repeat_all.png b/library/ui/src/main/res/drawable-hdpi/exo_controls_repeat_all.png
new file mode 100644
index 0000000000..2824e7847c
Binary files /dev/null and b/library/ui/src/main/res/drawable-hdpi/exo_controls_repeat_all.png differ
diff --git a/library/ui/src/main/res/drawable-hdpi/exo_controls_repeat_off.png b/library/ui/src/main/res/drawable-hdpi/exo_controls_repeat_off.png
new file mode 100644
index 0000000000..0b92f583da
Binary files /dev/null and b/library/ui/src/main/res/drawable-hdpi/exo_controls_repeat_off.png differ
diff --git a/library/ui/src/main/res/drawable-hdpi/exo_controls_repeat_one.png b/library/ui/src/main/res/drawable-hdpi/exo_controls_repeat_one.png
new file mode 100644
index 0000000000..232aa2b1cd
Binary files /dev/null and b/library/ui/src/main/res/drawable-hdpi/exo_controls_repeat_one.png differ
diff --git a/library/ui/src/main/res/drawable-ldpi/exo_controls_repeat_all.png b/library/ui/src/main/res/drawable-ldpi/exo_controls_repeat_all.png
new file mode 100644
index 0000000000..5c91a47519
Binary files /dev/null and b/library/ui/src/main/res/drawable-ldpi/exo_controls_repeat_all.png differ
diff --git a/library/ui/src/main/res/drawable-ldpi/exo_controls_repeat_off.png b/library/ui/src/main/res/drawable-ldpi/exo_controls_repeat_off.png
new file mode 100644
index 0000000000..a94abd864f
Binary files /dev/null and b/library/ui/src/main/res/drawable-ldpi/exo_controls_repeat_off.png differ
diff --git a/library/ui/src/main/res/drawable-ldpi/exo_controls_repeat_one.png b/library/ui/src/main/res/drawable-ldpi/exo_controls_repeat_one.png
new file mode 100644
index 0000000000..a59a985239
Binary files /dev/null and b/library/ui/src/main/res/drawable-ldpi/exo_controls_repeat_one.png differ
diff --git a/library/ui/src/main/res/drawable-mdpi/exo_controls_repeat_all.png b/library/ui/src/main/res/drawable-mdpi/exo_controls_repeat_all.png
new file mode 100644
index 0000000000..97f7e1cc75
Binary files /dev/null and b/library/ui/src/main/res/drawable-mdpi/exo_controls_repeat_all.png differ
diff --git a/library/ui/src/main/res/drawable-mdpi/exo_controls_repeat_off.png b/library/ui/src/main/res/drawable-mdpi/exo_controls_repeat_off.png
new file mode 100644
index 0000000000..6a02321702
Binary files /dev/null and b/library/ui/src/main/res/drawable-mdpi/exo_controls_repeat_off.png differ
diff --git a/library/ui/src/main/res/drawable-mdpi/exo_controls_repeat_one.png b/library/ui/src/main/res/drawable-mdpi/exo_controls_repeat_one.png
new file mode 100644
index 0000000000..59bac33705
Binary files /dev/null and b/library/ui/src/main/res/drawable-mdpi/exo_controls_repeat_one.png differ
diff --git a/library/ui/src/main/res/drawable-xhdpi/exo_controls_repeat_all.png b/library/ui/src/main/res/drawable-xhdpi/exo_controls_repeat_all.png
new file mode 100644
index 0000000000..2baaedecbf
Binary files /dev/null and b/library/ui/src/main/res/drawable-xhdpi/exo_controls_repeat_all.png differ
diff --git a/library/ui/src/main/res/drawable-xhdpi/exo_controls_repeat_off.png b/library/ui/src/main/res/drawable-xhdpi/exo_controls_repeat_off.png
new file mode 100644
index 0000000000..2468f92f9f
Binary files /dev/null and b/library/ui/src/main/res/drawable-xhdpi/exo_controls_repeat_off.png differ
diff --git a/library/ui/src/main/res/drawable-xhdpi/exo_controls_repeat_one.png b/library/ui/src/main/res/drawable-xhdpi/exo_controls_repeat_one.png
new file mode 100644
index 0000000000..4e1d53db77
Binary files /dev/null and b/library/ui/src/main/res/drawable-xhdpi/exo_controls_repeat_one.png differ
diff --git a/library/ui/src/main/res/drawable-xxhdpi/exo_controls_repeat_all.png b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_repeat_all.png
new file mode 100644
index 0000000000..d7207ebc0d
Binary files /dev/null and b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_repeat_all.png differ
diff --git a/library/ui/src/main/res/drawable-xxhdpi/exo_controls_repeat_off.png b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_repeat_off.png
new file mode 100644
index 0000000000..4d6253ead6
Binary files /dev/null and b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_repeat_off.png differ
diff --git a/library/ui/src/main/res/drawable-xxhdpi/exo_controls_repeat_one.png b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_repeat_one.png
new file mode 100644
index 0000000000..d577f4ebcd
Binary files /dev/null and b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_repeat_one.png differ
diff --git a/library/ui/src/main/res/layout/exo_playback_control_view.xml b/library/ui/src/main/res/layout/exo_playback_control_view.xml
index 1d6267e7f0..407329890d 100644
--- a/library/ui/src/main/res/layout/exo_playback_control_view.xml
+++ b/library/ui/src/main/res/layout/exo_playback_control_view.xml
@@ -34,6 +34,9 @@
+
+
diff --git a/library/ui/src/main/res/values/attrs.xml b/library/ui/src/main/res/values/attrs.xml
index d8340c21cd..2cb28709b6 100644
--- a/library/ui/src/main/res/values/attrs.xml
+++ b/library/ui/src/main/res/values/attrs.xml
@@ -34,6 +34,11 @@
+
+
+
+
+
@@ -57,6 +62,7 @@
+
diff --git a/library/ui/src/main/res/values/ids.xml b/library/ui/src/main/res/values/ids.xml
index 61db83825e..815487a54e 100644
--- a/library/ui/src/main/res/values/ids.xml
+++ b/library/ui/src/main/res/values/ids.xml
@@ -27,6 +27,7 @@
+