Repeat mode UI

Added repeat mode toggle buttons to UI. Current mode gets forwarded to
Exoplayer instance, but without playback behaviour changes yet.
Translations for button descriptions are also missing - this will be another CL.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=155386549
This commit is contained in:
tonihei 2017-05-08 09:19:56 -07:00 committed by Oliver Woodman
parent df0d1b0f8a
commit c70cd37c5a
25 changed files with 272 additions and 2 deletions

View File

@ -470,6 +470,10 @@ import java.util.Locale;
switch (repeatMode) { switch (repeatMode) {
case ExoPlayer.REPEAT_MODE_OFF: case ExoPlayer.REPEAT_MODE_OFF:
return "OFF"; return "OFF";
case ExoPlayer.REPEAT_MODE_ONE:
return "ONE";
case ExoPlayer.REPEAT_MODE_ALL:
return "ALL";
default: default:
return "?"; return "?";
} }

View File

@ -268,12 +268,20 @@ public interface ExoPlayer {
* Repeat modes for playback. * Repeat modes for playback.
*/ */
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({REPEAT_MODE_OFF}) @IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL})
public @interface RepeatMode {} public @interface RepeatMode {}
/** /**
* Normal playback without repetition. * Normal playback without repetition.
*/ */
int REPEAT_MODE_OFF = 0; 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 * Register a listener to receive events from the player. The listener's methods will be called on

View File

