mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-27 15:30:31 +05:30
Merge pull request #1686 from Bnyro/master
Improved double tab behavior
This commit is contained in:
commit
2055a5d14b
@ -1,7 +1,7 @@
|
||||
package com.github.libretube.models.interfaces
|
||||
|
||||
interface PlayerOptionsInterface {
|
||||
fun onCaptionClicked()
|
||||
interface OnlinePlayerOptions {
|
||||
fun onCaptionsClicked()
|
||||
|
||||
fun onQualityClicked()
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.github.libretube.models.interfaces
|
||||
|
||||
interface PlayerOptions {
|
||||
fun onAutoplayClicked()
|
||||
|
||||
fun onPlaybackSpeedClicked()
|
||||
|
||||
fun onResizeModeClicked()
|
||||
|
||||
fun onRepeatModeClicked()
|
||||
}
|
@ -3,5 +3,6 @@ package com.github.libretube.obj
|
||||
data class BottomSheetItem(
|
||||
val title: String,
|
||||
val drawable: Int? = null,
|
||||
val currentValue: String? = null
|
||||
val currentValue: String? = null,
|
||||
val onClick: () -> Unit = {}
|
||||
)
|
||||
|
@ -34,6 +34,7 @@ class BottomSheetAdapter(
|
||||
}
|
||||
|
||||
root.setOnClickListener {
|
||||
item.onClick.invoke()
|
||||
listener.invoke(position)
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ import com.github.libretube.extensions.query
|
||||
import com.github.libretube.extensions.toID
|
||||
import com.github.libretube.extensions.toStreamItem
|
||||
import com.github.libretube.models.PlayerViewModel
|
||||
import com.github.libretube.models.interfaces.PlayerOptionsInterface
|
||||
import com.github.libretube.models.interfaces.OnlinePlayerOptions
|
||||
import com.github.libretube.services.BackgroundMode
|
||||
import com.github.libretube.services.DownloadService
|
||||
import com.github.libretube.ui.activities.MainActivity
|
||||
@ -105,7 +105,7 @@ import java.io.IOException
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.abs
|
||||
|
||||
class PlayerFragment : BaseFragment() {
|
||||
class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
||||
|
||||
lateinit var binding: FragmentPlayerBinding
|
||||
private lateinit var playerBinding: ExoStyledPlayerControlViewBinding
|
||||
@ -296,76 +296,6 @@ class PlayerFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private val onlinePlayerOptionsInterface = object : PlayerOptionsInterface {
|
||||
override fun onCaptionClicked() {
|
||||
if (!this@PlayerFragment::streams.isInitialized ||
|
||||
streams.subtitles == null ||
|
||||
streams.subtitles!!.isEmpty()
|
||||
) {
|
||||
Toast.makeText(context, R.string.no_subtitles_available, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!)
|
||||
val subtitleCodesList = mutableListOf("")
|
||||
streams.subtitles!!.forEach {
|
||||
subtitlesNamesList += it.name!!
|
||||
subtitleCodesList += it.code!!
|
||||
}
|
||||
|
||||
BottomSheet()
|
||||
.setSimpleItems(subtitlesNamesList) { index ->
|
||||
val newParams = if (index != 0) {
|
||||
// caption selected
|
||||
|
||||
// get the caption language code
|
||||
val captionLanguageCode = subtitleCodesList[index]
|
||||
|
||||
// select the new caption preference
|
||||
trackSelector.buildUponParameters()
|
||||
.setPreferredTextLanguage(captionLanguageCode)
|
||||
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
|
||||
} else {
|
||||
// none selected
|
||||
// disable captions
|
||||
trackSelector.buildUponParameters()
|
||||
.setPreferredTextLanguage("")
|
||||
}
|
||||
|
||||
// set the new caption language
|
||||
trackSelector.setParameters(newParams)
|
||||
}
|
||||
.show(childFragmentManager)
|
||||
}
|
||||
|
||||
override fun onQualityClicked() {
|
||||
// get the available resolutions
|
||||
val (videosNameArray, videosUrlArray) = getAvailableResolutions()
|
||||
|
||||
// Dialog for quality selection
|
||||
val lastPosition = exoPlayer.currentPosition
|
||||
BottomSheet()
|
||||
.setSimpleItems(
|
||||
videosNameArray.toList()
|
||||
) { which ->
|
||||
if (
|
||||
videosNameArray[which] == getString(R.string.hls) ||
|
||||
videosNameArray[which] == "LBRY HLS"
|
||||
) {
|
||||
// set the progressive media source
|
||||
setHLSMediaSource(videosUrlArray[which])
|
||||
} else {
|
||||
val videoUri = videosUrlArray[which]
|
||||
val audioUrl =
|
||||
PlayerHelper.getAudioSource(requireContext(), streams.audioStreams!!)
|
||||
setMediaSource(videoUri, audioUrl)
|
||||
}
|
||||
exoPlayer.seekTo(lastPosition)
|
||||
}
|
||||
.show(childFragmentManager)
|
||||
}
|
||||
}
|
||||
|
||||
// actions that don't depend on video information
|
||||
private fun initializeOnClickActions() {
|
||||
binding.closeImageView.setOnClickListener {
|
||||
@ -814,7 +744,7 @@ class PlayerFragment : BaseFragment() {
|
||||
// initialize the player view actions
|
||||
binding.player.initialize(
|
||||
childFragmentManager,
|
||||
onlinePlayerOptionsInterface,
|
||||
this,
|
||||
doubleTapOverlayBinding,
|
||||
trackSelector
|
||||
)
|
||||
@ -1389,6 +1319,74 @@ class PlayerFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCaptionsClicked() {
|
||||
if (!this@PlayerFragment::streams.isInitialized ||
|
||||
streams.subtitles == null ||
|
||||
streams.subtitles!!.isEmpty()
|
||||
) {
|
||||
Toast.makeText(context, R.string.no_subtitles_available, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!)
|
||||
val subtitleCodesList = mutableListOf("")
|
||||
streams.subtitles!!.forEach {
|
||||
subtitlesNamesList += it.name!!
|
||||
subtitleCodesList += it.code!!
|
||||
}
|
||||
|
||||
BottomSheet()
|
||||
.setSimpleItems(subtitlesNamesList) { index ->
|
||||
val newParams = if (index != 0) {
|
||||
// caption selected
|
||||
|
||||
// get the caption language code
|
||||
val captionLanguageCode = subtitleCodesList[index]
|
||||
|
||||
// select the new caption preference
|
||||
trackSelector.buildUponParameters()
|
||||
.setPreferredTextLanguage(captionLanguageCode)
|
||||
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
|
||||
} else {
|
||||
// none selected
|
||||
// disable captions
|
||||
trackSelector.buildUponParameters()
|
||||
.setPreferredTextLanguage("")
|
||||
}
|
||||
|
||||
// set the new caption language
|
||||
trackSelector.setParameters(newParams)
|
||||
}
|
||||
.show(childFragmentManager)
|
||||
}
|
||||
|
||||
override fun onQualityClicked() {
|
||||
// get the available resolutions
|
||||
val (videosNameArray, videosUrlArray) = getAvailableResolutions()
|
||||
|
||||
// Dialog for quality selection
|
||||
val lastPosition = exoPlayer.currentPosition
|
||||
BottomSheet()
|
||||
.setSimpleItems(
|
||||
videosNameArray.toList()
|
||||
) { which ->
|
||||
if (
|
||||
videosNameArray[which] == getString(R.string.hls) ||
|
||||
videosNameArray[which] == "LBRY HLS"
|
||||
) {
|
||||
// set the progressive media source
|
||||
setHLSMediaSource(videosUrlArray[which])
|
||||
} else {
|
||||
val videoUri = videosUrlArray[which]
|
||||
val audioUrl =
|
||||
PlayerHelper.getAudioSource(requireContext(), streams.audioStreams!!)
|
||||
setMediaSource(videoUri, audioUrl)
|
||||
}
|
||||
exoPlayer.seekTo(lastPosition)
|
||||
}
|
||||
.show(childFragmentManager)
|
||||
}
|
||||
|
||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
||||
if (isInPictureInPictureMode) {
|
||||
|
@ -32,18 +32,18 @@ open class BottomSheet : BottomSheetDialogFragment() {
|
||||
binding.optionsRecycler.adapter = BottomSheetAdapter(items, listener)
|
||||
}
|
||||
|
||||
fun setItems(items: List<BottomSheetItem>, listener: (index: Int) -> Unit) = apply {
|
||||
fun setItems(items: List<BottomSheetItem>, listener: ((index: Int) -> Unit)?) = apply {
|
||||
this.items = items
|
||||
this.listener = { index ->
|
||||
listener.invoke(index)
|
||||
listener?.invoke(index)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
fun setSimpleItems(titles: List<String>, listener: (index: Int) -> Unit) = apply {
|
||||
fun setSimpleItems(titles: List<String>, listener: ((index: Int) -> Unit)?) = apply {
|
||||
this.items = titles.map { BottomSheetItem(it) }
|
||||
this.listener = { index ->
|
||||
listener.invoke(index)
|
||||
listener?.invoke(index)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ import com.github.libretube.databinding.DoubleTapOverlayBinding
|
||||
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
|
||||
import com.github.libretube.extensions.toDp
|
||||
import com.github.libretube.models.interfaces.DoubleTapInterface
|
||||
import com.github.libretube.models.interfaces.PlayerOptionsInterface
|
||||
import com.github.libretube.models.interfaces.OnlinePlayerOptions
|
||||
import com.github.libretube.models.interfaces.PlayerOptions
|
||||
import com.github.libretube.obj.BottomSheetItem
|
||||
import com.github.libretube.ui.activities.MainActivity
|
||||
import com.github.libretube.ui.sheets.PlaybackSpeedSheet
|
||||
@ -30,7 +31,7 @@ import com.google.android.exoplayer2.util.RepeatModeUtil
|
||||
internal class CustomExoPlayerView(
|
||||
context: Context,
|
||||
attributeSet: AttributeSet? = null
|
||||
) : StyledPlayerView(context, attributeSet) {
|
||||
) : StyledPlayerView(context, attributeSet), PlayerOptions {
|
||||
val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this)
|
||||
private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = null
|
||||
|
||||
@ -38,7 +39,7 @@ internal class CustomExoPlayerView(
|
||||
* Objects from the parent fragment
|
||||
*/
|
||||
private var doubleTapListener: DoubleTapInterface? = null
|
||||
private var playerOptionsInterface: PlayerOptionsInterface? = null
|
||||
private var playerOptionsInterface: OnlinePlayerOptions? = null
|
||||
private lateinit var childFragmentManager: FragmentManager
|
||||
private var trackSelector: TrackSelector? = null
|
||||
|
||||
@ -72,7 +73,7 @@ internal class CustomExoPlayerView(
|
||||
|
||||
fun initialize(
|
||||
childFragmentManager: FragmentManager,
|
||||
playerViewInterface: PlayerOptionsInterface?,
|
||||
playerViewInterface: OnlinePlayerOptions?,
|
||||
doubleTapOverlayBinding: DoubleTapOverlayBinding,
|
||||
trackSelector: TrackSelector?
|
||||
) {
|
||||
@ -136,78 +137,79 @@ internal class CustomExoPlayerView(
|
||||
|
||||
private fun initializeAdvancedOptions(context: Context) {
|
||||
binding.toggleOptions.setOnClickListener {
|
||||
val bottomSheetFragment = BottomSheet().apply {
|
||||
val items = mutableListOf(
|
||||
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.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.playback_speed),
|
||||
R.drawable.ic_speed,
|
||||
"${
|
||||
player?.playbackParameters?.speed
|
||||
.toString()
|
||||
.replace(".0", "")
|
||||
}x"
|
||||
)
|
||||
)
|
||||
|
||||
if (playerOptionsInterface != null) {
|
||||
items.add(
|
||||
BottomSheetItem(
|
||||
context.getString(R.string.quality),
|
||||
R.drawable.ic_hd,
|
||||
"${player?.videoSize?.height}p"
|
||||
)
|
||||
)
|
||||
items.add(
|
||||
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 -> onRepeatModeClicked()
|
||||
2 -> onResizeModeClicked()
|
||||
3 -> onPlaybackSpeedClicked()
|
||||
4 -> playerOptionsInterface?.onQualityClicked()
|
||||
5 -> playerOptionsInterface?.onCaptionClicked()
|
||||
val items = mutableListOf(
|
||||
BottomSheetItem(
|
||||
context.getString(R.string.player_autoplay),
|
||||
R.drawable.ic_play,
|
||||
if (autoplayEnabled) {
|
||||
context.getString(R.string.enabled)
|
||||
} else {
|
||||
context.getString(R.string.disabled)
|
||||
}
|
||||
) {
|
||||
onAutoplayClicked()
|
||||
},
|
||||
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)
|
||||
}
|
||||
) {
|
||||
onRepeatModeClicked()
|
||||
},
|
||||
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)
|
||||
}
|
||||
) {
|
||||
onResizeModeClicked()
|
||||
},
|
||||
BottomSheetItem(
|
||||
context.getString(R.string.playback_speed),
|
||||
R.drawable.ic_speed,
|
||||
"${
|
||||
player?.playbackParameters?.speed
|
||||
.toString()
|
||||
.replace(".0", "")
|
||||
}x"
|
||||
) {
|
||||
onPlaybackSpeedClicked()
|
||||
}
|
||||
)
|
||||
|
||||
if (playerOptionsInterface != null) {
|
||||
items.add(
|
||||
BottomSheetItem(
|
||||
context.getString(R.string.quality),
|
||||
R.drawable.ic_hd,
|
||||
"${player?.videoSize?.height}p"
|
||||
) {
|
||||
playerOptionsInterface?.onQualityClicked()
|
||||
}
|
||||
)
|
||||
items.add(
|
||||
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)
|
||||
}
|
||||
) {
|
||||
playerOptionsInterface?.onCaptionsClicked()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val bottomSheetFragment = BottomSheet().setItems(items, null)
|
||||
bottomSheetFragment.show(childFragmentManager, null)
|
||||
}
|
||||
}
|
||||
@ -307,7 +309,7 @@ internal class CustomExoPlayerView(
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAutoplayClicked() {
|
||||
override fun onAutoplayClicked() {
|
||||
// autoplay options dialog
|
||||
BottomSheet()
|
||||
.setSimpleItems(
|
||||
@ -324,11 +326,11 @@ internal class CustomExoPlayerView(
|
||||
.show(childFragmentManager)
|
||||
}
|
||||
|
||||
private fun onPlaybackSpeedClicked() {
|
||||
override fun onPlaybackSpeedClicked() {
|
||||
player?.let { PlaybackSpeedSheet(it).show(childFragmentManager) }
|
||||
}
|
||||
|
||||
private fun onResizeModeClicked() {
|
||||
override fun onResizeModeClicked() {
|
||||
// switching between original aspect ratio (black bars) and zoomed to fill device screen
|
||||
val aspectRatioModeNames = context.resources?.getStringArray(R.array.resizeMode)
|
||||
?.toList().orEmpty()
|
||||
@ -346,7 +348,7 @@ internal class CustomExoPlayerView(
|
||||
.show(childFragmentManager)
|
||||
}
|
||||
|
||||
private fun onRepeatModeClicked() {
|
||||
override fun onRepeatModeClicked() {
|
||||
val repeatModeNames = listOf(
|
||||
context.getString(R.string.repeat_mode_none),
|
||||
context.getString(R.string.repeat_mode_current)
|
||||
|
@ -3,40 +3,49 @@ package com.github.libretube.util
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
|
||||
abstract class DoubleTapListener : View.OnClickListener {
|
||||
|
||||
private val maximumTimeDifference = 300L
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
private var isSingleEvent = false
|
||||
private var timeStampLastClick = 0L
|
||||
private var timeStampLastDoubleClick = 0L
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
if (SystemClock.elapsedRealtime() - timeStampLastClick < maximumTimeDifference) {
|
||||
isSingleEvent = false
|
||||
handler.removeCallbacks(runnable)
|
||||
timeStampLastDoubleClick = SystemClock.elapsedRealtime()
|
||||
onDoubleClick()
|
||||
return
|
||||
}
|
||||
isSingleEvent = true
|
||||
handler.removeCallbacks(runnable)
|
||||
handler.postDelayed(runnable, maximumTimeDifference)
|
||||
timeStampLastClick = SystemClock.elapsedRealtime()
|
||||
}
|
||||
private var lastClick = 0L
|
||||
private var lastDoubleClick = 0L
|
||||
|
||||
abstract fun onDoubleClick()
|
||||
abstract fun onSingleClick()
|
||||
|
||||
private val runnable = Runnable {
|
||||
if (!isSingleEvent ||
|
||||
SystemClock.elapsedRealtime() - timeStampLastDoubleClick < maximumTimeDifference
|
||||
) {
|
||||
return@Runnable
|
||||
override fun onClick(v: View?) {
|
||||
if (isSecondClick()) {
|
||||
handler.removeCallbacks(runnable)
|
||||
lastDoubleClick = elapsedTime()
|
||||
onDoubleClick()
|
||||
} else {
|
||||
if (recentDoubleClick()) return
|
||||
handler.removeCallbacks(runnable)
|
||||
handler.postDelayed(runnable, MAX_TIME_DIFF)
|
||||
lastClick = elapsedTime()
|
||||
}
|
||||
}
|
||||
|
||||
private val runnable = Runnable {
|
||||
if (isSecondClick()) return@Runnable
|
||||
Log.e("single", "single")
|
||||
onSingleClick()
|
||||
}
|
||||
|
||||
private fun isSecondClick(): Boolean {
|
||||
return elapsedTime() - lastClick < MAX_TIME_DIFF
|
||||
}
|
||||
|
||||
private fun recentDoubleClick(): Boolean {
|
||||
return elapsedTime() - lastDoubleClick < MAX_TIME_DIFF
|
||||
}
|
||||
|
||||
fun elapsedTime() = SystemClock.elapsedRealtime()
|
||||
|
||||
companion object {
|
||||
private const val MAX_TIME_DIFF = 400L
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user