mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 00:10:32 +05:30
383 lines
14 KiB
Kotlin
383 lines
14 KiB
Kotlin
package com.github.libretube.views
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.content.Context
|
|
import android.os.Handler
|
|
import android.os.Looper
|
|
import android.util.AttributeSet
|
|
import android.view.LayoutInflater
|
|
import android.view.MotionEvent
|
|
import android.view.View
|
|
import androidx.fragment.app.FragmentManager
|
|
import com.github.libretube.R
|
|
import com.github.libretube.activities.MainActivity
|
|
import com.github.libretube.constants.PreferenceKeys
|
|
import com.github.libretube.constants.PreferenceRanges
|
|
import com.github.libretube.databinding.DialogSliderBinding
|
|
import com.github.libretube.databinding.DoubleTapOverlayBinding
|
|
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
|
|
import com.github.libretube.extensions.setSliderRangeAndValue
|
|
import com.github.libretube.interfaces.DoubleTapInterface
|
|
import com.github.libretube.interfaces.PlayerOptionsInterface
|
|
import com.github.libretube.obj.BottomSheetItem
|
|
import com.github.libretube.util.DoubleTapListener
|
|
import com.github.libretube.util.PreferenceHelper
|
|
import com.google.android.exoplayer2.trackselection.TrackSelector
|
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
|
import com.google.android.exoplayer2.ui.StyledPlayerView
|
|
import com.google.android.exoplayer2.util.RepeatModeUtil
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
internal class CustomExoPlayerView(
|
|
context: Context,
|
|
attributeSet: AttributeSet? = null
|
|
) : StyledPlayerView(context, attributeSet) {
|
|
val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this)
|
|
private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = null
|
|
|
|
/**
|
|
* Objects from the parent fragment
|
|
*/
|
|
private var doubleTapListener: DoubleTapInterface? = null
|
|
private var playerOptionsInterface: PlayerOptionsInterface? = null
|
|
private lateinit var childFragmentManager: FragmentManager
|
|
private var trackSelector: TrackSelector? = null
|
|
|
|
private val runnableHandler = Handler(Looper.getMainLooper())
|
|
|
|
// the x-position of where the user clicked
|
|
private var xPos = 0F
|
|
|
|
var isPlayerLocked: Boolean = false
|
|
|
|
/**
|
|
* Preferences
|
|
*/
|
|
var autoplayEnabled = PreferenceHelper.getBoolean(
|
|
PreferenceKeys.AUTO_PLAY,
|
|
false
|
|
)
|
|
|
|
private val seekIncrement = PreferenceHelper.getString(
|
|
PreferenceKeys.SEEK_INCREMENT,
|
|
"5"
|
|
).toLong() * 1000
|
|
|
|
private var resizeModePref = PreferenceHelper.getString(
|
|
PreferenceKeys.PLAYER_RESIZE_MODE,
|
|
"fit"
|
|
)
|
|
|
|
private fun toggleController() {
|
|
if (isControllerFullyVisible) hideController() else showController()
|
|
}
|
|
|
|
private val doubleTouchListener = object : DoubleTapListener() {
|
|
override fun onDoubleClick() {
|
|
doubleTapListener?.onEvent(xPos)
|
|
}
|
|
|
|
override fun onSingleClick() {
|
|
toggleController()
|
|
}
|
|
}
|
|
|
|
fun initialize(
|
|
childFragmentManager: FragmentManager,
|
|
playerViewInterface: PlayerOptionsInterface?,
|
|
doubleTapOverlayBinding: DoubleTapOverlayBinding,
|
|
trackSelector: TrackSelector?
|
|
) {
|
|
this.childFragmentManager = childFragmentManager
|
|
this.playerOptionsInterface = playerViewInterface
|
|
this.doubleTapOverlayBinding = doubleTapOverlayBinding
|
|
this.trackSelector = trackSelector
|
|
|
|
// set the double click listener for rewind/forward
|
|
setOnClickListener(doubleTouchListener)
|
|
|
|
enableDoubleTapToSeek()
|
|
|
|
initializeAdvancedOptions(context)
|
|
|
|
// locking the player
|
|
binding.lockPlayer.setOnClickListener {
|
|
// change the locked/unlocked icon
|
|
binding.lockPlayer.setImageResource(
|
|
if (!isPlayerLocked) {
|
|
R.drawable.ic_locked
|
|
} else {
|
|
R.drawable.ic_unlocked
|
|
}
|
|
)
|
|
|
|
// show/hide all the controls
|
|
lockPlayer(isPlayerLocked)
|
|
|
|
// change locked status
|
|
isPlayerLocked = !isPlayerLocked
|
|
}
|
|
|
|
resizeMode = when (resizeModePref) {
|
|
"fill" -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
|
"zoom" -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
|
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
|
}
|
|
}
|
|
|
|
override fun hideController() {
|
|
(context as? MainActivity)?.hideSystemBars()
|
|
super.hideController()
|
|
}
|
|
|
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
// save the x position of the touch event
|
|
xPos = event.x
|
|
// listen for a double touch
|
|
doubleTouchListener.onClick(this)
|
|
return false
|
|
}
|
|
|
|
private fun initializeAdvancedOptions(context: Context) {
|
|
binding.toggleOptions.setOnClickListener {
|
|
val bottomSheetFragment = BottomSheet().apply {
|
|
val items = listOf(
|
|
BottomSheetItem(
|
|
context.getString(R.string.player_autoplay),
|
|
R.drawable.ic_play,
|
|
if (autoplayEnabled) {
|
|
context.getString(R.string.enabled)
|
|
} else {
|
|
context.getString(R.string.disabled)
|
|
}
|
|
),
|
|
BottomSheetItem(
|
|
context.getString(R.string.playback_speed),
|
|
R.drawable.ic_speed,
|
|
"${player?.playbackParameters?.speed
|
|
.toString()
|
|
.replace(".0", "")
|
|
}x"
|
|
),
|
|
BottomSheetItem(
|
|
context.getString(R.string.repeat_mode),
|
|
R.drawable.ic_repeat,
|
|
if (player?.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) {
|
|
context.getString(R.string.repeat_mode_none)
|
|
} else {
|
|
context.getString(R.string.repeat_mode_current)
|
|
}
|
|
),
|
|
BottomSheetItem(
|
|
context.getString(R.string.player_resize_mode),
|
|
R.drawable.ic_aspect_ratio,
|
|
when (resizeMode) {
|
|
AspectRatioFrameLayout.RESIZE_MODE_FIT -> context.getString(R.string.resize_mode_fit)
|
|
AspectRatioFrameLayout.RESIZE_MODE_FILL -> context.getString(R.string.resize_mode_fill)
|
|
else -> context.getString(R.string.resize_mode_zoom)
|
|
}
|
|
),
|
|
BottomSheetItem(
|
|
context.getString(R.string.quality),
|
|
R.drawable.ic_hd,
|
|
"${player?.videoSize?.height}p"
|
|
),
|
|
BottomSheetItem(
|
|
context.getString(R.string.captions),
|
|
R.drawable.ic_caption,
|
|
if (trackSelector != null && trackSelector!!.parameters.preferredTextLanguages.isNotEmpty()) {
|
|
trackSelector!!.parameters.preferredTextLanguages[0]
|
|
} else {
|
|
context.getString(R.string.none)
|
|
}
|
|
)
|
|
)
|
|
setItems(items) { index ->
|
|
when (index) {
|
|
0 -> onAutoplayClicked()
|
|
1 -> onPlaybackSpeedClicked()
|
|
2 -> onRepeatModeClicked()
|
|
3 -> onResizeModeClicked()
|
|
4 -> playerOptionsInterface?.onQualityClicked()
|
|
5 -> playerOptionsInterface?.onCaptionClicked()
|
|
}
|
|
}
|
|
}
|
|
bottomSheetFragment.show(childFragmentManager, null)
|
|
}
|
|
}
|
|
|
|
// lock the player
|
|
private fun lockPlayer(isLocked: Boolean) {
|
|
// isLocked is the current (old) state of the player lock
|
|
val visibility = if (isLocked) View.VISIBLE else View.GONE
|
|
|
|
binding.exoTopBarRight.visibility = visibility
|
|
binding.exoCenterControls.visibility = visibility
|
|
binding.exoBottomBar.visibility = visibility
|
|
binding.closeImageButton.visibility = visibility
|
|
|
|
// disable double tap to seek when the player is locked
|
|
if (isLocked) {
|
|
// enable fast forward and rewind by double tapping
|
|
enableDoubleTapToSeek()
|
|
} else {
|
|
// disable fast forward and rewind by double tapping
|
|
doubleTapListener = null
|
|
}
|
|
}
|
|
|
|
private fun enableDoubleTapToSeek() {
|
|
// set seek increment text
|
|
val seekIncrementText = (seekIncrement / 1000).toString()
|
|
doubleTapOverlayBinding?.rewindTV?.text = seekIncrementText
|
|
doubleTapOverlayBinding?.forwardTV?.text = seekIncrementText
|
|
doubleTapListener =
|
|
object : DoubleTapInterface {
|
|
override fun onEvent(x: Float) {
|
|
when {
|
|
width * 0.5 > x -> rewind()
|
|
width * 0.5 < x -> forward()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun rewind() {
|
|
player?.seekTo((player?.currentPosition ?: 0L) - seekIncrement)
|
|
|
|
// show the rewind button
|
|
doubleTapOverlayBinding?.rewindBTN.apply {
|
|
this!!.visibility = View.VISIBLE
|
|
// clear previous animation
|
|
this.animate().rotation(0F).setDuration(0).start()
|
|
// start new animation
|
|
this.animate()
|
|
.rotation(-30F)
|
|
.setDuration(100)
|
|
.withEndAction {
|
|
// reset the animation when finished
|
|
animate().rotation(0F).setDuration(100).start()
|
|
}
|
|
.start()
|
|
|
|
runnableHandler.removeCallbacks(hideRewindButtonRunnable)
|
|
// start callback to hide the button
|
|
runnableHandler.postDelayed(hideRewindButtonRunnable, 700)
|
|
}
|
|
}
|
|
|
|
private fun forward() {
|
|
player?.seekTo(player!!.currentPosition + seekIncrement)
|
|
|
|
// show the forward button
|
|
doubleTapOverlayBinding?.forwardBTN.apply {
|
|
this!!.visibility = View.VISIBLE
|
|
// clear previous animation
|
|
this.animate().rotation(0F).setDuration(0).start()
|
|
// start new animation
|
|
this.animate()
|
|
.rotation(30F)
|
|
.setDuration(100)
|
|
.withEndAction {
|
|
// reset the animation when finished
|
|
animate().rotation(0F).setDuration(100).start()
|
|
}
|
|
.start()
|
|
|
|
// start callback to hide the button
|
|
runnableHandler.removeCallbacks(hideForwardButtonRunnable)
|
|
runnableHandler.postDelayed(hideForwardButtonRunnable, 700)
|
|
}
|
|
}
|
|
|
|
private val hideForwardButtonRunnable = Runnable {
|
|
doubleTapOverlayBinding?.forwardBTN.apply {
|
|
this!!.visibility = View.GONE
|
|
}
|
|
}
|
|
private val hideRewindButtonRunnable = Runnable {
|
|
doubleTapOverlayBinding?.rewindBTN.apply {
|
|
this!!.visibility = View.GONE
|
|
}
|
|
}
|
|
|
|
fun onAutoplayClicked() {
|
|
// autoplay options dialog
|
|
MaterialAlertDialogBuilder(context)
|
|
.setTitle(R.string.player_autoplay)
|
|
.setItems(
|
|
arrayOf(
|
|
context.getString(R.string.enabled),
|
|
context.getString(R.string.disabled)
|
|
)
|
|
) { _, index ->
|
|
when (index) {
|
|
0 -> autoplayEnabled = true
|
|
1 -> autoplayEnabled = false
|
|
}
|
|
}
|
|
.show()
|
|
}
|
|
|
|
fun onPlaybackSpeedClicked() {
|
|
val playbackSpeedBinding = DialogSliderBinding.inflate(
|
|
LayoutInflater.from(context)
|
|
)
|
|
playbackSpeedBinding.slider.setSliderRangeAndValue(
|
|
PreferenceRanges.playbackSpeed
|
|
)
|
|
playbackSpeedBinding.slider.value = player?.playbackParameters?.speed ?: 1f
|
|
// change playback speed dialog
|
|
MaterialAlertDialogBuilder(context)
|
|
.setTitle(R.string.change_playback_speed)
|
|
.setView(playbackSpeedBinding.root)
|
|
.setNegativeButton(R.string.cancel, null)
|
|
.setPositiveButton(R.string.okay) { _, _ ->
|
|
player?.setPlaybackSpeed(
|
|
playbackSpeedBinding.slider.value
|
|
)
|
|
}
|
|
.show()
|
|
}
|
|
|
|
fun onResizeModeClicked() {
|
|
// switching between original aspect ratio (black bars) and zoomed to fill device screen
|
|
val aspectRatioModeNames = context.resources?.getStringArray(R.array.resizeMode)
|
|
|
|
val aspectRatioModes = arrayOf(
|
|
AspectRatioFrameLayout.RESIZE_MODE_FIT,
|
|
AspectRatioFrameLayout.RESIZE_MODE_ZOOM,
|
|
AspectRatioFrameLayout.RESIZE_MODE_FILL
|
|
)
|
|
|
|
MaterialAlertDialogBuilder(context)
|
|
.setTitle(R.string.aspect_ratio)
|
|
.setItems(aspectRatioModeNames) { _, index ->
|
|
resizeMode = aspectRatioModes[index]
|
|
}
|
|
.show()
|
|
}
|
|
|
|
fun onRepeatModeClicked() {
|
|
val repeatModeNames = arrayOf(
|
|
context.getString(R.string.repeat_mode_none),
|
|
context.getString(R.string.repeat_mode_current)
|
|
)
|
|
|
|
val repeatModes = arrayOf(
|
|
RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE,
|
|
RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL
|
|
|
|
)
|
|
// repeat mode options dialog
|
|
MaterialAlertDialogBuilder(context)
|
|
.setTitle(R.string.repeat_mode)
|
|
.setItems(repeatModeNames) { _, index ->
|
|
player?.repeatMode = repeatModes[index]
|
|
}
|
|
.show()
|
|
}
|
|
}
|