Player gestures: separate logic and UI

This commit is contained in:
vkay94 2020-10-21 16:40:22 +02:00
parent 3871d5aed7
commit 2683043762
3 changed files with 569 additions and 488 deletions

View file

@ -0,0 +1,434 @@
package org.schabi.newpipe.player.event
import android.content.Context
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import org.schabi.newpipe.player.BasePlayer
import org.schabi.newpipe.player.MainPlayer
import org.schabi.newpipe.player.VideoPlayerImpl
import org.schabi.newpipe.player.helper.PlayerHelper
import org.schabi.newpipe.util.AnimationUtils
import kotlin.math.abs
import kotlin.math.hypot
import kotlin.math.max
/**
* Base gesture handling for [VideoPlayerImpl]
*
* This class contains the logic for the player gestures like View preparations
* and provides some abstract methods to make it easier separating the logic from the UI.
*/
abstract class BasePlayerGestureListener(
@JvmField
protected val playerImpl: VideoPlayerImpl,
@JvmField
protected val service: MainPlayer
) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener {
// ///////////////////////////////////////////////////////////////////
// Abstract methods for VIDEO and POPUP
// ///////////////////////////////////////////////////////////////////
abstract fun onDoubleTap(event: MotionEvent, portion: DisplayPortion)
abstract fun onSingleTap(playerType: MainPlayer.PlayerType)
abstract fun onScroll(
playerType: MainPlayer.PlayerType,
portion: DisplayPortion,
initialEvent: MotionEvent,
movingEvent: MotionEvent,
distanceX: Float,
distanceY: Float
)
abstract fun onScrollEnd(playerType: MainPlayer.PlayerType, event: MotionEvent)
// ///////////////////////////////////////////////////////////////////
// Abstract methods for POPUP (exclusive)
// ///////////////////////////////////////////////////////////////////
abstract fun onPopupResizingStart()
abstract fun onPopupResizingEnd()
private var initialPopupX: Int = -1
private var initialPopupY: Int = -1
private var isMovingInMain = false
private var isMovingInPopup = false
private var isResizing = false
private val tossFlingVelocity = PlayerHelper.getTossFlingVelocity(service)
// [popup] initial coordinates and distance between fingers
private var initPointerDistance = -1.0
private var initFirstPointerX = -1f
private var initFirstPointerY = -1f
private var initSecPointerX = -1f
private var initSecPointerY = -1f
// ///////////////////////////////////////////////////////////////////
// onTouch implementation
// ///////////////////////////////////////////////////////////////////
override fun onTouch(v: View, event: MotionEvent): Boolean {
return if (playerImpl.popupPlayerSelected()) {
onTouchInPopup(v, event)
} else {
onTouchInMain(v, event)
}
}
private fun onTouchInMain(v: View, event: MotionEvent): Boolean {
playerImpl.gestureDetector.onTouchEvent(event)
if (event.action == MotionEvent.ACTION_UP && isMovingInMain) {
isMovingInMain = false
onScrollEnd(MainPlayer.PlayerType.VIDEO, event)
}
return when (event.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
v.parent.requestDisallowInterceptTouchEvent(playerImpl.isFullscreen)
true
}
MotionEvent.ACTION_UP -> {
v.parent.requestDisallowInterceptTouchEvent(false)
false
}
else -> true
}
}
private fun onTouchInPopup(v: View, event: MotionEvent): Boolean {
if (playerImpl == null) {
return false
}
playerImpl.gestureDetector.onTouchEvent(event)
if (event.pointerCount == 2 && !isMovingInPopup && !isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.")
}
onPopupResizingStart()
// record coordinates of fingers
initFirstPointerX = event.getX(0)
initFirstPointerY = event.getY(0)
initSecPointerX = event.getX(1)
initSecPointerY = event.getY(1)
// record distance between fingers
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX.toDouble(),
initFirstPointerY - initSecPointerY.toDouble())
isResizing = true
}
if (event.action == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_MOVE > v = [$v], e1.getRaw = [${event.rawX}" +
", ${event.rawY}]")
}
return handleMultiDrag(event)
}
if (event.action == MotionEvent.ACTION_UP) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_UP > v = [$v], e1.getRaw = [${event.rawX}" +
", ${event.rawY}]")
}
if (isMovingInPopup) {
isMovingInPopup = false
onScrollEnd(MainPlayer.PlayerType.POPUP, event)
}
if (isResizing) {
isResizing = false
initPointerDistance = (-1).toDouble()
initFirstPointerX = (-1).toFloat()
initFirstPointerY = (-1).toFloat()
initSecPointerX = (-1).toFloat()
initSecPointerY = (-1).toFloat()
onPopupResizingEnd()
playerImpl.changeState(playerImpl.currentState)
}
if (!playerImpl.isPopupClosing) {
playerImpl.savePositionAndSize()
}
}
v.performClick()
return true
}
private fun handleMultiDrag(event: MotionEvent): Boolean {
if (initPointerDistance != -1.0 && event.pointerCount == 2) {
// get the movements of the fingers
val firstPointerMove = hypot(event.getX(0) - initFirstPointerX.toDouble(),
event.getY(0) - initFirstPointerY.toDouble())
val secPointerMove = hypot(event.getX(1) - initSecPointerX.toDouble(),
event.getY(1) - initSecPointerY.toDouble())
// minimum threshold beyond which pinch gesture will work
val minimumMove = ViewConfiguration.get(service).scaledTouchSlop
if (max(firstPointerMove, secPointerMove) > minimumMove) {
// calculate current distance between the pointers
val currentPointerDistance = hypot(event.getX(0) - event.getX(1).toDouble(),
event.getY(0) - event.getY(1).toDouble())
val popupWidth = playerImpl.popupWidth.toDouble()
// change co-ordinates of popup so the center stays at the same position
val newWidth = popupWidth * currentPointerDistance / initPointerDistance
initPointerDistance = currentPointerDistance
playerImpl.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt()
playerImpl.checkPopupPositionBounds()
playerImpl.updateScreenSize()
playerImpl.updatePopupSize(
Math.min(playerImpl.screenWidth.toDouble(), newWidth).toInt(),
-1)
return true
}
}
return false
}
// ///////////////////////////////////////////////////////////////////
// Simple gestures
// ///////////////////////////////////////////////////////////////////
override fun onDown(e: MotionEvent): Boolean {
if (DEBUG)
Log.d(TAG, "onDown called with e = [$e]")
return if (playerImpl.popupPlayerSelected())
onDownInPopup(e)
else
true
}
private fun onDownInPopup(e: MotionEvent): Boolean {
// Fix popup position when the user touch it, it may have the wrong one
// because the soft input is visible (the draggable area is currently resized).
playerImpl.updateScreenSize()
playerImpl.checkPopupPositionBounds()
initialPopupX = playerImpl.popupLayoutParams.x
initialPopupY = playerImpl.popupLayoutParams.y
playerImpl.popupWidth = playerImpl.popupLayoutParams.width.toFloat()
playerImpl.popupHeight = playerImpl.popupLayoutParams.height.toFloat()
return super.onDown(e)
}
override fun onDoubleTap(e: MotionEvent): Boolean {
if (DEBUG)
Log.d(TAG, "onDoubleTap called with e = [$e]")
onDoubleTap(e, getDisplayPortion(e))
return true
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
if (DEBUG)
Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]")
if (playerImpl.popupPlayerSelected()) {
if (playerImpl.player == null)
return false
onSingleTap(MainPlayer.PlayerType.POPUP)
return true
} else {
super.onSingleTapConfirmed(e)
if (playerImpl.currentState == BasePlayer.STATE_BLOCKED)
return true
onSingleTap(MainPlayer.PlayerType.VIDEO)
}
return true
}
override fun onLongPress(e: MotionEvent?) {
if (playerImpl.popupPlayerSelected()) {
playerImpl.updateScreenSize()
playerImpl.checkPopupPositionBounds()
playerImpl.updatePopupSize(playerImpl.screenWidth.toInt(), -1)
}
}
override fun onScroll(
initialEvent: MotionEvent,
movingEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
return if (playerImpl.popupPlayerSelected()) {
onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY)
} else {
onScrollInMain(initialEvent, movingEvent, distanceX, distanceY)
}
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
return if (playerImpl.popupPlayerSelected()) {
val absVelocityX = abs(velocityX)
val absVelocityY = abs(velocityY)
if (absVelocityX.coerceAtLeast(absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) {
playerImpl.popupLayoutParams.x = velocityX.toInt()
}
if (absVelocityY > tossFlingVelocity) {
playerImpl.popupLayoutParams.y = velocityY.toInt()
}
playerImpl.checkPopupPositionBounds()
playerImpl.windowManager
.updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)
return true
}
return false
} else {
true
}
}
private fun onScrollInMain(
initialEvent: MotionEvent,
movingEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (!playerImpl.isFullscreen) {
return false
}
val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service)
val isTouchingNavigationBar: Boolean = (initialEvent.y
> playerImpl.rootView.height - getNavigationBarHeight(service))
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false
}
val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD
if (!isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) ||
playerImpl.currentState == BasePlayer.STATE_COMPLETED) {
return false
}
isMovingInMain = true
onScroll(MainPlayer.PlayerType.VIDEO, getDisplayHalfPortion(initialEvent),
initialEvent, movingEvent, distanceX, distanceY)
return true
}
private fun onScrollInPopup(
initialEvent: MotionEvent,
movingEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (isResizing) {
return super.onScroll(initialEvent, movingEvent, distanceX, distanceY)
}
if (!isMovingInPopup) {
AnimationUtils.animateView(playerImpl.closeOverlayButton, true, 200)
}
isMovingInPopup = true
val diffX: Float = (movingEvent.rawX - initialEvent.rawX)
var posX: Float = (initialPopupX + diffX)
val diffY: Float = (movingEvent.rawY - initialEvent.rawY)
var posY: Float = (initialPopupY + diffY)
if (posX > playerImpl.screenWidth - playerImpl.popupWidth) {
posX = (playerImpl.screenWidth - playerImpl.popupWidth)
} else if (posX < 0) {
posX = 0f
}
if (posY > playerImpl.screenHeight - playerImpl.popupHeight) {
posY = (playerImpl.screenHeight - playerImpl.popupHeight)
} else if (posY < 0) {
posY = 0f
}
playerImpl.popupLayoutParams.x = posX.toInt()
playerImpl.popupLayoutParams.y = posY.toInt()
onScroll(MainPlayer.PlayerType.POPUP, getDisplayHalfPortion(initialEvent),
initialEvent, movingEvent, distanceX, distanceY)
playerImpl.windowManager
.updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)
return true
}
// ///////////////////////////////////////////////////////////////////
// Utils
// ///////////////////////////////////////////////////////////////////
private fun getDisplayPortion(e: MotionEvent): DisplayPortion {
return if (playerImpl.playerType == MainPlayer.PlayerType.POPUP) {
when {
e.x < playerImpl.popupWidth / 3.0 -> DisplayPortion.LEFT
e.x > playerImpl.popupWidth * 2.0 / 3.0 -> DisplayPortion.RIGHT
else -> DisplayPortion.MIDDLE
}
} else /* MainPlayer.PlayerType.VIDEO */ {
when {
e.x < playerImpl.rootView.width / 3.0 -> DisplayPortion.LEFT
e.x > playerImpl.rootView.width * 2.0 / 3.0 -> DisplayPortion.RIGHT
else -> DisplayPortion.MIDDLE
}
}
}
// Currently needed for scrolling since there is no action more the middle portion
private fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion {
return if (playerImpl.playerType == MainPlayer.PlayerType.POPUP) {
when {
e.x < playerImpl.popupWidth / 2.0 -> DisplayPortion.LEFT_HALF
else -> DisplayPortion.RIGHT_HALF
}
} else /* MainPlayer.PlayerType.VIDEO */ {
when {
e.x < playerImpl.rootView.width / 2.0 -> DisplayPortion.LEFT_HALF
else -> DisplayPortion.RIGHT_HALF
}
}
}
private fun getNavigationBarHeight(context: Context): Int {
val resId = context.resources
.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resId > 0) {
context.resources.getDimensionPixelSize(resId)
} else 0
}
private fun getStatusBarHeight(context: Context): Int {
val resId = context.resources
.getIdentifier("status_bar_height", "dimen", "android")
return if (resId > 0) {
context.resources.getDimensionPixelSize(resId)
} else 0
}
companion object {
private const val TAG = "BasePlayerGestListener"
private val DEBUG = BasePlayer.DEBUG
private const val MOVEMENT_THRESHOLD = 40
}
}

