Commit 5e1c96ad by olly Committed by kim-vde

Fix a couple of StyledPlayerControlView bugs

1. The first time the player controls are are made visible,
   there is no animation.
2. The first time the player controls are made visible, the
   "select tracks" button isn't displayed. When tapping to
   subsequently hide the player controls, the button briefly
   becomes visible and then is hidden again. This bug is due
   to state in StyledPlayerControlViewLayoutManager being
   out of sync, resulting in StyledPlayerControlView's
   onVisibilityChange not being called properly.

After this change both of these issues should be resolved.

PiperOrigin-RevId: 336704031
parent c21529de
......@@ -73,6 +73,8 @@
* Show overflow button in `StyledPlayerControlView` only when there is no
enough space.
* UI:
* Fix animation when `StyledPlayerView` first shows its playback controls.
### 2.12.0 (2020-09-11) ###
......
......@@ -459,6 +459,7 @@ public class StyledPlayerControlView extends FrameLayout {
@SuppressWarnings({
"nullness:argument.type.incompatible",
"nullness:assignment.type.incompatible",
"nullness:method.invocation.invalid",
"nullness:methodref.receiver.bound.invalid"
})
......@@ -526,8 +527,11 @@ public class StyledPlayerControlView extends FrameLayout {
a.recycle();
}
}
controlViewLayoutManager = new StyledPlayerControlViewLayoutManager();
controlViewLayoutManager.setAnimationEnabled(animationEnabled);
LayoutInflater.from(context).inflate(controllerLayoutId, /* root= */ this);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
componentListener = new ComponentListener();
visibilityListeners = new CopyOnWriteArrayList<>();
period = new Timeline.Period();
window = new Timeline.Window();
......@@ -537,13 +541,9 @@ public class StyledPlayerControlView extends FrameLayout {
playedAdGroups = new boolean[0];
extraAdGroupTimesMs = new long[0];
extraPlayedAdGroups = new boolean[0];
componentListener = new ComponentListener();
controlDispatcher = new DefaultControlDispatcher(fastForwardMs, rewindMs);
updateProgressAction = this::updateProgress;
LayoutInflater.from(context).inflate(controllerLayoutId, /* root= */ this);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
// Relating to Bottom Bar Left View
durationView = findViewById(R.id.exo_duration);
positionView = findViewById(R.id.exo_position);
......@@ -581,10 +581,10 @@ public class StyledPlayerControlView extends FrameLayout {
} else {
timeBar = null;
}
if (timeBar != null) {
timeBar.addListener(componentListener);
}
playPauseButton = findViewById(R.id.exo_play_pause);
if (playPauseButton != null) {
playPauseButton.setOnClickListener(componentListener);
......@@ -626,7 +626,6 @@ public class StyledPlayerControlView extends FrameLayout {
}
resources = context.getResources();
buttonAlphaEnabled =
(float) resources.getInteger(R.integer.exo_media_button_opacity_percentage_enabled) / 100;
buttonAlphaDisabled =
......@@ -634,10 +633,12 @@ public class StyledPlayerControlView extends FrameLayout {
vrButton = findViewById(R.id.exo_vr);
if (vrButton != null) {
setShowVrButton(showVrButton);
updateButton(/* enabled= */ false, vrButton);
}
controlViewLayoutManager = new StyledPlayerControlViewLayoutManager(this);
controlViewLayoutManager.setAnimationEnabled(animationEnabled);
// Related to Settings List View
String[] settingTexts = new String[2];
Drawable[] settingIcons = new Drawable[2];
......@@ -1071,6 +1072,11 @@ public class StyledPlayerControlView extends FrameLayout {
controlViewLayoutManager.hide();
}
/** Hides the controller without any animation. */
public void hideImmediately() {
controlViewLayoutManager.hideImmediately();
}
/** Returns whether the controller is fully visible, which means all UI controls are visible. */
public boolean isFullyVisible() {
return controlViewLayoutManager.isFullyVisible();
......@@ -1607,7 +1613,7 @@ public class StyledPlayerControlView extends FrameLayout {
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
controlViewLayoutManager.onViewAttached(this);
controlViewLayoutManager.onAttachedToWindow();
isAttachedToWindow = true;
if (isFullyVisible()) {
controlViewLayoutManager.resetHideCallbacks();
......@@ -1618,7 +1624,7 @@ public class StyledPlayerControlView extends FrameLayout {
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
controlViewLayoutManager.onViewDetached(this);
controlViewLayoutManager.onDetachedFromWindow();
isAttachedToWindow = false;
removeCallbacks(updateProgressAction);
controlViewLayoutManager.removeHideCallbacks();
......
......@@ -47,6 +47,26 @@ import java.util.List;
// Int for defining the UX state where the views are being animated to be shown.
private static final int UX_STATE_ANIMATING_SHOW = 4;
private final StyledPlayerControlView styledPlayerControlView;
@Nullable private final ViewGroup embeddedTransportControls;
@Nullable private final ViewGroup bottomBar;
@Nullable private final ViewGroup minimalControls;
@Nullable private final ViewGroup basicControls;
@Nullable private final ViewGroup extraControls;
@Nullable private final ViewGroup extraControlsScrollView;
@Nullable private final ViewGroup timeView;
@Nullable private final View timeBar;
@Nullable private final View overflowShowButton;
private final AnimatorSet hideMainBarsAnimator;
private final AnimatorSet hideProgressBarAnimator;
private final AnimatorSet hideAllBarsAnimator;
private final AnimatorSet showMainBarsAnimator;
private final AnimatorSet showAllBarsAnimator;
private final ValueAnimator overflowShowAnimator;
private final ValueAnimator overflowHideAnimator;
private final Runnable showAllBarsRunnable;
private final Runnable hideAllBarsRunnable;
private final Runnable hideProgressBarRunnable;
......@@ -57,32 +77,16 @@ import java.util.List;
private final List<View> shownButtons;
private int uxState;
private boolean initiallyHidden;
private boolean isMinimalMode;
private boolean needToShowBars;
private boolean animationEnabled;
@Nullable private StyledPlayerControlView styledPlayerControlView;
@Nullable private ViewGroup embeddedTransportControls;
@Nullable private ViewGroup bottomBar;
@Nullable private ViewGroup minimalControls;
@Nullable private ViewGroup basicControls;
@Nullable private ViewGroup extraControls;
@Nullable private ViewGroup extraControlsScrollView;
@Nullable private ViewGroup timeView;
@Nullable private View timeBar;
@Nullable private View overflowShowButton;
@Nullable private AnimatorSet hideMainBarsAnimator;
@Nullable private AnimatorSet hideProgressBarAnimator;
@Nullable private AnimatorSet hideAllBarsAnimator;
@Nullable private AnimatorSet showMainBarsAnimator;
@Nullable private AnimatorSet showAllBarsAnimator;
@Nullable private ValueAnimator overflowShowAnimator;
@Nullable private ValueAnimator overflowHideAnimator;
public StyledPlayerControlViewLayoutManager() {
@SuppressWarnings({
"nullness:method.invocation.invalid",
"nullness:methodref.receiver.bound.invalid"
})
public StyledPlayerControlViewLayoutManager(StyledPlayerControlView styledPlayerControlView) {
this.styledPlayerControlView = styledPlayerControlView;
showAllBarsRunnable = this::showAllBars;
hideAllBarsRunnable = this::hideAllBars;
hideProgressBarRunnable = this::hideProgressBar;
......@@ -92,112 +96,35 @@ import java.util.List;
animationEnabled = true;
uxState = UX_STATE_ALL_VISIBLE;
shownButtons = new ArrayList<>();
}
public void show() {
initiallyHidden = false;
if (this.styledPlayerControlView == null) {
return;
}
StyledPlayerControlView styledPlayerControlView = this.styledPlayerControlView;
if (!styledPlayerControlView.isVisible()) {
styledPlayerControlView.setVisibility(View.VISIBLE);
styledPlayerControlView.updateAll();
styledPlayerControlView.requestPlayPauseFocus();
}
styledPlayerControlView.post(showAllBarsRunnable);
}
public void hide() {
initiallyHidden = true;
if (styledPlayerControlView == null
|| uxState == UX_STATE_ANIMATING_HIDE
|| uxState == UX_STATE_NONE_VISIBLE) {
return;
}
removeHideCallbacks();
if (!animationEnabled) {
postDelayedRunnable(hideControllerRunnable, 0);
} else if (uxState == UX_STATE_ONLY_PROGRESS_VISIBLE) {
postDelayedRunnable(hideProgressBarRunnable, 0);
} else {
postDelayedRunnable(hideAllBarsRunnable, 0);
}
}
public void setAnimationEnabled(boolean animationEnabled) {
this.animationEnabled = animationEnabled;
}
public boolean isAnimationEnabled() {
return animationEnabled;
}
public void resetHideCallbacks() {
if (uxState == UX_STATE_ANIMATING_HIDE) {
return;
}
removeHideCallbacks();
int showTimeoutMs =
styledPlayerControlView != null ? styledPlayerControlView.getShowTimeoutMs() : 0;
if (showTimeoutMs > 0) {
if (!animationEnabled) {
postDelayedRunnable(hideControllerRunnable, showTimeoutMs);
} else if (uxState == UX_STATE_ONLY_PROGRESS_VISIBLE) {
postDelayedRunnable(hideProgressBarRunnable, ANIMATION_INTERVAL_MS);
} else {
postDelayedRunnable(hideMainBarsRunnable, showTimeoutMs);
}
}
}
public void removeHideCallbacks() {
if (styledPlayerControlView == null) {
return;
}
styledPlayerControlView.removeCallbacks(hideControllerRunnable);
styledPlayerControlView.removeCallbacks(hideAllBarsRunnable);
styledPlayerControlView.removeCallbacks(hideMainBarsRunnable);
styledPlayerControlView.removeCallbacks(hideProgressBarRunnable);
}
// TODO(insun): Pass StyledPlayerControlView to constructor and reduce multiple nullchecks.
public void onViewAttached(StyledPlayerControlView v) {
styledPlayerControlView = v;
v.setVisibility(initiallyHidden ? View.GONE : View.VISIBLE);
v.addOnLayoutChangeListener(onLayoutChangeListener);
// Relating to Center View
ViewGroup centerView = v.findViewById(R.id.exo_center_view);
embeddedTransportControls = v.findViewById(R.id.exo_embedded_transport_controls);
ViewGroup centerView = styledPlayerControlView.findViewById(R.id.exo_center_view);
embeddedTransportControls =
styledPlayerControlView.findViewById(R.id.exo_embedded_transport_controls);
// Relating to Minimal Layout
minimalControls = v.findViewById(R.id.exo_minimal_controls);
minimalControls = styledPlayerControlView.findViewById(R.id.exo_minimal_controls);
// Relating to Bottom Bar View
ViewGroup bottomBar = v.findViewById(R.id.exo_bottom_bar);
bottomBar = styledPlayerControlView.findViewById(R.id.exo_bottom_bar);
// Relating to Bottom Bar Left View
timeView = v.findViewById(R.id.exo_time);
View timeBar = v.findViewById(R.id.exo_progress);
timeView = styledPlayerControlView.findViewById(R.id.exo_time);
timeBar = styledPlayerControlView.findViewById(R.id.exo_progress);
// Relating to Bottom Bar Right View
basicControls = v.findViewById(R.id.exo_basic_controls);
extraControls = v.findViewById(R.id.exo_extra_controls);
extraControlsScrollView = v.findViewById(R.id.exo_extra_controls_scroll_view);
overflowShowButton = v.findViewById(R.id.exo_overflow_show);
View overflowHideButton = v.findViewById(R.id.exo_overflow_hide);
basicControls = styledPlayerControlView.findViewById(R.id.exo_basic_controls);
extraControls = styledPlayerControlView.findViewById(R.id.exo_extra_controls);
extraControlsScrollView =
styledPlayerControlView.findViewById(R.id.exo_extra_controls_scroll_view);
overflowShowButton = styledPlayerControlView.findViewById(R.id.exo_overflow_show);
View overflowHideButton = styledPlayerControlView.findViewById(R.id.exo_overflow_hide);
if (overflowShowButton != null && overflowHideButton != null) {
overflowShowButton.setOnClickListener(this::onOverflowButtonClick);
overflowHideButton.setOnClickListener(this::onOverflowButtonClick);
}
this.bottomBar = bottomBar;
this.timeBar = timeBar;
Resources resources = v.getResources();
Resources resources = styledPlayerControlView.getResources();
float progressBarHeight = resources.getDimension(R.dimen.exo_custom_progress_thumb_size);
float bottomBarHeight = resources.getDimension(R.dimen.exo_bottom_bar_height);
......@@ -206,7 +133,6 @@ import java.util.List;
fadeOutAnimator.addUpdateListener(
animation -> {
float animatedValue = (float) animation.getAnimatedValue();
if (centerView != null) {
centerView.setAlpha(animatedValue);
}
......@@ -239,7 +165,6 @@ import java.util.List;
fadeInAnimator.addUpdateListener(
animation -> {
float animatedValue = (float) animation.getAnimatedValue();
if (centerView != null) {
centerView.setAlpha(animatedValue);
}
......@@ -276,9 +201,7 @@ import java.util.List;
public void onAnimationEnd(Animator animation) {
setUxState(UX_STATE_ONLY_PROGRESS_VISIBLE);
if (needToShowBars) {
if (styledPlayerControlView != null) {
styledPlayerControlView.post(showAllBarsRunnable);
}
styledPlayerControlView.post(showAllBarsRunnable);
needToShowBars = false;
}
}
......@@ -301,9 +224,7 @@ import java.util.List;
public void onAnimationEnd(Animator animation) {
setUxState(UX_STATE_NONE_VISIBLE);
if (needToShowBars) {
if (styledPlayerControlView != null) {
styledPlayerControlView.post(showAllBarsRunnable);
}
styledPlayerControlView.post(showAllBarsRunnable);
needToShowBars = false;
}
}
......@@ -325,9 +246,7 @@ import java.util.List;
public void onAnimationEnd(Animator animation) {
setUxState(UX_STATE_NONE_VISIBLE);
if (needToShowBars) {
if (styledPlayerControlView != null) {
styledPlayerControlView.post(showAllBarsRunnable);
}
styledPlayerControlView.post(showAllBarsRunnable);
needToShowBars = false;
}
}
......@@ -420,14 +339,78 @@ import java.util.List;
});
}
public void onViewDetached(StyledPlayerControlView v) {
v.removeOnLayoutChangeListener(onLayoutChangeListener);
public void show() {
if (!styledPlayerControlView.isVisible()) {
styledPlayerControlView.setVisibility(View.VISIBLE);
styledPlayerControlView.updateAll();
styledPlayerControlView.requestPlayPauseFocus();
}
showAllBars();
}
public boolean isFullyVisible() {
if (styledPlayerControlView == null) {
return false;
public void hide() {
if (uxState == UX_STATE_ANIMATING_HIDE || uxState == UX_STATE_NONE_VISIBLE) {
return;
}
removeHideCallbacks();
if (!animationEnabled) {
hideController();
} else if (uxState == UX_STATE_ONLY_PROGRESS_VISIBLE) {
hideProgressBar();
} else {
hideAllBars();
}
}
public void hideImmediately() {
if (uxState == UX_STATE_ANIMATING_HIDE || uxState == UX_STATE_NONE_VISIBLE) {
return;
}
removeHideCallbacks();
hideController();
}
public void setAnimationEnabled(boolean animationEnabled) {
this.animationEnabled = animationEnabled;
}
public boolean isAnimationEnabled() {
return animationEnabled;
}
public void resetHideCallbacks() {
if (uxState == UX_STATE_ANIMATING_HIDE) {
return;
}
removeHideCallbacks();
int showTimeoutMs = styledPlayerControlView.getShowTimeoutMs();
if (showTimeoutMs > 0) {
if (!animationEnabled) {
postDelayedRunnable(hideControllerRunnable, showTimeoutMs);
} else if (uxState == UX_STATE_ONLY_PROGRESS_VISIBLE) {
postDelayedRunnable(hideProgressBarRunnable, ANIMATION_INTERVAL_MS);
} else {
postDelayedRunnable(hideMainBarsRunnable, showTimeoutMs);
}
}
}
public void removeHideCallbacks() {
styledPlayerControlView.removeCallbacks(hideControllerRunnable);
styledPlayerControlView.removeCallbacks(hideAllBarsRunnable);
styledPlayerControlView.removeCallbacks(hideMainBarsRunnable);
styledPlayerControlView.removeCallbacks(hideProgressBarRunnable);
}
public void onAttachedToWindow() {
styledPlayerControlView.addOnLayoutChangeListener(onLayoutChangeListener);
}
public void onDetachedFromWindow() {
styledPlayerControlView.removeOnLayoutChangeListener(onLayoutChangeListener);
}
public boolean isFullyVisible() {
return uxState == UX_STATE_ALL_VISIBLE && styledPlayerControlView.isVisible();
}
......@@ -455,18 +438,15 @@ import java.util.List;
private void setUxState(int uxState) {
int prevUxState = this.uxState;
this.uxState = uxState;
if (styledPlayerControlView != null) {
StyledPlayerControlView styledPlayerControlView = this.styledPlayerControlView;
if (uxState == UX_STATE_NONE_VISIBLE) {
styledPlayerControlView.setVisibility(View.GONE);
} else if (prevUxState == UX_STATE_NONE_VISIBLE) {
styledPlayerControlView.setVisibility(View.VISIBLE);
}
// TODO(insun): Notify specific uxState. Currently reuses legacy visibility listener for API
// compatibility.
if (prevUxState != uxState) {
styledPlayerControlView.notifyOnVisibilityChange();
}
if (uxState == UX_STATE_NONE_VISIBLE) {
styledPlayerControlView.setVisibility(View.GONE);
} else if (prevUxState == UX_STATE_NONE_VISIBLE) {
styledPlayerControlView.setVisibility(View.VISIBLE);
}
// TODO(insun): Notify specific uxState. Currently reuses legacy visibility listener for API
// compatibility.
if (prevUxState != uxState) {
styledPlayerControlView.notifyOnVisibilityChange();
}
}
......@@ -494,9 +474,9 @@ import java.util.List;
private void onOverflowButtonClick(View v) {
resetHideCallbacks();
if (v.getId() == R.id.exo_overflow_show && overflowShowAnimator != null) {
if (v.getId() == R.id.exo_overflow_show) {
overflowShowAnimator.start();
} else if (v.getId() == R.id.exo_overflow_hide && overflowHideAnimator != null) {
} else if (v.getId() == R.id.exo_overflow_hide) {
overflowHideAnimator.start();
}
}
......@@ -510,14 +490,10 @@ import java.util.List;
switch (uxState) {
case UX_STATE_NONE_VISIBLE:
if (showAllBarsAnimator != null) {
showAllBarsAnimator.start();
}
showAllBarsAnimator.start();
break;
case UX_STATE_ONLY_PROGRESS_VISIBLE:
if (showMainBarsAnimator != null) {
showMainBarsAnimator.start();
}
showMainBarsAnimator.start();
break;
case UX_STATE_ANIMATING_HIDE:
needToShowBars = true;
......@@ -531,23 +507,14 @@ import java.util.List;
}
private void hideAllBars() {
if (hideAllBarsAnimator == null) {
return;
}
hideAllBarsAnimator.start();
}
private void hideProgressBar() {
if (hideProgressBarAnimator == null) {
return;
}
hideProgressBarAnimator.start();
}
private void hideMainBars() {
if (hideMainBarsAnimator == null) {
return;
}
hideMainBarsAnimator.start();
postDelayedRunnable(hideProgressBarRunnable, ANIMATION_INTERVAL_MS);
}
......@@ -561,7 +528,7 @@ import java.util.List;
}
private void postDelayedRunnable(Runnable runnable, long interval) {
if (styledPlayerControlView != null && interval >= 0) {
if (interval >= 0) {
styledPlayerControlView.postDelayed(runnable, interval);
}
}
......@@ -582,19 +549,14 @@ import java.util.List;
}
private boolean shouldBeMinimalMode() {
if (this.styledPlayerControlView == null) {
return isMinimalMode;
}
ViewGroup playerControlView = this.styledPlayerControlView;
int width =
playerControlView.getWidth()
- playerControlView.getPaddingLeft()
- playerControlView.getPaddingRight();
styledPlayerControlView.getWidth()
- styledPlayerControlView.getPaddingLeft()
- styledPlayerControlView.getPaddingRight();
int height =
playerControlView.getHeight()
- playerControlView.getPaddingBottom()
- playerControlView.getPaddingTop();
styledPlayerControlView.getHeight()
- styledPlayerControlView.getPaddingBottom()
- styledPlayerControlView.getPaddingTop();
int defaultModeWidth =
Math.max(
getWidth(embeddedTransportControls), getWidth(timeView) + getWidth(overflowShowButton));
......@@ -605,16 +567,11 @@ import java.util.List;
}
private void updateLayoutForSizeChange() {
if (this.styledPlayerControlView == null) {
return;
}
StyledPlayerControlView playerControlView = this.styledPlayerControlView;
if (minimalControls != null) {
minimalControls.setVisibility(isMinimalMode ? View.VISIBLE : View.INVISIBLE);
}
View fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen);
View fullScreenButton = styledPlayerControlView.findViewById(R.id.exo_fullscreen);
if (fullScreenButton != null) {
ViewGroup parent = (ViewGroup) fullScreenButton.getParent();
parent.removeView(fullScreenButton);
......@@ -629,10 +586,9 @@ import java.util.List;
}
}
if (timeBar != null) {
View timeBar = this.timeBar;
MarginLayoutParams timeBarParams = (MarginLayoutParams) timeBar.getLayoutParams();
int timeBarMarginBottom =
playerControlView
styledPlayerControlView
.getResources()
.getDimensionPixelSize(R.dimen.exo_custom_progress_margin_bottom);
timeBarParams.bottomMargin = (isMinimalMode ? 0 : timeBarMarginBottom);
......@@ -668,15 +624,11 @@ import java.util.List;
if (basicControls == null || extraControls == null) {
return;
}
ViewGroup basicControls = this.basicControls;
ViewGroup extraControls = this.extraControls;
int width =
(styledPlayerControlView != null
? styledPlayerControlView.getWidth()
- styledPlayerControlView.getPaddingLeft()
- styledPlayerControlView.getPaddingRight()
: 0);
styledPlayerControlView.getWidth()
- styledPlayerControlView.getPaddingLeft()
- styledPlayerControlView.getPaddingRight();
int bottomBarWidth = getWidth(timeView);
for (int i = 0; i < basicControls.getChildCount(); ++i) {
bottomBarWidth += basicControls.getChildAt(i).getWidth();
......@@ -690,7 +642,7 @@ import java.util.List;
// Move control views from basicControls to extraControls
ArrayList<View> movingChildren = new ArrayList<>();
int movingWidth = 0;
// The last child is overflow show button which shouldn't move.
// The last child is overflow show button, which shouldn't move.
int endIndex = basicControls.getChildCount() - 1;
for (int index = 0; index < endIndex; index++) {
View child = basicControls.getChildAt(index);
......@@ -705,18 +657,18 @@ import java.util.List;
basicControls.removeViews(0, movingChildren.size());
for (View child : movingChildren) {
// The last child of extra controls should be overflow hide button.
// Adding other buttons before it.
// The last child of extra controls is the overflow hide button. Adding other buttons
// before it.
int index = extraControls.getChildCount() - 1;
extraControls.addView(child, index);
}
}
} else {
// move controls from extraControls to basicControls if possible, else do nothing
// Move controls from extraControls to basicControls if possible, else do nothing.
ArrayList<View> movingChildren = new ArrayList<>();
int movingWidth = 0;
// The last child of extra controls is overflow button and it should not move.
// The last child of extra controls is the overflow button, which shouldn't move.
int endIndex = extraControls.getChildCount() - 2;
for (int index = endIndex; index >= 0; index--) {
View child = extraControls.getChildAt(index);
......
......@@ -510,11 +510,11 @@ public class StyledPlayerView extends FrameLayout implements AdsLoader.AdViewPro
this.controllerAutoShow = controllerAutoShow;
this.controllerHideDuringAds = controllerHideDuringAds;
this.useController = useController && controller != null;
hideController();
updateContentDescription();
if (controller != null) {
controller.hideImmediately();
controller.addVisibilityListener(/* listener= */ componentListener);
}
updateContentDescription();
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment