DroidFS/app/src/main/java/sushi/hardcore/droidfs/widgets/DoubleTapPlayerView.kt

166 lines
5.8 KiB
Kotlin
Raw Normal View History

2022-01-18 20:18:44 +01:00
package sushi.hardcore.droidfs.widgets
import android.annotation.SuppressLint
import android.content.Context
2022-03-23 14:56:15 +01:00
import android.content.res.Configuration
2022-01-18 20:18:44 +01:00
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
2022-03-23 14:56:15 +01:00
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
2022-01-18 20:18:44 +01:00
import androidx.core.view.GestureDetectorCompat
2022-03-23 14:56:15 +01:00
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import com.google.android.exoplayer2.ui.StyledPlayerView
import sushi.hardcore.droidfs.R
2022-01-18 20:18:44 +01:00
class DoubleTapPlayerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
2022-03-23 14:56:15 +01:00
) : StyledPlayerView(context, attrs, defStyleAttr) {
2022-01-18 20:18:44 +01:00
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)
2022-03-23 14:56:15 +01:00
private val density by lazy {
context.resources.displayMetrics.density
}
private val originalExoIconPaddingBottom by lazy {
resources.getDimension(R.dimen.exo_icon_padding_bottom)
}
private val originalExoIconSize by lazy {
resources.getDimension(R.dimen.exo_icon_size)
}
init {
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
updateButtonSize(Configuration.ORIENTATION_LANDSCAPE)
}
}
2022-01-18 20:18:44 +01:00
@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)
}
}
}
}
2022-03-23 14:56:15 +01:00
private fun updateButtonSize(orientation: Int) {
val size = (if (orientation == Configuration.ORIENTATION_LANDSCAPE) 45*density else originalExoIconSize).toInt()
listOf(R.id.exo_prev, R.id.exo_rew_with_amount, R.id.exo_play_pause, R.id.exo_ffwd_with_amount, R.id.exo_next).forEach {
findViewById<View>(it).updateLayoutParams {
width = size
height = size
}
}
// fix text vertical alignment inside icons
val paddingBottom = (if (orientation == Configuration.ORIENTATION_LANDSCAPE) 15*density else originalExoIconPaddingBottom).toInt()
listOf(R.id.exo_rew_with_amount, R.id.exo_ffwd_with_amount).forEach {
findViewById<Button>(it).updatePadding(bottom = paddingBottom)
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val centerControls = findViewById<LinearLayout>(R.id.exo_center_controls)
(centerControls.parent as ViewGroup).removeView(centerControls)
findViewById<FrameLayout>(
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
R.id.center_controls_bar
else
R.id.center_controls_external
).addView(centerControls)
updateButtonSize(newConfig.orientation)
}
2022-01-18 20:18:44 +01:00
}