From 674e92e1ee87c8951b8c829ed7748a1e7fad3a8a Mon Sep 17 00:00:00 2001 From: bachinger Date: Fri, 11 Oct 2019 12:36:49 +0100 Subject: [PATCH] provide content description for the player view to make show/hide controls accessible PiperOrigin-RevId: 274148026 --- RELEASENOTES.md | 1 + .../exoplayer2/ui/PlayerControlView.java | 26 +++++++---- .../android/exoplayer2/ui/PlayerView.java | 44 +++++++++++++++++-- library/ui/src/main/res/values/strings.xml | 4 ++ 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c414a5684d..c8d1b6fd4c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -110,6 +110,7 @@ * Add `MediaPeriod.isLoading` to improve `Player.isLoading` state. * Add support for ID3-in-EMSG in HLS streams ([spec](https://aomediacodec.github.io/av1-id3/)). +* Make show and hide player controls accessible for TalkBack in `PlayerView`. ### 2.10.5 (2019-09-20) ### diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java index 3b6711766e..b8642e2e42 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerControlView.java @@ -43,6 +43,7 @@ import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.Formatter; import java.util.Locale; +import java.util.concurrent.CopyOnWriteArrayList; /** * A view for controlling {@link Player} instances. @@ -231,6 +232,7 @@ public class PlayerControlView extends FrameLayout { private static final int MAX_UPDATE_INTERVAL_MS = 1000; private final ComponentListener componentListener; + private final CopyOnWriteArrayList visibilityListeners; private final View previousButton; private final View nextButton; private final View playButton; @@ -265,7 +267,6 @@ public class PlayerControlView extends FrameLayout { @Nullable private Player player; private com.google.android.exoplayer2.ControlDispatcher controlDispatcher; - @Nullable private VisibilityListener visibilityListener; @Nullable private ProgressUpdateListener progressUpdateListener; @Nullable private PlaybackPreparer playbackPreparer; @@ -335,6 +336,7 @@ public class PlayerControlView extends FrameLayout { a.recycle(); } } + visibilityListeners = new CopyOnWriteArrayList<>(); period = new Timeline.Period(); window = new Timeline.Window(); formatBuilder = new StringBuilder(); @@ -510,13 +512,21 @@ public class PlayerControlView extends FrameLayout { } /** - * Sets the {@link VisibilityListener}. + * Adds a {@link VisibilityListener}. * - * @param listener The listener to be notified about visibility changes, or null to remove the - * current listener. + * @param listener The listener to be notified about visibility changes. */ - public void setVisibilityListener(@Nullable VisibilityListener listener) { - this.visibilityListener = listener; + public void addVisibilityListener(VisibilityListener listener) { + visibilityListeners.add(listener); + } + + /** + * Removes a {@link VisibilityListener}. + * + * @param listener The listener to be removed. + */ + public void removeVisibilityListener(VisibilityListener listener) { + visibilityListeners.remove(listener); } /** @@ -697,7 +707,7 @@ public class PlayerControlView extends FrameLayout { public void show() { if (!isVisible()) { setVisibility(VISIBLE); - if (visibilityListener != null) { + for (VisibilityListener visibilityListener : visibilityListeners) { visibilityListener.onVisibilityChange(getVisibility()); } updateAll(); @@ -711,7 +721,7 @@ public class PlayerControlView extends FrameLayout { public void hide() { if (isVisible()) { setVisibility(GONE); - if (visibilityListener != null) { + for (VisibilityListener visibilityListener : visibilityListeners) { visibilityListener.onVisibilityChange(getVisibility()); } removeCallbacks(updateProgressAction); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index 20775ceb8f..3c6f6e1d9a 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -294,6 +294,7 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider private Player player; private boolean useController; + @Nullable private PlayerControlView.VisibilityListener controllerVisibilityListener; private boolean useArtwork; @Nullable private Drawable defaultArtwork; private @ShowBuffering int showBuffering; @@ -483,6 +484,10 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider this.controllerHideDuringAds = controllerHideDuringAds; this.useController = useController && controller != null; hideController(); + updateContentDescription(); + if (controller != null) { + controller.addVisibilityListener(/* listener= */ componentListener); + } } /** @@ -687,8 +692,9 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider controller.setPlayer(player); } else if (controller != null) { controller.hide(); - controller.setPlayer(null); + controller.setPlayer(/* player= */ null); } + updateContentDescription(); } /** @@ -880,6 +886,7 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider public void setControllerHideOnTouch(boolean controllerHideOnTouch) { Assertions.checkState(controller != null); this.controllerHideOnTouch = controllerHideOnTouch; + updateContentDescription(); } /** @@ -921,7 +928,16 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider public void setControllerVisibilityListener( @Nullable PlayerControlView.VisibilityListener listener) { Assertions.checkState(controller != null); - controller.setVisibilityListener(listener); + if (this.controllerVisibilityListener == listener) { + return; + } + if (this.controllerVisibilityListener != null) { + controller.removeVisibilityListener(this.controllerVisibilityListener); + } + this.controllerVisibilityListener = listener; + if (listener != null) { + controller.addVisibilityListener(listener); + } } /** @@ -1359,6 +1375,20 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider } } + private void updateContentDescription() { + if (controller == null || !useController) { + setContentDescription(/* contentDescription= */ null); + } else if (controller.getVisibility() == View.VISIBLE) { + setContentDescription( + /* contentDescription= */ controllerHideOnTouch + ? getResources().getString(R.string.exo_controls_hide) + : null); + } else { + setContentDescription( + /* contentDescription= */ getResources().getString(R.string.exo_controls_show)); + } + } + @TargetApi(23) private static void configureEditModeLogoV23(Resources resources, ImageView logo) { logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null)); @@ -1418,7 +1448,8 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider TextOutput, VideoListener, OnLayoutChangeListener, - SingleTapListener { + SingleTapListener, + PlayerControlView.VisibilityListener { // TextOutput implementation @@ -1513,5 +1544,12 @@ public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider public boolean onSingleTapUp(MotionEvent e) { return toggleControllerVisibility(); } + + // PlayerControlView.VisibilityListener implementation + + @Override + public void onVisibilityChange(int visibility) { + updateContentDescription(); + } } } diff --git a/library/ui/src/main/res/values/strings.xml b/library/ui/src/main/res/values/strings.xml index f0282b4991..74c5e1f3f6 100644 --- a/library/ui/src/main/res/values/strings.xml +++ b/library/ui/src/main/res/values/strings.xml @@ -14,6 +14,10 @@ limitations under the License. --> + + Show player controls + + Hide player controls Previous track