mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 07:13:00 +00:00 
			
		
		
		
	Merge pull request #4587 from vkay94/separate-player-gesture-logic-ui
Separate player gesture logic and UI
This commit is contained in:
		| @@ -0,0 +1,503 @@ | ||||
| package org.schabi.newpipe.player.event | ||||
|  | ||||
| import android.content.Context | ||||
| import android.os.Handler | ||||
| 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]") | ||||
|  | ||||
|         if (isDoubleTapping && isDoubleTapEnabled) { | ||||
|             doubleTapControls?.onDoubleTapProgressDown(getDisplayPortion(e)) | ||||
|             return true | ||||
|         } | ||||
|  | ||||
|         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 (isDoubleTapping) | ||||
|             return true | ||||
|  | ||||
|         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 | ||||
|     } | ||||
|  | ||||
|     // /////////////////////////////////////////////////////////////////// | ||||
|     // Multi double tapping | ||||
|     // /////////////////////////////////////////////////////////////////// | ||||
|  | ||||
|     var doubleTapControls: DoubleTapListener? = null | ||||
|         private set | ||||
|  | ||||
|     val isDoubleTapEnabled: Boolean | ||||
|         get() = doubleTapDelay > 0 | ||||
|  | ||||
|     var isDoubleTapping = false | ||||
|         private set | ||||
|  | ||||
|     fun doubleTapControls(listener: DoubleTapListener) = apply { | ||||
|         doubleTapControls = listener | ||||
|     } | ||||
|  | ||||
|     private var doubleTapDelay = DOUBLE_TAP_DELAY | ||||
|     private val doubleTapHandler: Handler = Handler() | ||||
|     private val doubleTapRunnable = Runnable { | ||||
|         if (DEBUG) | ||||
|             Log.d(TAG, "doubleTapRunnable called") | ||||
|  | ||||
|         isDoubleTapping = false | ||||
|         doubleTapControls?.onDoubleTapFinished() | ||||
|     } | ||||
|  | ||||
|     fun startMultiDoubleTap(e: MotionEvent) { | ||||
|         if (!isDoubleTapping) { | ||||
|             if (DEBUG) | ||||
|                 Log.d(TAG, "startMultiDoubleTap called with e = [$e]") | ||||
|  | ||||
|             keepInDoubleTapMode() | ||||
|             doubleTapControls?.onDoubleTapStarted(getDisplayPortion(e)) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun keepInDoubleTapMode() { | ||||
|         if (DEBUG) | ||||
|             Log.d(TAG, "keepInDoubleTapMode called") | ||||
|  | ||||
|         isDoubleTapping = true | ||||
|         doubleTapHandler.removeCallbacks(doubleTapRunnable) | ||||
|         doubleTapHandler.postDelayed(doubleTapRunnable, doubleTapDelay) | ||||
|     } | ||||
|  | ||||
|     fun endMultiDoubleTap() { | ||||
|         if (DEBUG) | ||||
|             Log.d(TAG, "endMultiDoubleTap called") | ||||
|  | ||||
|         isDoubleTapping = false | ||||
|         doubleTapHandler.removeCallbacks(doubleTapRunnable) | ||||
|         doubleTapControls?.onDoubleTapFinished() | ||||
|     } | ||||
|  | ||||
|     fun enableMultiDoubleTap(enable: Boolean) = apply { | ||||
|         doubleTapDelay = if (enable) DOUBLE_TAP_DELAY else 0 | ||||
|     } | ||||
|  | ||||
|     // /////////////////////////////////////////////////////////////////// | ||||
|     // 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 DOUBLE_TAP_DELAY = 550L | ||||
|         private const val MOVEMENT_THRESHOLD = 40 | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,5 @@ | ||||
| package org.schabi.newpipe.player.event | ||||
|  | ||||
| enum class DisplayPortion { | ||||
|     LEFT, MIDDLE, RIGHT, LEFT_HALF, RIGHT_HALF | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package org.schabi.newpipe.player.event | ||||
|  | ||||
| interface DoubleTapListener { | ||||
|     fun onDoubleTapStarted(portion: DisplayPortion) {} | ||||
|     fun onDoubleTapProgressDown(portion: DisplayPortion) {} | ||||
|     fun onDoubleTapFinished() {} | ||||
| } | ||||
| @@ -1,16 +1,16 @@ | ||||
| package org.schabi.newpipe.player.event; | ||||
|  | ||||
| import android.app.Activity; | ||||
| 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 android.view.Window; | ||||
| import android.view.WindowManager; | ||||
| import android.widget.ProgressBar; | ||||
|  | ||||
| import androidx.appcompat.content.res.AppCompatResources; | ||||
|  | ||||
| import org.jetbrains.annotations.NotNull; | ||||
| import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.player.BasePlayer; | ||||
| import org.schabi.newpipe.player.MainPlayer; | ||||
| @@ -23,217 +23,116 @@ 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.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 | ||||
|         extends GestureDetector.SimpleOnGestureListener | ||||
|         extends BasePlayerGestureListener | ||||
|         implements View.OnTouchListener { | ||||
|     private static final String TAG = ".PlayerGestureListener"; | ||||
|     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 isBrightnessGestureEnabled; | ||||
|     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) { | ||||
|         this.playerImpl = playerImpl; | ||||
|         this.service = service; | ||||
|         this.tossFlingVelocity = PlayerHelper.getTossFlingVelocity(service); | ||||
|         super(playerImpl, service); | ||||
|  | ||||
|         isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service); | ||||
|         isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service); | ||||
|         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 | ||||
|     public boolean onDoubleTap(final MotionEvent e) { | ||||
|     public void onDoubleTap(@NotNull final MotionEvent event, | ||||
|                             @NotNull final DisplayPortion portion) { | ||||
|         if (DEBUG) { | ||||
|             Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " | ||||
|                     + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); | ||||
|             Log.d(TAG, "onDoubleTap called with playerType = [" | ||||
|                     + playerImpl.getPlayerType() + "], portion = [" | ||||
|                     + portion + "]"); | ||||
|         } | ||||
|         if (playerImpl.isSomePopupMenuVisible()) { | ||||
|             playerImpl.hideControls(0, 0); | ||||
|         } | ||||
|  | ||||
|         if (playerImpl.popupPlayerSelected()) { | ||||
|             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) { | ||||
|         if (portion == DisplayPortion.LEFT) { | ||||
|             playerImpl.onFastRewind(); | ||||
|         } else { | ||||
|             playerImpl.getPlayPauseButton().performClick(); | ||||
|         } else if (portion == DisplayPortion.MIDDLE) { | ||||
|             playerImpl.onPlayPause(); | ||||
|         } else if (portion == DisplayPortion.RIGHT) { | ||||
|             playerImpl.onFastForward(); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private boolean onSingleTapConfirmedInMain(final MotionEvent e) { | ||||
|     @Override | ||||
|     public void onSingleTap(@NotNull final MainPlayer.PlayerType playerType) { | ||||
|         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) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         if (playerImpl.isControlsVisible()) { | ||||
|             playerImpl.hideControls(150, 0); | ||||
|         } else { | ||||
|             if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) { | ||||
|                 playerImpl.showControls(0); | ||||
|             if (playerImpl.isControlsVisible()) { | ||||
|                 playerImpl.hideControls(100, 100); | ||||
|             } else { | ||||
|                 playerImpl.getPlayPauseButton().requestFocus(); | ||||
|                 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, | ||||
|                                    final float distanceX, final float distanceY) { | ||||
|         if ((!isVolumeGestureEnabled && !isBrightnessGestureEnabled) | ||||
|                 || !playerImpl.isFullscreen()) { | ||||
|             return false; | ||||
|     @Override | ||||
|     public void onScroll(@NotNull final MainPlayer.PlayerType playerType, | ||||
|                          @NotNull final DisplayPortion portion, | ||||
|                          @NotNull final MotionEvent initialEvent, | ||||
|                          @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); | ||||
|         final boolean isTouchingNavigationBar = initialEvent.getY() | ||||
|                 > playerImpl.getRootView().getHeight() - getNavigationBarHeight(service); | ||||
|         if (isTouchingStatusBar || isTouchingNavigationBar) { | ||||
|             return false; | ||||
|             } else /* DisplayPortion.RIGHT_HALF */ { | ||||
|                 onScrollMainBrightness(distanceX, distanceY); | ||||
|             } | ||||
|  | ||||
|         } 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 = " + | ||||
|                 ", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" + | ||||
|                 ", 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) { | ||||
|     private void onScrollMainVolume(final float distanceX, final float distanceY) { | ||||
|         if (isVolumeGestureEnabled) { | ||||
|             playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY); | ||||
|             final float currentProgressPercent = (float) playerImpl | ||||
|                     .getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength(); | ||||
| @@ -258,10 +157,14 @@ public class PlayerGestureListener | ||||
|             if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { | ||||
|                 playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE); | ||||
|             } | ||||
|         } else { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void onScrollMainBrightness(final float distanceX, final float distanceY) { | ||||
|         if (isBrightnessGestureEnabled) { | ||||
|             final Activity parent = playerImpl.getParentActivity(); | ||||
|             if (parent == null) { | ||||
|                 return true; | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             final Window window = parent.getWindow(); | ||||
| @@ -299,330 +202,71 @@ public class PlayerGestureListener | ||||
|                 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) { | ||||
|             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) { | ||||
|             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.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { | ||||
|                 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.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) { | ||||
|             playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); | ||||
|             if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) { | ||||
|                 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) { | ||||
|         playerImpl.getGestureDetector().onTouchEvent(event); | ||||
|         if (event.getAction() == MotionEvent.ACTION_UP && isMovingInMain) { | ||||
|             isMovingInMain = false; | ||||
|             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; | ||||
|     @Override | ||||
|     public void onPopupResizingStart() { | ||||
|         if (DEBUG) { | ||||
|             Log.d(TAG, "onPopupResizingStart called"); | ||||
|         } | ||||
|         playerImpl.showAndAnimateControl(-1, true); | ||||
|         playerImpl.getLoadingPanel().setVisibility(View.GONE); | ||||
|  | ||||
|         playerImpl.hideControls(0, 0); | ||||
|  | ||||
|         if (e.getX() > playerImpl.getPopupWidth() / 2) { | ||||
|             playerImpl.onFastForward(); | ||||
|         } else { | ||||
|             playerImpl.onFastRewind(); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|         animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0); | ||||
|         animateView(playerImpl.getResizingIndicator(), true, 200, 0); | ||||
|     } | ||||
|  | ||||
|     private boolean onSingleTapConfirmedInPopup(final MotionEvent e) { | ||||
|         if (playerImpl == null || playerImpl.getPlayer() == null) { | ||||
|             return false; | ||||
|     @Override | ||||
|     public void onPopupResizingEnd() { | ||||
|         if (DEBUG) { | ||||
|             Log.d(TAG, "onPopupResizingEnd called"); | ||||
|         } | ||||
|         if (playerImpl.isControlsVisible()) { | ||||
|             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; | ||||
|         animateView(playerImpl.getResizingIndicator(), false, 100, 0); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Stypox
					Stypox