summaryrefslogtreecommitdiffstats
path: root/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt
diff options
context:
space:
mode:
Diffstat (limited to 'doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt')
-rw-r--r--doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt222
1 files changed, 222 insertions, 0 deletions
diff --git a/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt
new file mode 100644
index 000000000..a3374d90c
--- /dev/null
+++ b/doubletapplayerview/src/main/java/com/github/vkay94/dtpv/DoubleTapPlayerView.kt
@@ -0,0 +1,222 @@
+package com.github.vkay94.dtpv
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.util.AttributeSet
+import android.util.Log
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.View
+import androidx.core.view.GestureDetectorCompat
+import androidx.media3.ui.PlayerView
+
+
+/**
+ * Custom player class for Double-Tapping listening
+ */
+open class DoubleTapPlayerView @JvmOverloads constructor(
+ context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : PlayerView(context!!, attrs, defStyleAttr) {
+
+ private val gestureDetector: GestureDetectorCompat
+ private val gestureListener: DoubleTapGestureListener = DoubleTapGestureListener(rootView)
+
+ private var controller: PlayerDoubleTapListener? = null
+ get() = gestureListener.controls
+ set(value) {
+ gestureListener.controls = value
+ field = value
+ }
+
+ private var controllerRef: Int = -1
+
+ init {
+ gestureDetector = GestureDetectorCompat(context, gestureListener)
+
+ // Check whether controller is set through XML
+ attrs?.let {
+ val a = context?.obtainStyledAttributes(attrs, R.styleable.DoubleTapPlayerView, 0,0)
+ controllerRef = a?.getResourceId(R.styleable.DoubleTapPlayerView_dtpv_controller, -1) ?: -1
+
+ a?.recycle()
+ }
+ }
+
+ /**
+ * If this field is set to `true` this view will handle double tapping, otherwise it will
+ * handle touches the same way as the original [PlayerView][com.google.android.exoplayer2.ui.PlayerView] does
+ */
+ var isDoubleTapEnabled = true
+
+ /**
+ * Time window a double tap is active, so a followed tap is calling a gesture detector
+ * method instead of normal tap (see [PlayerView.onTouchEvent])
+ */
+ var doubleTapDelay: Long = 700
+ get() = gestureListener.doubleTapDelay
+ set(value) {
+ gestureListener.doubleTapDelay = value
+ field = value
+ }
+
+ /**
+ * Sets the [PlayerDoubleTapListener] which handles the gesture callbacks.
+ *
+ * Primarily used for [YouTubeOverlay][com.github.vkay94.dtpv.youtube.YouTubeOverlay]
+ */
+ fun controller(controller: PlayerDoubleTapListener) = apply { this.controller = controller }
+
+ /**
+ * Returns the current state of double tapping.
+ */
+ fun isInDoubleTapMode(): Boolean = gestureListener.isDoubleTapping
+
+ /**
+ * Resets the timeout to keep in double tap mode.
+ *
+ * Called once in [PlayerDoubleTapListener.onDoubleTapStarted]. Needs to be called
+ * from outside if the double tap is customized / overridden to detect ongoing taps
+ */
+ fun keepInDoubleTapMode() {
+ gestureListener.keepInDoubleTapMode()
+ }
+
+ /**
+ * Cancels double tap mode instantly by calling [PlayerDoubleTapListener.onDoubleTapFinished]
+ */
+ fun cancelInDoubleTapMode() {
+ gestureListener.cancelInDoubleTapMode()
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ if (isDoubleTapEnabled) {
+ gestureDetector.onTouchEvent(ev)
+
+ // Do not trigger original behavior when double tapping
+ // otherwise the controller would show/hide - it would flack
+ return true
+ }
+ return super.onTouchEvent(ev)
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+
+ // If the PlayerView is set by XML then call the corresponding setter method
+ if (controllerRef != -1) {
+ try {
+ val view = (this.parent as View).findViewById(controllerRef) as View
+ if (view is PlayerDoubleTapListener) {
+ controller(view)
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ Log.e("DoubleTapPlayerView",
+ "controllerRef is either invalid or not PlayerDoubleTapListener: ${e.message}")
+ }
+ }
+
+ }
+
+ /**
+ * Gesture Listener for double tapping
+ *
+ * For more information which methods are called in certain situations look for
+ * [GestureDetector.onTouchEvent][android.view.GestureDetector.onTouchEvent],
+ * especially for ACTION_DOWN and ACTION_UP
+ */
+ private class DoubleTapGestureListener(private val rootView: View) : GestureDetector.SimpleOnGestureListener() {
+
+ private val mHandler = Handler(Looper.getMainLooper())
+ private val mRunnable = Runnable {
+ if (DEBUG) Log.d(TAG, "Runnable called")
+ isDoubleTapping = false
+ controls?.onDoubleTapFinished()
+ }
+
+ var controls: PlayerDoubleTapListener? = null
+ var isDoubleTapping = false
+ var doubleTapDelay: Long = 650
+
+ /**
+ * Resets the timeout to keep in double tap mode.
+ *
+ * Called once in [PlayerDoubleTapListener.onDoubleTapStarted]. Needs to be called
+ * from outside if the double tap is customized / overridden to detect ongoing taps
+ */
+ fun keepInDoubleTapMode() {
+ isDoubleTapping = true
+ mHandler.removeCallbacks(mRunnable)
+ mHandler.postDelayed(mRunnable, doubleTapDelay)
+ }
+
+ /**
+ * Cancels double tap mode instantly by calling [PlayerDoubleTapListener.onDoubleTapFinished]
+ */
+ fun cancelInDoubleTapMode() {
+ mHandler.removeCallbacks(mRunnable)
+ isDoubleTapping = false
+ controls?.onDoubleTapFinished()
+ }
+
+ override fun onDown(e: MotionEvent): Boolean {
+ // Used to override the other methods
+ if (isDoubleTapping) {
+ controls?.onDoubleTapProgressDown(e.x, e.y)
+ return true
+ }
+ return super.onDown(e)
+ }
+
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
+ if (isDoubleTapping) {
+ if (DEBUG) Log.d(TAG, "onSingleTapUp: isDoubleTapping = true")
+ controls?.onDoubleTapProgressUp(e.x, e.y)
+ return true
+ }
+ return super.onSingleTapUp(e)
+ }
+
+ override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
+ // Ignore this event if double tapping is still active
+ // Return true needed because this method is also called if you tap e.g. three times
+ // in a row, therefore the controller would appear since the original behavior is
+ // to hide and show on single tap
+ if (isDoubleTapping) return true
+ if (DEBUG) Log.d(TAG, "onSingleTapConfirmed: isDoubleTap = false")
+ return rootView.performClick()
+ }
+
+ override fun onDoubleTap(e: MotionEvent): Boolean {
+ // First tap (ACTION_DOWN) of both taps
+ if (DEBUG) Log.d(TAG, "onDoubleTap")
+ if (!isDoubleTapping) {
+ isDoubleTapping = true
+ keepInDoubleTapMode()
+ controls?.onDoubleTapStarted(e.x, e.y)
+ }
+ return true
+ }
+
+ override fun onDoubleTapEvent(e: MotionEvent): Boolean {
+ // Second tap (ACTION_UP) of both taps
+ if (e.actionMasked == MotionEvent.ACTION_UP && isDoubleTapping) {
+ if (DEBUG) Log.d(
+ TAG,
+ "onDoubleTapEvent, ACTION_UP"
+ )
+ controls?.onDoubleTapProgressUp(e.x, e.y)
+ return true
+ }
+ return super.onDoubleTapEvent(e)
+ }
+
+ companion object {
+ private const val TAG = ".DTGListener"
+ private var DEBUG = true
+ }
+ }
+} \ No newline at end of file