@ -18,13 +18,17 @@ package com.google.android.exoplayer2.ui;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.IntDef;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; 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.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Formatter; import java.util.Formatter;
import java.util.Locale; import java.util.Locale;
@ -70,6 +76,14 @@ import java.util.Locale;
* <li>Default: {@link #DEFAULT_FAST_FORWARD_MS}</li> * <li>Default: {@link #DEFAULT_FAST_FORWARD_MS}</li>
* </ul> * </ul>
* </li> * </li>
* <li><b>{@code repeat_toggle_modes}</b> - 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}.
* <ul>
* <li>Corresponding method: {@link #setRepeatToggleModes(int)}</li>
* <li>Default: {@link #DEFAULT_REPEAT_TOGGLE_MODES}</li>
* </ul>
* </li>
* <li><b>{@code controller_layout_id}</b> - Specifies the id of the layout to be inflated. See * <li><b>{@code controller_layout_id}</b> - Specifies the id of the layout to be inflated. See
* below for more details. * below for more details.
* <ul> * <ul>
@ -117,6 +131,11 @@ import java.util.Locale;
* <li>Type: {@link View}</li> * <li>Type: {@link View}</li>
* </ul> * </ul>
* </li> * </li>
* <li><b>{@code exo_repeat_toggle}</b> - The repeat toggle button.
* <ul>
* <li>Type: {@link View}</li>
* </ul>
* </li>
* <li><b>{@code exo_position}</b> - Text view displaying the current playback position. * <li><b>{@code exo_position}</b> - Text view displaying the current playback position.
* <ul> * <ul>
* <li>Type: {@link TextView}</li> * <li>Type: {@link TextView}</li>
@ -189,6 +208,14 @@ public class PlaybackControlView extends FrameLayout {
*/ */
boolean dispatchSeekTo(ExoPlayer player, int windowIndex, long positionMs); 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; 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_FAST_FORWARD_MS = 15000;
public static final int DEFAULT_REWIND_MS = 5000; public static final int DEFAULT_REWIND_MS = 5000;
public static final int DEFAULT_SHOW_TIMEOUT_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. * 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 pauseButton;
private final View fastForwardButton; private final View fastForwardButton;
private final View rewindButton; private final View rewindButton;
private final ImageView repeatToggleButton;
private final TextView durationView; private final TextView durationView;
private final TextView positionView; private final TextView positionView;
private final TimeBar timeBar; private final TimeBar timeBar;
@ -237,6 +292,13 @@ public class PlaybackControlView extends FrameLayout {
private final Timeline.Period period; private final Timeline.Period period;
private final Timeline.Window window; 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 ExoPlayer player;
private ControlDispatcher controlDispatcher; private ControlDispatcher controlDispatcher;
private VisibilityListener visibilityListener; private VisibilityListener visibilityListener;
@ -248,6 +310,7 @@ public class PlaybackControlView extends FrameLayout {
private int rewindMs; private int rewindMs;
private int fastForwardMs; private int fastForwardMs;
private int showTimeoutMs; private int showTimeoutMs;
private @RepeatToggleModes int repeatToggleModes;
private long hideAtMs; private long hideAtMs;
private long[] adBreakTimesMs; private long[] adBreakTimesMs;
@ -280,6 +343,7 @@ public class PlaybackControlView extends FrameLayout {
rewindMs = DEFAULT_REWIND_MS; rewindMs = DEFAULT_REWIND_MS;
fastForwardMs = DEFAULT_FAST_FORWARD_MS; fastForwardMs = DEFAULT_FAST_FORWARD_MS;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS; showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
if (attrs != null) { if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.PlaybackControlView, 0, 0); R.styleable.PlaybackControlView, 0, 0);
@ -290,6 +354,7 @@ public class PlaybackControlView extends FrameLayout {
showTimeoutMs = a.getInt(R.styleable.PlaybackControlView_show_timeout, showTimeoutMs); showTimeoutMs = a.getInt(R.styleable.PlaybackControlView_show_timeout, showTimeoutMs);
controllerLayoutId = a.getResourceId(R.styleable.PlaybackControlView_controller_layout_id, controllerLayoutId = a.getResourceId(R.styleable.PlaybackControlView_controller_layout_id,
controllerLayoutId); controllerLayoutId);
repeatToggleModes = getRepeatToggleModes(a, repeatToggleModes);
} finally { } finally {
a.recycle(); a.recycle();
} }
@ -335,6 +400,26 @@ public class PlaybackControlView extends FrameLayout {
if (fastForwardButton != null) { if (fastForwardButton != null) {
fastForwardButton.setOnClickListener(componentListener); 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; 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 * 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. * 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() { private void updateAll() {
updatePlayPauseButton(); updatePlayPauseButton();
updateNavigation(); updateNavigation();
updateRepeatModeButton();
updateProgress(); 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() { private void updateTimeBarMode() {
if (player == null) { if (player == null) {
return; 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() { private void rewind() {
if (rewindMs <= 0) { if (rewindMs <= 0) {
return; return;
@ -908,7 +1074,8 @@ public class PlaybackControlView extends FrameLayout {
@Override @Override
public void onRepeatModeChanged(int repeatMode) { public void onRepeatModeChanged(int repeatMode) {
// Do nothing. updateRepeatModeButton();
updateNavigation();
} }
@Override @Override
@ -959,6 +1126,8 @@ public class PlaybackControlView extends FrameLayout {
controlDispatcher.dispatchSetPlayWhenReady(player, true); controlDispatcher.dispatchSetPlayWhenReady(player, true);
} else if (pauseButton == view) { } else if (pauseButton == view) {
controlDispatcher.dispatchSetPlayWhenReady(player, false); controlDispatcher.dispatchSetPlayWhenReady(player, false);
} else if (repeatToggleButton == view) {
controlDispatcher.dispatchSetRepeatMode(player, getNextRepeatMode());
} }
} }
hideAfterTimeout(); hideAfterTimeout();

View File

@ -591,6 +591,16 @@ public final class SimpleExoPlayerView extends FrameLayout {
controller.setFastForwardIncrementMs(fastForwardMs); 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. * Sets whether the time bar should show all windows, as opposed to just the current one.
* *

View File

@ -0,0 +1,23 @@
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
</vector>

View File

@ -0,0 +1,23 @@
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#4EFFFFFF"
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
</vector>

View File

@ -0,0 +1,23 @@
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="32dp"
android:width="32dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4zM13,15L13,9h-1l-2,1v1h1.5v4L13,15z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

View File

@ -34,6 +34,9 @@
<ImageButton android:id="@id/exo_rew" <ImageButton android:id="@id/exo_rew"
style="@style/ExoMediaButton.Rewind"/> style="@style/ExoMediaButton.Rewind"/>
<ImageButton android:id="@id/exo_repeat_toggle"
style="@style/ExoMediaButton"/>
<ImageButton android:id="@id/exo_play" <ImageButton android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"/> style="@style/ExoMediaButton.Play"/>

View File

@ -34,6 +34,11 @@
<attr name="fastforward_increment" format="integer"/> <attr name="fastforward_increment" format="integer"/>
<attr name="player_layout_id" format="reference"/> <attr name="player_layout_id" format="reference"/>
<attr name="controller_layout_id" format="reference"/> <attr name="controller_layout_id" format="reference"/>
<attr name="repeat_toggle_modes">
<flag name="none" value="0"/>
<flag name="one" value="1"/>
<flag name="all" value="2"/>
</attr>
<declare-styleable name="SimpleExoPlayerView"> <declare-styleable name="SimpleExoPlayerView">
<attr name="use_artwork" format="boolean"/> <attr name="use_artwork" format="boolean"/>
@ -57,6 +62,7 @@
<attr name="show_timeout"/> <attr name="show_timeout"/>
<attr name="rewind_increment"/> <attr name="rewind_increment"/>
<attr name="fastforward_increment"/> <attr name="fastforward_increment"/>
<attr name="repeat_toggle_modes"/>
<attr name="controller_layout_id"/> <attr name="controller_layout_id"/>
</declare-styleable> </declare-styleable>

View File

@ -27,6 +27,7 @@
<item name="exo_ffwd" type="id"/> <item name="exo_ffwd" type="id"/>
<item name="exo_prev" type="id"/> <item name="exo_prev" type="id"/>
<item name="exo_next" type="id"/> <item name="exo_next" type="id"/>
<item name="exo_repeat_toggle" type="id"/>
<item name="exo_duration" type="id"/> <item name="exo_duration" type="id"/>
<item name="exo_position" type="id"/> <item name="exo_position" type="id"/>
<item name="exo_progress" type="id"/> <item name="exo_progress" type="id"/>