View file

@ -0,0 +1,5 @@
package org.schabi.newpipe.player.event
enum class DisplayPortion {
LEFT, MIDDLE, RIGHT, LEFT_HALF, RIGHT_HALF
}

View file

@ -1,16 +1,16 @@
package org.schabi.newpipe.player.event; package org.schabi.newpipe.player.event;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.util.Log; import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import org.jetbrains.annotations.NotNull;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.player.BasePlayer; import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.MainPlayer;
@ -23,217 +23,114 @@ import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA; import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA;
import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.AnimationUtils.animateView;
/**
* GestureListener for the player
*
* While {@link BasePlayerGestureListener} contains the logic behind the single gestures
* this class focuses on the visual aspect like hiding and showing the controls or changing
* volume/brightness during scrolling for specific events.
*/
public class PlayerGestureListener public class PlayerGestureListener
extends GestureDetector.SimpleOnGestureListener extends BasePlayerGestureListener
implements View.OnTouchListener { implements View.OnTouchListener {
private static final String TAG = ".PlayerGestureListener"; private static final String TAG = ".PlayerGestureListener";
private static final boolean DEBUG = BasePlayer.DEBUG; private static final boolean DEBUG = BasePlayer.DEBUG;
private final VideoPlayerImpl playerImpl;
private final MainPlayer service;
private int initialPopupX;
private int initialPopupY;
private boolean isMovingInMain;
private boolean isMovingInPopup;
private boolean isResizing;
private final int tossFlingVelocity;
private final boolean isVolumeGestureEnabled; private final boolean isVolumeGestureEnabled;
private final boolean isBrightnessGestureEnabled; private final boolean isBrightnessGestureEnabled;
private final int maxVolume; private final int maxVolume;
private static final int MOVEMENT_THRESHOLD = 40;
// [popup] initial coordinates and distance between fingers
private double initPointerDistance = -1;
private float initFirstPointerX = -1;
private float initFirstPointerY = -1;
private float initSecPointerX = -1;
private float initSecPointerY = -1;
public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) { public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) {
this.playerImpl = playerImpl; super(playerImpl, service);
this.service = service;
this.tossFlingVelocity = PlayerHelper.getTossFlingVelocity(service);
isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service); isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service); isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service);
maxVolume = playerImpl.getAudioReactor().getMaxVolume(); maxVolume = playerImpl.getAudioReactor().getMaxVolume();
} }
/*//////////////////////////////////////////////////////////////////////////
// Helpers
//////////////////////////////////////////////////////////////////////////*/
/*
* Main and popup players' gesture listeners is too different.
* So it will be better to have different implementations of them
* */
@Override @Override
public boolean onDoubleTap(final MotionEvent e) { public void onDoubleTap(@NotNull final MotionEvent event,
@NotNull final DisplayPortion portion) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " Log.d(TAG, "onDoubleTap called with playerType = ["
+ e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); + playerImpl.getPlayerType() + "], portion = ["
+ portion + "]");
}
if (playerImpl.isSomePopupMenuVisible()) {
playerImpl.hideControls(0, 0);
} }
if (playerImpl.popupPlayerSelected()) { if (portion == DisplayPortion.LEFT) {
return onDoubleTapInPopup(e);
} else {
return onDoubleTapInMain(e);
}
}
@Override
public boolean onSingleTapConfirmed(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onSingleTapConfirmedInPopup(e);
} else {
return onSingleTapConfirmedInMain(e);
}
}
@Override
public boolean onDown(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onDown() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onDownInPopup(e);
} else {
return true;
}
}
@Override
public void onLongPress(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
onLongPressInPopup(e);
}
}
@Override
public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if (playerImpl.popupPlayerSelected()) {
return onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY);
} else {
return onScrollInMain(initialEvent, movingEvent, distanceX, distanceY);
}
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2,
final float velocityX, final float velocityY) {
if (DEBUG) {
Log.d(TAG, "onFling() called with velocity: dX=["
+ velocityX + "], dY=[" + velocityY + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onFlingInPopup(e1, e2, velocityX, velocityY);
} else {
return true;
}
}
@Override
public boolean onTouch(final View v, final MotionEvent event) {
/*if (DEBUG && false) {
Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
}*/
if (playerImpl.popupPlayerSelected()) {
return onTouchInPopup(v, event);
} else {
return onTouchInMain(v, event);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Main player listener
//////////////////////////////////////////////////////////////////////////*/
private boolean onDoubleTapInMain(final MotionEvent e) {
if (e.getX() > playerImpl.getRootView().getWidth() * 2.0 / 3.0) {
playerImpl.onFastForward();
} else if (e.getX() < playerImpl.getRootView().getWidth() / 3.0) {
playerImpl.onFastRewind(); playerImpl.onFastRewind();
} else { } else if (portion == DisplayPortion.RIGHT) {
playerImpl.getPlayPauseButton().performClick(); playerImpl.onFastForward();
} }
return true;
} }
@Override
private boolean onSingleTapConfirmedInMain(final MotionEvent e) { public void onSingleTap(@NotNull final MainPlayer.PlayerType playerType) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]"); Log.d(TAG, "onSingleTap called with playerType = ["
+ playerImpl.getPlayerType() + "]");
} }
if (playerType == MainPlayer.PlayerType.POPUP) {
if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) { if (playerImpl.isControlsVisible()) {
return true; playerImpl.hideControls(100, 100);
}
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(150, 0);
} else {
if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
playerImpl.showControls(0);
} else { } else {
playerImpl.getPlayPauseButton().requestFocus();
playerImpl.showControlsThenHide(); playerImpl.showControlsThenHide();
} }
} else /* playerType == MainPlayer.PlayerType.VIDEO */ {
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(150, 0);
} else {
if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
playerImpl.showControls(0);
} else {
playerImpl.showControlsThenHide();
}
}
} }
return true;
} }
private boolean onScrollInMain(final MotionEvent initialEvent, final MotionEvent movingEvent, @Override
final float distanceX, final float distanceY) { public void onScroll(@NotNull final MainPlayer.PlayerType playerType,
if ((!isVolumeGestureEnabled && !isBrightnessGestureEnabled) @NotNull final DisplayPortion portion,
|| !playerImpl.isFullscreen()) { @NotNull final MotionEvent initialEvent,
return false; @NotNull final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if (DEBUG) {
Log.d(TAG, "onScroll called with playerType = ["
+ playerImpl.getPlayerType() + "], portion = ["
+ portion + "]");
} }
if (playerType == MainPlayer.PlayerType.VIDEO) {
if (portion == DisplayPortion.LEFT_HALF) {
onScrollMainVolume(distanceX, distanceY);
final boolean isTouchingStatusBar = initialEvent.getY() < getStatusBarHeight(service); } else /* DisplayPortion.RIGHT_HALF */ {
final boolean isTouchingNavigationBar = initialEvent.getY() onScrollMainBrightness(distanceX, distanceY);
> playerImpl.getRootView().getHeight() - getNavigationBarHeight(service); }
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false; } else /* MainPlayer.PlayerType.POPUP */ {
final View closingOverlayView = playerImpl.getClosingOverlayView();
if (playerImpl.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
animateView(closingOverlayView, true, 250);
}
} else {
if (closingOverlayView.getVisibility() == View.VISIBLE) {
animateView(closingOverlayView, false, 0);
}
}
} }
}
/*if (DEBUG && false) Log.d(TAG, "onScrollInMain = " + private void onScrollMainVolume(final float distanceX, final float distanceY) {
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" + if (isVolumeGestureEnabled) {
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]");*/
final boolean insideThreshold =
Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
if (!isMovingInMain && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
|| playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
return false;
}
isMovingInMain = true;
final boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
final boolean acceptVolumeArea = acceptAnyArea
|| initialEvent.getX() > playerImpl.getRootView().getWidth() / 2.0;
if (isVolumeGestureEnabled && acceptVolumeArea) {
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY); playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) playerImpl final float currentProgressPercent = (float) playerImpl
.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength(); .getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
@ -258,10 +155,14 @@ public class PlayerGestureListener
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE); playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
} }
} else { }
}
private void onScrollMainBrightness(final float distanceX, final float distanceY) {
if (isBrightnessGestureEnabled) {
final Activity parent = playerImpl.getParentActivity(); final Activity parent = playerImpl.getParentActivity();
if (parent == null) { if (parent == null) {
return true; return;
} }
final Window window = parent.getWindow(); final Window window = parent.getWindow();
@ -299,330 +200,71 @@ public class PlayerGestureListener
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE); playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
} }
} }
return true;
} }
private void onScrollEndInMain() { @Override
public void onScrollEnd(@NotNull final MainPlayer.PlayerType playerType,
@NotNull final MotionEvent event) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onScrollEnd() called"); Log.d(TAG, "onScrollEnd called with playerType = ["
+ playerImpl.getPlayerType() + "]");
} }
if (playerType == MainPlayer.PlayerType.VIDEO) {
if (DEBUG) {
Log.d(TAG, "onScrollEnd() called");
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200); animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA,
} false, 200, 200);
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { }
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200); if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
} animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA,
false, 200, 200);
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) { if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
} else {
if (playerImpl == null) {
return;
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
if (playerImpl.isInsideClosingRadius(event)) {
playerImpl.closePopup();
} else {
animateView(playerImpl.getClosingOverlayView(), false, 0);
if (!playerImpl.isPopupClosing) {
animateView(playerImpl.getCloseOverlayButton(), false, 200);
}
}
} }
} }
private boolean onTouchInMain(final View v, final MotionEvent event) { @Override
playerImpl.getGestureDetector().onTouchEvent(event); public void onPopupResizingStart() {
if (event.getAction() == MotionEvent.ACTION_UP && isMovingInMain) { if (DEBUG) {
isMovingInMain = false; Log.d(TAG, "onPopupResizingStart called");
onScrollEndInMain();
}
// This hack allows to stop receiving touch events on appbar
// while touching video player's view
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
v.getParent().requestDisallowInterceptTouchEvent(playerImpl.isFullscreen());
return true;
case MotionEvent.ACTION_UP:
v.getParent().requestDisallowInterceptTouchEvent(false);
return false;
default:
return true;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Popup player listener
//////////////////////////////////////////////////////////////////////////*/
private boolean onDoubleTapInPopup(final MotionEvent e) {
if (playerImpl == null || !playerImpl.isPlaying()) {
return false;
} }
playerImpl.showAndAnimateControl(-1, true);
playerImpl.getLoadingPanel().setVisibility(View.GONE);
playerImpl.hideControls(0, 0); playerImpl.hideControls(0, 0);
animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
if (e.getX() > playerImpl.getPopupWidth() / 2) { animateView(playerImpl.getResizingIndicator(), true, 200, 0);
playerImpl.onFastForward();
} else {
playerImpl.onFastRewind();
}
return true;
} }
private boolean onSingleTapConfirmedInPopup(final MotionEvent e) { @Override
if (playerImpl == null || playerImpl.getPlayer() == null) { public void onPopupResizingEnd() {
return false; if (DEBUG) {
Log.d(TAG, "onPopupResizingEnd called");
} }
if (playerImpl.isControlsVisible()) { animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.hideControls(100, 100);
} else {
playerImpl.getPlayPauseButton().requestFocus();
playerImpl.showControlsThenHide();
}
return true;
}
private boolean onDownInPopup(final MotionEvent e) {
// Fix popup position when the user touch it, it may have the wrong one
// because the soft input is visible (the draggable area is currently resized).
playerImpl.updateScreenSize();
playerImpl.checkPopupPositionBounds();
initialPopupX = playerImpl.getPopupLayoutParams().x;
initialPopupY = playerImpl.getPopupLayoutParams().y;
playerImpl.setPopupWidth(playerImpl.getPopupLayoutParams().width);
playerImpl.setPopupHeight(playerImpl.getPopupLayoutParams().height);
return super.onDown(e);
}
private void onLongPressInPopup(final MotionEvent e) {
playerImpl.updateScreenSize();
playerImpl.checkPopupPositionBounds();
playerImpl.updatePopupSize((int) playerImpl.getScreenWidth(), -1);
}
private boolean onScrollInPopup(final MotionEvent initialEvent,
final MotionEvent movingEvent,
final float distanceX,
final float distanceY) {
if (isResizing || playerImpl == null) {
return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
}
if (!isMovingInPopup) {
animateView(playerImpl.getCloseOverlayButton(), true, 200);
}
isMovingInPopup = true;
final float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX());
float posX = (int) (initialPopupX + diffX);
final float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY());
float posY = (int) (initialPopupY + diffY);
if (posX > (playerImpl.getScreenWidth() - playerImpl.getPopupWidth())) {
posX = (int) (playerImpl.getScreenWidth() - playerImpl.getPopupWidth());
} else if (posX < 0) {
posX = 0;
}
if (posY > (playerImpl.getScreenHeight() - playerImpl.getPopupHeight())) {
posY = (int) (playerImpl.getScreenHeight() - playerImpl.getPopupHeight());
} else if (posY < 0) {
posY = 0;
}
playerImpl.getPopupLayoutParams().x = (int) posX;
playerImpl.getPopupLayoutParams().y = (int) posY;
final View closingOverlayView = playerImpl.getClosingOverlayView();
if (playerImpl.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
animateView(closingOverlayView, true, 250);
}
} else {
if (closingOverlayView.getVisibility() == View.VISIBLE) {
animateView(closingOverlayView, false, 0);
}
}
// if (DEBUG) {
// Log.d(TAG, "onScrollInPopup = "
// + "e1.getRaw = [" + initialEvent.getRawX() + ", "
// + initialEvent.getRawY() + "], "
// + "e1.getX,Y = [" + initialEvent.getX() + ", "
// + initialEvent.getY() + "], "
// + "e2.getRaw = [" + movingEvent.getRawX() + ", "
// + movingEvent.getRawY() + "], "
// + "e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "], "
// + "distanceX,Y = [" + distanceX + ", " + distanceY + "], "
// + "posX,Y = [" + posX + ", " + posY + "], "
// + "popupW,H = [" + popupWidth + " x " + popupHeight + "]");
// }
playerImpl.windowManager
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
return true;
}
private void onScrollEndInPopup(final MotionEvent event) {
if (playerImpl == null) {
return;
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
if (playerImpl.isInsideClosingRadius(event)) {
playerImpl.closePopup();
} else {
animateView(playerImpl.getClosingOverlayView(), false, 0);
if (!playerImpl.isPopupClosing) {
animateView(playerImpl.getCloseOverlayButton(), false, 200);
}
}
}
private boolean onFlingInPopup(final MotionEvent e1,
final MotionEvent e2,
final float velocityX,
final float velocityY) {
if (playerImpl == null) {
return false;
}
final float absVelocityX = Math.abs(velocityX);
final float absVelocityY = Math.abs(velocityY);
if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) {
playerImpl.getPopupLayoutParams().x = (int) velocityX;
}
if (absVelocityY > tossFlingVelocity) {
playerImpl.getPopupLayoutParams().y = (int) velocityY;
}
playerImpl.checkPopupPositionBounds();
playerImpl.windowManager
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
return true;
}
return false;
}
private boolean onTouchInPopup(final View v, final MotionEvent event) {
if (playerImpl == null) {
return false;
}
playerImpl.getGestureDetector().onTouchEvent(event);
if (event.getPointerCount() == 2 && !isMovingInPopup && !isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
}
playerImpl.showAndAnimateControl(-1, true);
playerImpl.getLoadingPanel().setVisibility(View.GONE);
playerImpl.hideControls(0, 0);
animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
animateView(playerImpl.getResizingIndicator(), true, 200, 0);
//record coordinates of fingers
initFirstPointerX = event.getX(0);
initFirstPointerY = event.getY(0);
initSecPointerX = event.getX(1);
initSecPointerY = event.getY(1);
//record distance between fingers
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX,
initFirstPointerY - initSecPointerY);
isResizing = true;
}
if (event.getAction() == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], "
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
}
return handleMultiDrag(event);
}
if (event.getAction() == MotionEvent.ACTION_UP) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], "
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
}
if (isMovingInPopup) {
isMovingInPopup = false;
onScrollEndInPopup(event);
}
if (isResizing) {
isResizing = false;
initPointerDistance = -1;
initFirstPointerX = -1;
initFirstPointerY = -1;
initSecPointerX = -1;
initSecPointerY = -1;
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.changeState(playerImpl.getCurrentState());
}
if (!playerImpl.isPopupClosing) {
playerImpl.savePositionAndSize();
}
}
v.performClick();
return true;
}
private boolean handleMultiDrag(final MotionEvent event) {
if (initPointerDistance != -1 && event.getPointerCount() == 2) {
// get the movements of the fingers
final double firstPointerMove = Math.hypot(event.getX(0) - initFirstPointerX,
event.getY(0) - initFirstPointerY);
final double secPointerMove = Math.hypot(event.getX(1) - initSecPointerX,
event.getY(1) - initSecPointerY);
// minimum threshold beyond which pinch gesture will work
final int minimumMove = ViewConfiguration.get(service).getScaledTouchSlop();
if (Math.max(firstPointerMove, secPointerMove) > minimumMove) {
// calculate current distance between the pointers
final double currentPointerDistance =
Math.hypot(event.getX(0) - event.getX(1),
event.getY(0) - event.getY(1));
final double popupWidth = playerImpl.getPopupWidth();
// change co-ordinates of popup so the center stays at the same position
final double newWidth = (popupWidth * currentPointerDistance / initPointerDistance);
initPointerDistance = currentPointerDistance;
playerImpl.getPopupLayoutParams().x += (popupWidth - newWidth) / 2;
playerImpl.checkPopupPositionBounds();
playerImpl.updateScreenSize();
playerImpl.updatePopupSize(
(int) Math.min(playerImpl.getScreenWidth(), newWidth),
-1);
return true;
}
}
return false;
}
/*
* Utils
* */
private int getNavigationBarHeight(final Context context) {
final int resId = context.getResources()
.getIdentifier("navigation_bar_height", "dimen", "android");
if (resId > 0) {
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
}
private int getStatusBarHeight(final Context context) {
final int resId = context.getResources()
.getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
} }
} }