diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 383dfeb941..af497ee3e0 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -201,6 +201,10 @@
error state with the given error information
([#543](https://github.com/androidx/media/issues/543)).
* UI:
+ * Add customisation of various icons in `PlayerControlView` through xml
+ attributes to allow different drawables per `PlayerView` instance,
+ rather than global overrides
+ ([#1200](https://github.com/androidx/media/issues/1200)).
* Downloads:
* OkHttp Extension:
* Cronet Extension:
diff --git a/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java b/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java
index 3bcb03083b..de752071ee 100644
--- a/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java
+++ b/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java
@@ -64,7 +64,6 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;
-import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.content.res.ResourcesCompat;
@@ -165,10 +164,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
* PlayerControlView}, and will be propagated to the inflated {@link DefaultTimeBar}.
*
*
- *
Overriding drawables
+ * Overriding drawables globally
*
* The drawables used by {@code PlayerControlView} can be overridden by drawables with the same
- * names defined in your application. The drawables that can be overridden are:
+ * names defined in your application. Note that these icons will be the same across all usages of
+ * {@code PlayerView}/{@code PlayerControlView} in your app. The drawables that can be overridden
+ * are:
*
*
* - {@code exo_styled_controls_play} - The play icon.
@@ -187,6 +188,86 @@ import java.util.concurrent.CopyOnWriteArrayList;
* disabled.
*
- {@code exo_styled_controls_shuffle_on} - The shuffle icon when shuffling is enabled.
*
- {@code exo_styled_controls_vr} - The VR icon.
+ *
- {@code exo_styled_controls_fullscreen_enter} - The fullscreen icon for when the
+ * player is minimized.
+ *
- {@code exo_styled_controls_fullscreen_exit} - The fullscreen icon for when the
+ * player is in fullscreen mode.
+ *
+ *
+ * Overriding drawables locally
+ *
+ * If you want to customize drawable per PlayerView instance, you can use the following attributes:
+ * {@code PlayerView}/{@code PlayerControlView} in your app. The drawables that can be overridden
+ * are:
+ *
+ *
+ * - {@code play_icon} - The drawable resource ID for the play/pause button when play is
+ * shown.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_play}
+ *
+ * - {@code pause_icon} - The drawable resource ID for the play/pause button when pause
+ * is shown.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_pause}
+ *
+ * - {@code previous_icon} - The drawable resource ID for the previous button.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_previous}
+ *
+ * - {@code next_icon} - The drawable resource ID for the next button.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_next}
+ *
+ * - {@code repeat_off_icon} - The drawable resource ID for the repeat button when the
+ * mode is {@code none}.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_repeat_off}
+ *
+ * - {@code repeat_one_icon} - The drawable resource ID for the repeat button when the
+ * mode is {@code one}.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_repeat_one}
+ *
+ * - {@code repeat_all_icon} - The drawable resource ID for the repeat button when the
+ * mode is {@code all}.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_repeat_all}
+ *
+ * - {@code shuffle_on_icon} - The drawable resource ID for the repeat button when the
+ * mode is {@code one}.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_shuffle_on}
+ *
+ * - {@code shuffle_off_icon} - The drawable resource ID for the repeat button when the
+ * mode is {@code all}.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_shuffle_off}
+ *
+ * - {@code subtitle_on_icon} - The drawable resource ID for the subtitle button when the
+ * text track is on.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_subtitle_on}
+ *
+ * - {@code subtitle_off_icon} - The drawable resource ID for the subtitle button when
+ * the text track is off.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_subtitle_off}
+ *
+ * - {@code vr_icon} - The drawable resource ID for the VR button.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_vr}
+ *
+ * - {@code fullscreen_enter_icon} - The drawable resource ID for the fullscreen button
+ * when the player is minimized.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_fullscreen_enter}
+ *
+ * - {@code fullscreen_exit_icon} - The drawable resource ID for the fullscreen button
+ * when the player is in fullscreen mode.
+ *
+ * - Default: {@code @drawable/exo_styled_controls_fullscreen_exit}
+ *
*
*/
@UnstableApi
@@ -282,16 +363,16 @@ public class PlayerControlView extends FrameLayout {
private final PopupWindow settingsWindow;
private final int settingsWindowMargin;
- @Nullable private final View previousButton;
- @Nullable private final View nextButton;
- @Nullable private final View playPauseButton;
+ @Nullable private final ImageView previousButton;
+ @Nullable private final ImageView nextButton;
+ @Nullable private final ImageView playPauseButton;
@Nullable private final View fastForwardButton;
@Nullable private final View rewindButton;
@Nullable private final TextView fastForwardButtonTextView;
@Nullable private final TextView rewindButtonTextView;
@Nullable private final ImageView repeatToggleButton;
@Nullable private final ImageView shuffleButton;
- @Nullable private final View vrButton;
+ @Nullable private final ImageView vrButton;
@Nullable private final ImageView subtitleButton;
@Nullable private final ImageView fullScreenButton;
@Nullable private final ImageView minimalFullScreenButton;
@@ -307,6 +388,8 @@ public class PlayerControlView extends FrameLayout {
private final Timeline.Window window;
private final Runnable updateProgressAction;
+ private final Drawable playButtonDrawable;
+ private final Drawable pauseButtonDrawable;
private final Drawable repeatOffButtonDrawable;
private final Drawable repeatOneButtonDrawable;
private final Drawable repeatAllButtonDrawable;
@@ -380,6 +463,21 @@ public class PlayerControlView extends FrameLayout {
@Nullable AttributeSet playbackAttrs) {
super(context, attrs, defStyleAttr);
int controllerLayoutId = R.layout.exo_player_control_view;
+ int playDrawableResId = R.drawable.exo_styled_controls_play;
+ int pauseDrawableResId = R.drawable.exo_styled_controls_pause;
+ int nextDrawableResId = R.drawable.exo_styled_controls_next;
+ int previousDrawableResId = R.drawable.exo_styled_controls_previous;
+ int fullScreenExitDrawableResId = R.drawable.exo_styled_controls_fullscreen_exit;
+ int fullScreenEnterDrawableResId = R.drawable.exo_styled_controls_fullscreen_enter;
+ int repeatOffDrawableResId = R.drawable.exo_styled_controls_repeat_off;
+ int repeatOneDrawableResId = R.drawable.exo_styled_controls_repeat_one;
+ int repeatAllDrawableResId = R.drawable.exo_styled_controls_repeat_all;
+ int shuffleOnDrawableResId = R.drawable.exo_styled_controls_shuffle_on;
+ int shuffleOffDrawableResId = R.drawable.exo_styled_controls_shuffle_off;
+ int subtitleOnDrawableResId = R.drawable.exo_styled_controls_subtitle_on;
+ int subtitleOffDrawableResId = R.drawable.exo_styled_controls_subtitle_off;
+ int vrDrawableResId = R.drawable.exo_styled_controls_vr;
+
showPlayButtonIfSuppressed = true;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
@@ -402,6 +500,38 @@ public class PlayerControlView extends FrameLayout {
try {
controllerLayoutId =
a.getResourceId(R.styleable.PlayerControlView_controller_layout_id, controllerLayoutId);
+ playDrawableResId =
+ a.getResourceId(R.styleable.PlayerControlView_play_icon, playDrawableResId);
+ pauseDrawableResId =
+ a.getResourceId(R.styleable.PlayerControlView_pause_icon, pauseDrawableResId);
+ nextDrawableResId =
+ a.getResourceId(R.styleable.PlayerControlView_next_icon, nextDrawableResId);
+ previousDrawableResId =
+ a.getResourceId(R.styleable.PlayerControlView_previous_icon, previousDrawableResId);
+ fullScreenExitDrawableResId =
+ a.getResourceId(
+ R.styleable.PlayerControlView_fullscreen_exit_icon, fullScreenExitDrawableResId);
+ fullScreenEnterDrawableResId =
+ a.getResourceId(
+ R.styleable.PlayerControlView_fullscreen_enter_icon, fullScreenEnterDrawableResId);
+ repeatOffDrawableResId =
+ a.getResourceId(R.styleable.PlayerControlView_repeat_off_icon, repeatOffDrawableResId);
+ repeatOneDrawableResId =
+ a.getResourceId(R.styleable.PlayerControlView_repeat_one_icon, repeatOneDrawableResId);
+ repeatAllDrawableResId =
+ a.getResourceId(R.styleable.PlayerControlView_repeat_all_icon, repeatAllDrawableResId);
+ shuffleOnDrawableResId =
+ a.getResourceId(R.styleable.PlayerControlView_shuffle_on_icon, shuffleOnDrawableResId);
+ shuffleOffDrawableResId =
+ a.getResourceId(
+ R.styleable.PlayerControlView_shuffle_off_icon, shuffleOffDrawableResId);
+ subtitleOnDrawableResId =
+ a.getResourceId(
+ R.styleable.PlayerControlView_subtitle_on_icon, subtitleOnDrawableResId);
+ subtitleOffDrawableResId =
+ a.getResourceId(
+ R.styleable.PlayerControlView_subtitle_off_icon, subtitleOffDrawableResId);
+ vrDrawableResId = a.getResourceId(R.styleable.PlayerControlView_vr_icon, vrDrawableResId);
showTimeoutMs = a.getInt(R.styleable.PlayerControlView_show_timeout, showTimeoutMs);
repeatToggleModes = getRepeatToggleModes(a, repeatToggleModes);
showRewindButton =
@@ -501,10 +631,13 @@ public class PlayerControlView extends FrameLayout {
}
previousButton = findViewById(R.id.exo_prev);
if (previousButton != null) {
+ previousButton.setImageDrawable(
+ getDrawable(context, context.getResources(), previousDrawableResId));
previousButton.setOnClickListener(componentListener);
}
nextButton = findViewById(R.id.exo_next);
if (nextButton != null) {
+ nextButton.setImageDrawable(getDrawable(context, context.getResources(), nextDrawableResId));
nextButton.setOnClickListener(componentListener);
}
Typeface typeface = ResourcesCompat.getFont(context, R.font.roboto_medium_numbers);
@@ -543,6 +676,7 @@ public class PlayerControlView extends FrameLayout {
vrButton = findViewById(R.id.exo_vr);
if (vrButton != null) {
+ vrButton.setImageDrawable(getDrawable(context, resources, vrDrawableResId));
updateButton(/* enabled= */ false, vrButton);
}
@@ -578,10 +712,8 @@ public class PlayerControlView extends FrameLayout {
needToHideBars = true;
trackNameProvider = new DefaultTrackNameProvider(getResources());
- subtitleOnButtonDrawable =
- getDrawable(context, resources, R.drawable.exo_styled_controls_subtitle_on);
- subtitleOffButtonDrawable =
- getDrawable(context, resources, R.drawable.exo_styled_controls_subtitle_off);
+ subtitleOnButtonDrawable = getDrawable(context, resources, subtitleOnDrawableResId);
+ subtitleOffButtonDrawable = getDrawable(context, resources, subtitleOffDrawableResId);
subtitleOnContentDescription =
resources.getString(R.string.exo_controls_cc_enabled_description);
subtitleOffContentDescription =
@@ -592,20 +724,15 @@ public class PlayerControlView extends FrameLayout {
new PlaybackSpeedAdapter(
resources.getStringArray(R.array.exo_controls_playback_speeds), PLAYBACK_SPEEDS);
- fullScreenExitDrawable =
- getDrawable(context, resources, R.drawable.exo_styled_controls_fullscreen_exit);
- fullScreenEnterDrawable =
- getDrawable(context, resources, R.drawable.exo_styled_controls_fullscreen_enter);
- repeatOffButtonDrawable =
- getDrawable(context, resources, R.drawable.exo_styled_controls_repeat_off);
- repeatOneButtonDrawable =
- getDrawable(context, resources, R.drawable.exo_styled_controls_repeat_one);
- repeatAllButtonDrawable =
- getDrawable(context, resources, R.drawable.exo_styled_controls_repeat_all);
- shuffleOnButtonDrawable =
- getDrawable(context, resources, R.drawable.exo_styled_controls_shuffle_on);
- shuffleOffButtonDrawable =
- getDrawable(context, resources, R.drawable.exo_styled_controls_shuffle_off);
+ playButtonDrawable = getDrawable(context, resources, playDrawableResId);
+ pauseButtonDrawable = getDrawable(context, resources, pauseDrawableResId);
+ fullScreenExitDrawable = getDrawable(context, resources, fullScreenExitDrawableResId);
+ fullScreenEnterDrawable = getDrawable(context, resources, fullScreenEnterDrawableResId);
+ repeatOffButtonDrawable = getDrawable(context, resources, repeatOffDrawableResId);
+ repeatOneButtonDrawable = getDrawable(context, resources, repeatOneDrawableResId);
+ repeatAllButtonDrawable = getDrawable(context, resources, repeatAllDrawableResId);
+ shuffleOnButtonDrawable = getDrawable(context, resources, shuffleOnDrawableResId);
+ shuffleOffButtonDrawable = getDrawable(context, resources, shuffleOffDrawableResId);
fullScreenExitContentDescription =
resources.getString(R.string.exo_controls_fullscreen_exit_description);
fullScreenEnterContentDescription =
@@ -1000,18 +1127,13 @@ public class PlayerControlView extends FrameLayout {
}
if (playPauseButton != null) {
boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed);
- @DrawableRes
- int drawableRes =
- shouldShowPlayButton
- ? R.drawable.exo_styled_controls_play
- : R.drawable.exo_styled_controls_pause;
+ Drawable drawable = shouldShowPlayButton ? playButtonDrawable : pauseButtonDrawable;
@StringRes
int stringRes =
shouldShowPlayButton
? R.string.exo_controls_play_description
: R.string.exo_controls_pause_description;
- ((ImageView) playPauseButton)
- .setImageDrawable(getDrawable(getContext(), resources, drawableRes));
+ ((ImageView) playPauseButton).setImageDrawable(drawable);
playPauseButton.setContentDescription(resources.getString(stringRes));
boolean enablePlayPause = shouldEnablePlayPauseButton();
diff --git a/libraries/ui/src/main/res/values/attrs.xml b/libraries/ui/src/main/res/values/attrs.xml
index 01e64e3c07..120cf43b71 100644
--- a/libraries/ui/src/main/res/values/attrs.xml
+++ b/libraries/ui/src/main/res/values/attrs.xml
@@ -62,14 +62,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -114,12 +128,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -171,16 +199,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+