Merge pull request #4587 from vkay94/separate-player-gesture-logic-ui
Separate player gesture logic and UI
This commit is contained in:
commit
f1583b6e0c
|
@ -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;
|
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,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.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.MIDDLE) {
|
||||||
playerImpl.getPlayPauseButton().performClick();
|
playerImpl.onPlayPause();
|
||||||
|
} else if (portion == DisplayPortion.RIGHT) {
|
||||||
|
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 +157,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 +202,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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue