114 lines
3.7 KiB
Kotlin
114 lines
3.7 KiB
Kotlin
|
package sushi.hardcore.droidfs.widgets
|
||
|
|
||
|
import android.annotation.SuppressLint
|
||
|
import android.content.Context
|
||
|
import android.media.session.PlaybackState
|
||
|
import android.os.Handler
|
||
|
import android.os.Looper
|
||
|
import android.util.AttributeSet
|
||
|
import android.view.GestureDetector
|
||
|
import android.view.MotionEvent
|
||
|
import androidx.core.view.GestureDetectorCompat
|
||
|
import com.google.android.exoplayer2.ui.PlayerView
|
||
|
|
||
|
class DoubleTapPlayerView @JvmOverloads constructor(
|
||
|
context: Context,
|
||
|
attrs: AttributeSet? = null,
|
||
|
defStyleAttr: Int = 0
|
||
|
): PlayerView(context, attrs, defStyleAttr) {
|
||
|
|
||
|
companion object {
|
||
|
const val SEEK_SECONDS = 10
|
||
|
const val SEEK_MILLISECONDS = SEEK_SECONDS*1000
|
||
|
}
|
||
|
|
||
|
lateinit var doubleTapOverlay: DoubleTapOverlay
|
||
|
private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
|
||
|
private var isDoubleTapping = false
|
||
|
private val handler = Handler(Looper.getMainLooper())
|
||
|
private val stopDoubleTap = Runnable {
|
||
|
isDoubleTapping = false
|
||
|
}
|
||
|
|
||
|
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||
|
if (isDoubleTapping) {
|
||
|
handleDoubleTap(e.x, e.y)
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
||
|
if (!isDoubleTapping)
|
||
|
performClick()
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
override fun onDoubleTap(e: MotionEvent): Boolean {
|
||
|
if (!isDoubleTapping)
|
||
|
keepDoubleTapping()
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
|
||
|
if (e.actionMasked == MotionEvent.ACTION_UP && isDoubleTapping)
|
||
|
handleDoubleTap(e.x, e.y)
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
fun cancelDoubleTap() {
|
||
|
handler.removeCallbacks(stopDoubleTap)
|
||
|
isDoubleTapping = false
|
||
|
}
|
||
|
|
||
|
fun keepDoubleTapping() {
|
||
|
handler.removeCallbacks(stopDoubleTap)
|
||
|
isDoubleTapping = true
|
||
|
handler.postDelayed(stopDoubleTap, 700)
|
||
|
}
|
||
|
}
|
||
|
private val gestureDetector = GestureDetectorCompat(context, gestureListener)
|
||
|
|
||
|
@SuppressLint("ClickableViewAccessibility")
|
||
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||
|
gestureDetector.onTouchEvent(event)
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
fun handleDoubleTap(x: Float, y: Float) {
|
||
|
player?.let { player ->
|
||
|
if (player.playbackState == PlaybackState.STATE_ERROR ||
|
||
|
player.playbackState == PlaybackState.STATE_NONE ||
|
||
|
player.playbackState == PlaybackState.STATE_STOPPED)
|
||
|
gestureListener.cancelDoubleTap()
|
||
|
else if (player.currentPosition > 500 && x < doubleTapOverlay.width * 0.35)
|
||
|
triggerSeek(false, x, y)
|
||
|
else if (player.currentPosition < player.duration && x > doubleTapOverlay.width * 0.65)
|
||
|
triggerSeek(true, x, y)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private fun triggerSeek(forward: Boolean, x: Float, y: Float) {
|
||
|
doubleTapOverlay.showAnimation(forward, x, y)
|
||
|
player?.let { player ->
|
||
|
seekTo(
|
||
|
if (forward)
|
||
|
player.currentPosition + SEEK_MILLISECONDS
|
||
|
else
|
||
|
player.currentPosition - SEEK_MILLISECONDS
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private fun seekTo(position: Long) {
|
||
|
player?.let { player ->
|
||
|
when {
|
||
|
position <= 0 -> player.seekTo(0)
|
||
|
position >= player.duration -> player.seekTo(player.duration)
|
||
|
else -> {
|
||
|
gestureListener.keepDoubleTapping()
|
||
|
player.seekTo(position)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|