diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
index 13c5d14df0..3823f1760e 100644
--- a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
+++ b/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java
@@ -16,6 +16,8 @@
package com.google.android.exoplayer2.ui;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -52,7 +54,7 @@ public class PlaybackControlView extends FrameLayout {
public static final int DEFAULT_FAST_FORWARD_MS = 15000;
public static final int DEFAULT_REWIND_MS = 5000;
- public static final int DEFAULT_SHOW_DURATION_MS = 5000;
+ public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000;
private static final int PROGRESS_BAR_MAX = 1000;
private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000;
@@ -74,9 +76,10 @@ public class PlaybackControlView extends FrameLayout {
private VisibilityListener visibilityListener;
private boolean dragging;
- private int rewindMs = DEFAULT_REWIND_MS;
- private int fastForwardMs = DEFAULT_FAST_FORWARD_MS;
- private int showDurationMs = DEFAULT_SHOW_DURATION_MS;
+ private int rewindMs;
+ private int fastForwardMs;
+ private int showTimeoutMs;
+ private long hideAtMs;
private final Runnable updateProgressAction = new Runnable() {
@Override
@@ -103,6 +106,22 @@ public class PlaybackControlView extends FrameLayout {
public PlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ rewindMs = DEFAULT_REWIND_MS;
+ fastForwardMs = DEFAULT_FAST_FORWARD_MS;
+ showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
+ if (attrs != null) {
+ TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
+ R.styleable.PlaybackControlView, 0, 0);
+ try {
+ rewindMs = a.getInt(R.styleable.PlaybackControlView_rewind_increment, rewindMs);
+ fastForwardMs = a.getInt(R.styleable.PlaybackControlView_fastforward_increment,
+ fastForwardMs);
+ showTimeoutMs = a.getInt(R.styleable.PlaybackControlView_show_timeout, showTimeoutMs);
+ } finally {
+ a.recycle();
+ }
+ }
+
currentWindow = new Timeline.Window();
formatBuilder = new StringBuilder();
formatter = new Formatter(formatBuilder, Locale.getDefault());
@@ -124,7 +143,6 @@ public class PlaybackControlView extends FrameLayout {
rewindButton.setOnClickListener(componentListener);
fastForwardButton = findViewById(R.id.ffwd);
fastForwardButton.setOnClickListener(componentListener);
- updateAll();
}
/**
@@ -169,6 +187,7 @@ public class PlaybackControlView extends FrameLayout {
*/
public void setRewindIncrementMs(int rewindMs) {
this.rewindMs = rewindMs;
+ updateNavigation();
}
/**
@@ -178,51 +197,60 @@ public class PlaybackControlView extends FrameLayout {
*/
public void setFastForwardIncrementMs(int fastForwardMs) {
this.fastForwardMs = fastForwardMs;
+ updateNavigation();
}
/**
- * Sets the duration to show the playback control in milliseconds.
+ * Returns the playback controls timeout. The playback controls are automatically hidden after
+ * this duration of time has elapsed without user input.
*
- * @param showDurationMs The duration in milliseconds.
+ * @return The duration in milliseconds. A non-positive value indicates that the controls will
+ * remain visible indefinitely.
*/
- public void setShowDurationMs(int showDurationMs) {
- this.showDurationMs = showDurationMs;
+ public int getShowTimeoutMs() {
+ return showTimeoutMs;
}
/**
- * Shows the controller for the duration last passed to {@link #setShowDurationMs(int)}, or for
- * {@link #DEFAULT_SHOW_DURATION_MS} if {@link #setShowDurationMs(int)} has not been called.
+ * Sets the playback controls timeout. The playback controls are automatically hidden after this
+ * duration of time has elapsed without user input.
+ *
+ * @param showTimeoutMs The duration in milliseconds. A non-positive value will cause the controls
+ * to remain visible indefinitely.
+ */
+ public void setShowTimeoutMs(int showTimeoutMs) {
+ this.showTimeoutMs = showTimeoutMs;
+ }
+
+ /**
+ * 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.
*/
public void show() {
- show(showDurationMs);
- }
-
- /**
- * Shows the controller for the {@code durationMs}. If {@code durationMs} is 0 the controller is
- * shown until {@link #hide()} is called.
- *
- * @param durationMs The duration in milliseconds.
- */
- public void show(int durationMs) {
- setVisibility(VISIBLE);
- if (visibilityListener != null) {
- visibilityListener.onVisibilityChange(getVisibility());
+ if (!isVisible()) {
+ setVisibility(VISIBLE);
+ if (visibilityListener != null) {
+ visibilityListener.onVisibilityChange(getVisibility());
+ }
+ updateAll();
}
- updateAll();
- showDurationMs = durationMs;
- hideDeferred();
+ // Call hideAfterTimeout even if already visible to reset the timeout.
+ hideAfterTimeout();
}
/**
* Hides the controller.
*/
public void hide() {
- setVisibility(GONE);
- if (visibilityListener != null) {
- visibilityListener.onVisibilityChange(getVisibility());
+ if (isVisible()) {
+ setVisibility(GONE);
+ if (visibilityListener != null) {
+ visibilityListener.onVisibilityChange(getVisibility());
+ }
+ removeCallbacks(updateProgressAction);
+ removeCallbacks(hideAction);
+ hideAtMs = C.TIME_UNSET;
}
- removeCallbacks(updateProgressAction);
- removeCallbacks(hideAction);
}
/**
@@ -232,10 +260,15 @@ public class PlaybackControlView extends FrameLayout {
return getVisibility() == VISIBLE;
}
- private void hideDeferred() {
+ private void hideAfterTimeout() {
removeCallbacks(hideAction);
- if (showDurationMs > 0) {
- postDelayed(hideAction, showDurationMs);
+ if (showTimeoutMs > 0) {
+ hideAtMs = SystemClock.uptimeMillis() + showTimeoutMs;
+ if (isAttachedToWindow()) {
+ postDelayed(hideAction, showTimeoutMs);
+ }
+ } else {
+ hideAtMs = C.TIME_UNSET;
}
}
@@ -246,7 +279,7 @@ public class PlaybackControlView extends FrameLayout {
}
private void updatePlayPauseButton() {
- if (!isVisible()) {
+ if (!isVisible() || !isAttachedToWindow()) {
return;
}
boolean playing = player != null && player.getPlayWhenReady();
@@ -258,7 +291,7 @@ public class PlaybackControlView extends FrameLayout {
}
private void updateNavigation() {
- if (!isVisible()) {
+ if (!isVisible() || !isAttachedToWindow()) {
return;
}
Timeline currentTimeline = player != null ? player.getCurrentTimeline() : null;
@@ -276,13 +309,13 @@ public class PlaybackControlView extends FrameLayout {
}
setButtonEnabled(enablePrevious , previousButton);
setButtonEnabled(enableNext, nextButton);
- setButtonEnabled(isSeekable, fastForwardButton);
- setButtonEnabled(isSeekable, rewindButton);
+ setButtonEnabled(fastForwardMs > 0 && isSeekable, fastForwardButton);
+ setButtonEnabled(rewindMs > 0 && isSeekable, rewindButton);
progressBar.setEnabled(isSeekable);
}
private void updateProgress() {
- if (!isVisible()) {
+ if (!isVisible() || !isAttachedToWindow()) {
return;
}
long duration = player == null ? 0 : player.getDuration();
@@ -377,13 +410,40 @@ public class PlaybackControlView extends FrameLayout {
}
private void rewind() {
+ if (rewindMs <= 0) {
+ return;
+ }
player.seekTo(Math.max(player.getCurrentPosition() - rewindMs, 0));
}
private void fastForward() {
+ if (fastForwardMs <= 0) {
+ return;
+ }
player.seekTo(Math.min(player.getCurrentPosition() + fastForwardMs, player.getDuration()));
}
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (hideAtMs != C.TIME_UNSET) {
+ long delayMs = hideAtMs - SystemClock.uptimeMillis();
+ if (delayMs <= 0) {
+ hide();
+ } else {
+ postDelayed(hideAction, delayMs);
+ }
+ }
+ updateAll();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ removeCallbacks(updateProgressAction);
+ removeCallbacks(hideAction);
+ }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (player == null || event.getAction() != KeyEvent.ACTION_DOWN) {
@@ -440,7 +500,7 @@ public class PlaybackControlView extends FrameLayout {
public void onStopTrackingTouch(SeekBar seekBar) {
dragging = false;
player.seekTo(positionValue(seekBar.getProgress()));
- hideDeferred();
+ hideAfterTimeout();
}
@Override
@@ -485,7 +545,7 @@ public class PlaybackControlView extends FrameLayout {
} else if (playButton == view) {
player.setPlayWhenReady(!player.getPlayWhenReady());
}
- hideDeferred();
+ hideAfterTimeout();
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java
index cd0acb77fa..51955ccef3 100644
--- a/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java
+++ b/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java
@@ -48,8 +48,10 @@ public final class SimpleExoPlayerView extends FrameLayout {
private final AspectRatioFrameLayout layout;
private final PlaybackControlView controller;
private final ComponentListener componentListener;
+
private SimpleExoPlayer player;
private boolean useController = true;
+ private int controllerShowTimeoutMs;
public SimpleExoPlayerView(Context context) {
this(context, null);
@@ -64,6 +66,9 @@ public final class SimpleExoPlayerView extends FrameLayout {
boolean useTextureView = false;
int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
+ int rewindMs = PlaybackControlView.DEFAULT_REWIND_MS;
+ int fastForwardMs = PlaybackControlView.DEFAULT_FAST_FORWARD_MS;
+ int controllerShowTimeoutMs = PlaybackControlView.DEFAULT_SHOW_TIMEOUT_MS;
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SimpleExoPlayerView, 0, 0);
@@ -73,6 +78,11 @@ public final class SimpleExoPlayerView extends FrameLayout {
useTextureView);
resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode,
AspectRatioFrameLayout.RESIZE_MODE_FIT);
+ rewindMs = a.getInt(R.styleable.SimpleExoPlayerView_rewind_increment, rewindMs);
+ fastForwardMs = a.getInt(R.styleable.SimpleExoPlayerView_fastforward_increment,
+ fastForwardMs);
+ controllerShowTimeoutMs = a.getInt(R.styleable.SimpleExoPlayerView_show_timeout,
+ controllerShowTimeoutMs);
} finally {
a.recycle();
}
@@ -82,12 +92,17 @@ public final class SimpleExoPlayerView extends FrameLayout {
componentListener = new ComponentListener();
layout = (AspectRatioFrameLayout) findViewById(R.id.video_frame);
layout.setResizeMode(resizeMode);
- controller = (PlaybackControlView) findViewById(R.id.control);
shutterView = findViewById(R.id.shutter);
subtitleLayout = (SubtitleView) findViewById(R.id.subtitles);
subtitleLayout.setUserDefaultStyle();
subtitleLayout.setUserDefaultTextSize();
+ controller = (PlaybackControlView) findViewById(R.id.control);
+ controller.hide();
+ controller.setRewindIncrementMs(rewindMs);
+ controller.setFastForwardIncrementMs(fastForwardMs);
+ this.controllerShowTimeoutMs = controllerShowTimeoutMs;
+
View view = useTextureView ? new TextureView(context) : new SurfaceView(context);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -122,6 +137,9 @@ public final class SimpleExoPlayerView extends FrameLayout {
this.player.setVideoSurface(null);
}
this.player = player;
+ if (useController) {
+ controller.setPlayer(player);
+ }
if (player != null) {
if (surfaceView instanceof TextureView) {
player.setVideoTextureView((TextureView) surfaceView);
@@ -131,20 +149,36 @@ public final class SimpleExoPlayerView extends FrameLayout {
player.setVideoListener(componentListener);
player.addListener(componentListener);
player.setTextOutput(componentListener);
+ maybeShowController(false);
} else {
shutterView.setVisibility(VISIBLE);
- }
- if (useController) {
- controller.setPlayer(player);
+ controller.hide();
}
}
/**
- * Set the {@code useController} flag which indicates whether the playback control view should
- * be used or not. If set to {@code false} the controller is never visible and is disconnected
- * from the player.
+ * Sets the resize mode which can be of value {@link AspectRatioFrameLayout#RESIZE_MODE_FIT},
+ * {@link AspectRatioFrameLayout#RESIZE_MODE_FIXED_HEIGHT} or
+ * {@link AspectRatioFrameLayout#RESIZE_MODE_FIXED_WIDTH}.
*
- * @param useController If {@code false} the playback control is never used.
+ * @param resizeMode The resize mode.
+ */
+ public void setResizeMode(int resizeMode) {
+ layout.setResizeMode(resizeMode);
+ }
+
+ /**
+ * Returns whether the playback controls are enabled.
+ */
+ public boolean getUseController() {
+ return useController;
+ }
+
+ /**
+ * Sets whether playback controls are enabled. If set to {@code false} the playback controls are
+ * never visible and are disconnected from the player.
+ *
+ * @param useController Whether playback controls should be enabled.
*/
public void setUseController(boolean useController) {
if (this.useController == useController) {
@@ -160,14 +194,26 @@ public final class SimpleExoPlayerView extends FrameLayout {
}
/**
- * Sets the resize mode which can be of value {@link AspectRatioFrameLayout#RESIZE_MODE_FIT},
- * {@link AspectRatioFrameLayout#RESIZE_MODE_FIXED_HEIGHT} or
- * {@link AspectRatioFrameLayout#RESIZE_MODE_FIXED_WIDTH}.
+ * Returns the playback controls timeout. The playback controls are automatically hidden after
+ * this duration of time has elapsed without user input and with playback or buffering in
+ * progress.
*
- * @param resizeMode The resize mode.
+ * @return The timeout in milliseconds. A non-positive value will cause the controller to remain
+ * visible indefinitely.
*/
- public void setResizeMode(int resizeMode) {
- layout.setResizeMode(resizeMode);
+ public int getControllerShowTimeoutMs() {
+ return controllerShowTimeoutMs;
+ }
+
+ /**
+ * Sets the playback controls timeout. The playback controls are automatically hidden after this
+ * duration of time has elapsed without user input and with playback or buffering in progress.
+ *
+ * @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause
+ * the controller to remain visible indefinitely.
+ */
+ public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) {
+ this.controllerShowTimeoutMs = controllerShowTimeoutMs;
}
/**
@@ -197,15 +243,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
controller.setFastForwardIncrementMs(fastForwardMs);
}
- /**
- * Sets the duration to show the playback control in milliseconds.
- *
- * @param showDurationMs The duration in milliseconds.
- */
- public void setControlShowDurationMs(int showDurationMs) {
- controller.setShowDurationMs(showDurationMs);
- }
-
/**
* Get the view onto which video is rendered. This is either a {@link SurfaceView} (default)
* or a {@link TextureView} if the {@code use_texture_view} view attribute has been set to true.
@@ -218,21 +255,23 @@ public final class SimpleExoPlayerView extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (useController && ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
- if (controller.isVisible()) {
- controller.hide();
- } else {
- controller.show();
- }
+ if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ if (controller.isVisible()) {
+ controller.hide();
+ } else {
+ maybeShowController(true);
}
return true;
}
+
@Override
public boolean onTrackballEvent(MotionEvent ev) {
- if (!useController) {
+ if (!useController || player == null) {
return false;
}
- controller.show();
+ maybeShowController(true);
return true;
}
@@ -241,6 +280,20 @@ public final class SimpleExoPlayerView extends FrameLayout {
return useController ? controller.dispatchKeyEvent(event) : super.dispatchKeyEvent(event);
}
+ private void maybeShowController(boolean isForced) {
+ if (!useController || player == null) {
+ return;
+ }
+ int playbackState = player.getPlaybackState();
+ boolean showIndefinitely = playbackState == ExoPlayer.STATE_IDLE
+ || playbackState == ExoPlayer.STATE_ENDED || !player.getPlayWhenReady();
+ boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0;
+ controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs);
+ if (isForced || showIndefinitely || wasShowingIndefinitely) {
+ controller.show();
+ }
+ }
+
private final class ComponentListener implements SimpleExoPlayer.VideoListener,
TextRenderer.Output, ExoPlayer.EventListener {
@@ -278,9 +331,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
- if (useController && playbackState == ExoPlayer.STATE_ENDED) {
- controller.show(0);
- }
+ maybeShowController(false);
}
@Override
diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml
index 5f39dcb6c4..d58882c0aa 100644
--- a/library/src/main/res/values/attrs.xml
+++ b/library/src/main/res/values/attrs.xml
@@ -20,10 +20,16 @@
+
+
+
+
+
+
@@ -31,4 +37,10 @@
+
+
+
+
+
+