Merge pull request #1686 from Bnyro/master

Improved double tab behavior
This commit is contained in:
Bnyro 2022-10-29 15:33:58 +02:00 committed by GitHub
commit 2055a5d14b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 202 additions and 180 deletions

View File

@ -1,7 +1,7 @@
package com.github.libretube.models.interfaces
interface PlayerOptionsInterface {
fun onCaptionClicked()
interface OnlinePlayerOptions {
fun onCaptionsClicked()
fun onQualityClicked()
}

View File

@ -0,0 +1,11 @@
package com.github.libretube.models.interfaces
interface PlayerOptions {
fun onAutoplayClicked()
fun onPlaybackSpeedClicked()
fun onResizeModeClicked()
fun onRepeatModeClicked()
}

View File

@ -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 = {}
)

View File

@ -34,6 +34,7 @@ class BottomSheetAdapter(
}
root.setOnClickListener {
item.onClick.invoke()
listener.invoke(position)
}
}

View File

@ -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) {

View File

@ -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()
}
}

View File

@ -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,7 +137,6 @@ 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),
@ -146,7 +146,9 @@ internal class CustomExoPlayerView(
} else {
context.getString(R.string.disabled)
}
),
) {
onAutoplayClicked()
},
BottomSheetItem(
context.getString(R.string.repeat_mode),
R.drawable.ic_repeat,
@ -155,7 +157,9 @@ internal class CustomExoPlayerView(
} else {
context.getString(R.string.repeat_mode_current)
}
),
) {
onRepeatModeClicked()
},
BottomSheetItem(
context.getString(R.string.player_resize_mode),
R.drawable.ic_aspect_ratio,
@ -164,7 +168,9 @@ internal class CustomExoPlayerView(
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,
@ -173,7 +179,9 @@ internal class CustomExoPlayerView(
.toString()
.replace(".0", "")
}x"
)
) {
onPlaybackSpeedClicked()
}
)
if (playerOptionsInterface != null) {
@ -182,7 +190,9 @@ internal class CustomExoPlayerView(
context.getString(R.string.quality),
R.drawable.ic_hd,
"${player?.videoSize?.height}p"
)
) {
playerOptionsInterface?.onQualityClicked()
}
)
items.add(
BottomSheetItem(
@ -193,21 +203,13 @@ internal class CustomExoPlayerView(
} else {
context.getString(R.string.none)
}
)
) {
playerOptionsInterface?.onCaptionsClicked()
}
)
}
setItems(items) { index ->
when (index) {
0 -> onAutoplayClicked()
1 -> onRepeatModeClicked()
2 -> onResizeModeClicked()
3 -> onPlaybackSpeedClicked()
4 -> playerOptionsInterface?.onQualityClicked()
5 -> playerOptionsInterface?.onCaptionClicked()
}
}
}
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)

View File

@ -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
}
}