mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 22:30:30 +05:30
seperate playerView logic
This commit is contained in:
parent
c3ad60f19c
commit
37dcef7fce
@ -46,8 +46,6 @@ import com.github.libretube.api.RetrofitInstance
|
|||||||
import com.github.libretube.api.SubscriptionHelper
|
import com.github.libretube.api.SubscriptionHelper
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
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.DoubleTapOverlayBinding
|
||||||
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
|
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
|
||||||
import com.github.libretube.databinding.FragmentPlayerBinding
|
import com.github.libretube.databinding.FragmentPlayerBinding
|
||||||
@ -61,10 +59,8 @@ import com.github.libretube.extensions.TAG
|
|||||||
import com.github.libretube.extensions.await
|
import com.github.libretube.extensions.await
|
||||||
import com.github.libretube.extensions.formatShort
|
import com.github.libretube.extensions.formatShort
|
||||||
import com.github.libretube.extensions.hideKeyboard
|
import com.github.libretube.extensions.hideKeyboard
|
||||||
import com.github.libretube.extensions.setSliderRangeAndValue
|
|
||||||
import com.github.libretube.extensions.toID
|
import com.github.libretube.extensions.toID
|
||||||
import com.github.libretube.interfaces.DoubleTapInterface
|
import com.github.libretube.interfaces.PlayerViewInterface
|
||||||
import com.github.libretube.interfaces.PlayerOptionsInterface
|
|
||||||
import com.github.libretube.models.PlayerViewModel
|
import com.github.libretube.models.PlayerViewModel
|
||||||
import com.github.libretube.obj.ChapterSegment
|
import com.github.libretube.obj.ChapterSegment
|
||||||
import com.github.libretube.obj.Segment
|
import com.github.libretube.obj.Segment
|
||||||
@ -77,7 +73,6 @@ import com.github.libretube.util.ImageHelper
|
|||||||
import com.github.libretube.util.NowPlayingNotification
|
import com.github.libretube.util.NowPlayingNotification
|
||||||
import com.github.libretube.util.PlayerHelper
|
import com.github.libretube.util.PlayerHelper
|
||||||
import com.github.libretube.util.PreferenceHelper
|
import com.github.libretube.util.PreferenceHelper
|
||||||
import com.github.libretube.views.PlayerOptionsBottomSheet
|
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl
|
import com.google.android.exoplayer2.DefaultLoadControl
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
@ -92,13 +87,11 @@ import com.google.android.exoplayer2.source.MediaSource
|
|||||||
import com.google.android.exoplayer2.source.MergingMediaSource
|
import com.google.android.exoplayer2.source.MergingMediaSource
|
||||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
|
||||||
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
import com.google.android.exoplayer2.ui.StyledPlayerView
|
||||||
import com.google.android.exoplayer2.upstream.DataSource
|
import com.google.android.exoplayer2.upstream.DataSource
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSource
|
import com.google.android.exoplayer2.upstream.DefaultDataSource
|
||||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
||||||
import com.google.android.exoplayer2.util.RepeatModeUtil
|
|
||||||
import com.google.android.exoplayer2.video.VideoSize
|
import com.google.android.exoplayer2.video.VideoSize
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -153,7 +146,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
* for the player view
|
* for the player view
|
||||||
*/
|
*/
|
||||||
private lateinit var exoPlayerView: StyledPlayerView
|
private lateinit var exoPlayerView: StyledPlayerView
|
||||||
private var isPlayerLocked: Boolean = false
|
|
||||||
private var subtitle = mutableListOf<SubtitleConfiguration>()
|
private var subtitle = mutableListOf<SubtitleConfiguration>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,7 +153,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
*/
|
*/
|
||||||
private var token = ""
|
private var token = ""
|
||||||
private var relatedStreamsEnabled = true
|
private var relatedStreamsEnabled = true
|
||||||
private var autoplayEnabled = false
|
|
||||||
private var autoRotationEnabled = true
|
private var autoRotationEnabled = true
|
||||||
private var playbackSpeed = "1F"
|
private var playbackSpeed = "1F"
|
||||||
private var pausePlayerOnScreenOffEnabled = false
|
private var pausePlayerOnScreenOffEnabled = false
|
||||||
@ -169,7 +160,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
private var watchHistoryEnabled = true
|
private var watchHistoryEnabled = true
|
||||||
private var watchPositionsEnabled = true
|
private var watchPositionsEnabled = true
|
||||||
private var useSystemCaptionStyle = true
|
private var useSystemCaptionStyle = true
|
||||||
private var seekIncrement = 5L
|
|
||||||
private var videoFormatPreference = "webm"
|
private var videoFormatPreference = "webm"
|
||||||
private var defRes = ""
|
private var defRes = ""
|
||||||
private var bufferingGoal = 50000
|
private var bufferingGoal = 50000
|
||||||
@ -178,7 +168,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
private var sponsorBlockNotifications = true
|
private var sponsorBlockNotifications = true
|
||||||
private var skipButtonsEnabled = false
|
private var skipButtonsEnabled = false
|
||||||
private var pipEnabled = true
|
private var pipEnabled = true
|
||||||
private var resizeModePref = "fit"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* for autoplay
|
* for autoplay
|
||||||
@ -245,7 +234,7 @@ class PlayerFragment : BaseFragment() {
|
|||||||
* somehow the bottom bar is invisible on low screen resolutions, this fixes it
|
* somehow the bottom bar is invisible on low screen resolutions, this fixes it
|
||||||
*/
|
*/
|
||||||
private fun showBottomBar() {
|
private fun showBottomBar() {
|
||||||
if (this::playerBinding.isInitialized && !isPlayerLocked) {
|
if (this::playerBinding.isInitialized && !binding.player.isPlayerLocked) {
|
||||||
playerBinding.exoBottomBar.visibility = View.VISIBLE
|
playerBinding.exoBottomBar.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
Handler(Looper.getMainLooper()).postDelayed(this::showBottomBar, 100)
|
Handler(Looper.getMainLooper()).postDelayed(this::showBottomBar, 100)
|
||||||
@ -260,11 +249,7 @@ class PlayerFragment : BaseFragment() {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
// save whether related streams and autoplay are enabled
|
// save whether related streams are enabled
|
||||||
autoplayEnabled = PreferenceHelper.getBoolean(
|
|
||||||
PreferenceKeys.AUTO_PLAY,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
relatedStreamsEnabled = PreferenceHelper.getBoolean(
|
relatedStreamsEnabled = PreferenceHelper.getBoolean(
|
||||||
PreferenceKeys.RELATED_STREAMS,
|
PreferenceKeys.RELATED_STREAMS,
|
||||||
true
|
true
|
||||||
@ -300,11 +285,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
seekIncrement = PreferenceHelper.getString(
|
|
||||||
PreferenceKeys.SEEK_INCREMENT,
|
|
||||||
"5"
|
|
||||||
).toLong() * 1000
|
|
||||||
|
|
||||||
videoFormatPreference = PreferenceHelper.getString(
|
videoFormatPreference = PreferenceHelper.getString(
|
||||||
PreferenceKeys.PLAYER_VIDEO_FORMAT,
|
PreferenceKeys.PLAYER_VIDEO_FORMAT,
|
||||||
"webm"
|
"webm"
|
||||||
@ -348,11 +328,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
PreferenceKeys.PICTURE_IN_PICTURE,
|
PreferenceKeys.PICTURE_IN_PICTURE,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
resizeModePref = PreferenceHelper.getString(
|
|
||||||
PreferenceKeys.PLAYER_RESIZE_MODE,
|
|
||||||
"fit"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@ -424,25 +399,7 @@ class PlayerFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val playerOptionsInterface = object : PlayerOptionsInterface {
|
private val playerViewInterface = object : PlayerViewInterface {
|
||||||
override fun onAutoplayClicked() {
|
|
||||||
// autoplay options dialog
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.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()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCaptionClicked() {
|
override fun onCaptionClicked() {
|
||||||
if (!this@PlayerFragment::streams.isInitialized ||
|
if (!this@PlayerFragment::streams.isInitialized ||
|
||||||
streams.subtitles == null ||
|
streams.subtitles == null ||
|
||||||
@ -515,62 +472,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaybackSpeedClicked() {
|
|
||||||
val playbackSpeedBinding = DialogSliderBinding.inflate(layoutInflater)
|
|
||||||
playbackSpeedBinding.slider.setSliderRangeAndValue(
|
|
||||||
PreferenceRanges.playbackSpeed
|
|
||||||
)
|
|
||||||
playbackSpeedBinding.slider.value = exoPlayer.playbackParameters.speed
|
|
||||||
// change playback speed dialog
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.change_playback_speed)
|
|
||||||
.setView(playbackSpeedBinding.root)
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.setPositiveButton(R.string.okay) { _, _ ->
|
|
||||||
exoPlayer.setPlaybackSpeed(
|
|
||||||
playbackSpeedBinding.slider.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override 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(requireContext())
|
|
||||||
.setTitle(R.string.aspect_ratio)
|
|
||||||
.setItems(aspectRatioModeNames) { _, index ->
|
|
||||||
exoPlayerView.resizeMode = aspectRatioModes[index]
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override 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_ALL,
|
|
||||||
RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE
|
|
||||||
)
|
|
||||||
// repeat mode options dialog
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.repeat_mode)
|
|
||||||
.setItems(repeatModeNames) { _, index ->
|
|
||||||
exoPlayer.repeatMode = repeatModes[index]
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// actions that don't depend on video information
|
// actions that don't depend on video information
|
||||||
@ -591,55 +492,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
.remove(this)
|
.remove(this)
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
// show the advanced player options
|
|
||||||
playerBinding.toggleOptions.setOnClickListener {
|
|
||||||
val bottomSheetFragment = PlayerOptionsBottomSheet().apply {
|
|
||||||
setOnClickListeners(playerOptionsInterface)
|
|
||||||
// set the auto play mode
|
|
||||||
currentAutoplayMode = if (autoplayEnabled) {
|
|
||||||
context.getString(R.string.enabled)
|
|
||||||
} else {
|
|
||||||
context.getString(R.string.disabled)
|
|
||||||
}
|
|
||||||
// set the current caption language
|
|
||||||
currentCaptions =
|
|
||||||
if (trackSelector.parameters.preferredTextLanguages.isNotEmpty()) {
|
|
||||||
trackSelector.parameters.preferredTextLanguages[0]
|
|
||||||
} else {
|
|
||||||
context.getString(R.string.none)
|
|
||||||
}
|
|
||||||
// set the playback speed
|
|
||||||
currentPlaybackSpeed = "${
|
|
||||||
exoPlayer.playbackParameters.speed.toString()
|
|
||||||
.replace(".0", "")
|
|
||||||
}x"
|
|
||||||
// set the quality text
|
|
||||||
val isAdaptive = exoPlayer.videoFormat?.codecs != null
|
|
||||||
val quality = exoPlayer.videoSize.height
|
|
||||||
if (quality != 0) {
|
|
||||||
currentQuality =
|
|
||||||
if (isAdaptive) {
|
|
||||||
"${context.getString(R.string.hls)} • ${quality}p"
|
|
||||||
} else {
|
|
||||||
"${quality}p"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// set the repeat mode
|
|
||||||
currentRepeatMode =
|
|
||||||
if (exoPlayer.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) {
|
|
||||||
context.getString(R.string.repeat_mode_none)
|
|
||||||
} else {
|
|
||||||
context.getString(R.string.repeat_mode_current)
|
|
||||||
}
|
|
||||||
// set the aspect ratio mode
|
|
||||||
currentResizeMode = when (exoPlayerView.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bottomSheetFragment.show(childFragmentManager, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.playImageView.setOnClickListener {
|
binding.playImageView.setOnClickListener {
|
||||||
if (!exoPlayer.isPlaying) {
|
if (!exoPlayer.isPlaying) {
|
||||||
@ -677,22 +529,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// lock and unlock the player
|
|
||||||
playerBinding.lockPlayer.setOnClickListener {
|
|
||||||
// change the locked/unlocked icon
|
|
||||||
if (!isPlayerLocked) {
|
|
||||||
playerBinding.lockPlayer.setImageResource(R.drawable.ic_locked)
|
|
||||||
} else {
|
|
||||||
playerBinding.lockPlayer.setImageResource(R.drawable.ic_unlocked)
|
|
||||||
}
|
|
||||||
|
|
||||||
// show/hide all the controls
|
|
||||||
lockPlayer(isPlayerLocked)
|
|
||||||
|
|
||||||
// change locked status
|
|
||||||
isPlayerLocked = !isPlayerLocked
|
|
||||||
}
|
|
||||||
|
|
||||||
// set default playback speed
|
// set default playback speed
|
||||||
exoPlayer.setPlaybackSpeed(playbackSpeed.toFloat())
|
exoPlayer.setPlaybackSpeed(playbackSpeed.toFloat())
|
||||||
|
|
||||||
@ -919,7 +755,7 @@ class PlayerFragment : BaseFragment() {
|
|||||||
// show comments if related streams disabled
|
// show comments if related streams disabled
|
||||||
if (!relatedStreamsEnabled) toggleComments()
|
if (!relatedStreamsEnabled) toggleComments()
|
||||||
// prepare for autoplay
|
// prepare for autoplay
|
||||||
if (autoplayEnabled) setNextStream()
|
if (binding.player.autoplayEnabled) setNextStream()
|
||||||
|
|
||||||
// add the video to the watch history
|
// add the video to the watch history
|
||||||
if (watchHistoryEnabled) DatabaseHelper.addToWatchHistory(videoId!!, streams)
|
if (watchHistoryEnabled) DatabaseHelper.addToWatchHistory(videoId!!, streams)
|
||||||
@ -1023,11 +859,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
controllerHideOnTouch = true
|
controllerHideOnTouch = true
|
||||||
useController = false
|
useController = false
|
||||||
player = exoPlayer
|
player = exoPlayer
|
||||||
resizeMode = when (resizeModePref) {
|
|
||||||
"fill" -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
|
||||||
"zoom" -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
|
||||||
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useSystemCaptionStyle) {
|
if (useSystemCaptionStyle) {
|
||||||
@ -1049,6 +880,14 @@ class PlayerFragment : BaseFragment() {
|
|||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private fun initializePlayerView(response: Streams) {
|
private fun initializePlayerView(response: Streams) {
|
||||||
|
// initialize the player view actions
|
||||||
|
binding.player.initialize(
|
||||||
|
childFragmentManager,
|
||||||
|
playerViewInterface,
|
||||||
|
doubleTapOverlayBinding,
|
||||||
|
trackSelector
|
||||||
|
)
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
playerViewsInfo.text =
|
playerViewsInfo.text =
|
||||||
context?.getString(R.string.views, response.views.formatShort()) +
|
context?.getString(R.string.views, response.views.formatShort()) +
|
||||||
@ -1073,8 +912,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
|
|
||||||
playerBinding.exoTitle.text = response.title
|
playerBinding.exoTitle.text = response.title
|
||||||
|
|
||||||
enableDoubleTapToSeek()
|
|
||||||
|
|
||||||
// init the chapters recyclerview
|
// init the chapters recyclerview
|
||||||
if (response.chapters != null) {
|
if (response.chapters != null) {
|
||||||
chapters = response.chapters
|
chapters = response.chapters
|
||||||
@ -1120,11 +957,11 @@ class PlayerFragment : BaseFragment() {
|
|||||||
playbackState == Player.STATE_ENDED &&
|
playbackState == Player.STATE_ENDED &&
|
||||||
nextStreamId != null &&
|
nextStreamId != null &&
|
||||||
!transitioning &&
|
!transitioning &&
|
||||||
autoplayEnabled
|
binding.player.autoplayEnabled
|
||||||
) {
|
) {
|
||||||
transitioning = true
|
transitioning = true
|
||||||
// check whether autoplay is enabled
|
// check whether autoplay is enabled
|
||||||
if (autoplayEnabled) playNextVideo()
|
if (binding.player.autoplayEnabled) playNextVideo()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playbackState == Player.STATE_READY) {
|
if (playbackState == Player.STATE_READY) {
|
||||||
@ -1250,83 +1087,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableDoubleTapToSeek() {
|
|
||||||
// set seek increment text
|
|
||||||
val seekIncrementText = (seekIncrement / 1000).toString()
|
|
||||||
doubleTapOverlayBinding.rewindTV.text = seekIncrementText
|
|
||||||
doubleTapOverlayBinding.forwardTV.text = seekIncrementText
|
|
||||||
binding.player.setOnDoubleTapListener(
|
|
||||||
object : DoubleTapInterface {
|
|
||||||
override fun onEvent(x: Float) {
|
|
||||||
val width = exoPlayerView.width
|
|
||||||
when {
|
|
||||||
width * 0.5 > x -> rewind()
|
|
||||||
width * 0.5 < x -> forward()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun rewind() {
|
|
||||||
exoPlayer.seekTo(exoPlayer.currentPosition - seekIncrement)
|
|
||||||
|
|
||||||
// show the rewind button
|
|
||||||
doubleTapOverlayBinding.rewindBTN.apply {
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
// clear previous animation
|
|
||||||
animate().rotation(0F).setDuration(0).start()
|
|
||||||
// start new animation
|
|
||||||
animate()
|
|
||||||
.rotation(-30F)
|
|
||||||
.setDuration(100)
|
|
||||||
.withEndAction {
|
|
||||||
// reset the animation when finished
|
|
||||||
animate().rotation(0F).setDuration(100).start()
|
|
||||||
}
|
|
||||||
.start()
|
|
||||||
|
|
||||||
removeCallbacks(hideRewindButtonRunnable)
|
|
||||||
// start callback to hide the button
|
|
||||||
postDelayed(hideRewindButtonRunnable, 700)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun forward() {
|
|
||||||
exoPlayer.seekTo(exoPlayer.currentPosition + seekIncrement)
|
|
||||||
|
|
||||||
// show the forward button
|
|
||||||
doubleTapOverlayBinding.forwardBTN.apply {
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
// clear previous animation
|
|
||||||
animate().rotation(0F).setDuration(0).start()
|
|
||||||
// start new animation
|
|
||||||
animate()
|
|
||||||
.rotation(30F)
|
|
||||||
.setDuration(100)
|
|
||||||
.withEndAction {
|
|
||||||
// reset the animation when finished
|
|
||||||
animate().rotation(0F).setDuration(100).start()
|
|
||||||
}
|
|
||||||
.start()
|
|
||||||
|
|
||||||
// start callback to hide the button
|
|
||||||
removeCallbacks(hideForwardButtonRunnable)
|
|
||||||
postDelayed(hideForwardButtonRunnable, 700)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val hideForwardButtonRunnable = Runnable {
|
|
||||||
doubleTapOverlayBinding.forwardBTN.apply {
|
|
||||||
visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private val hideRewindButtonRunnable = Runnable {
|
|
||||||
doubleTapOverlayBinding.rewindBTN.apply {
|
|
||||||
visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initializeChapters() {
|
private fun initializeChapters() {
|
||||||
if (chapters.isEmpty()) {
|
if (chapters.isEmpty()) {
|
||||||
binding.chaptersRecView.visibility = View.GONE
|
binding.chaptersRecView.visibility = View.GONE
|
||||||
@ -1533,7 +1293,7 @@ class PlayerFragment : BaseFragment() {
|
|||||||
// handles the audio focus
|
// handles the audio focus
|
||||||
val audioAttributes = AudioAttributes.Builder()
|
val audioAttributes = AudioAttributes.Builder()
|
||||||
.setUsage(C.USAGE_MEDIA)
|
.setUsage(C.USAGE_MEDIA)
|
||||||
.setContentType(C.CONTENT_TYPE_MOVIE)
|
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// handles the duration of media to retain in the buffer prior to the current playback position (for fast backward seeking)
|
// handles the duration of media to retain in the buffer prior to the current playback position (for fast backward seeking)
|
||||||
@ -1570,34 +1330,6 @@ class PlayerFragment : BaseFragment() {
|
|||||||
nowPlayingNotification.updatePlayerNotification(streams)
|
nowPlayingNotification.updatePlayerNotification(streams)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
playerBinding.exoTopBarRight.visibility = visibility
|
|
||||||
playerBinding.exoCenterControls.visibility = visibility
|
|
||||||
playerBinding.exoBottomBar.visibility = visibility
|
|
||||||
playerBinding.closeImageButton.visibility = visibility
|
|
||||||
playerBinding.exoTitle.visibility =
|
|
||||||
if (isLocked &&
|
|
||||||
viewModel.isFullscreen.value == true
|
|
||||||
) {
|
|
||||||
View.VISIBLE
|
|
||||||
} else {
|
|
||||||
View.INVISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
binding.player.setOnDoubleTapListener(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isSubscribed() {
|
private fun isSubscribed() {
|
||||||
fun run() {
|
fun run() {
|
||||||
val channelId = streams.uploaderUrl!!.toID()
|
val channelId = streams.uploaderUrl!!.toID()
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.github.libretube.interfaces
|
||||||
|
|
||||||
|
interface PlayerViewInterface {
|
||||||
|
fun onCaptionClicked()
|
||||||
|
|
||||||
|
fun onQualityClicked()
|
||||||
|
}
|
@ -3,12 +3,28 @@ package com.github.libretube.views
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
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.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.databinding.ExoStyledPlayerControlViewBinding
|
||||||
|
import com.github.libretube.extensions.setSliderRangeAndValue
|
||||||
import com.github.libretube.interfaces.DoubleTapInterface
|
import com.github.libretube.interfaces.DoubleTapInterface
|
||||||
|
import com.github.libretube.interfaces.PlayerOptionsInterface
|
||||||
|
import com.github.libretube.interfaces.PlayerViewInterface
|
||||||
import com.github.libretube.util.DoubleTapListener
|
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.ui.StyledPlayerView
|
||||||
|
import com.google.android.exoplayer2.util.RepeatModeUtil
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
internal class CustomExoPlayerView(
|
internal class CustomExoPlayerView(
|
||||||
@ -16,17 +32,36 @@ internal class CustomExoPlayerView(
|
|||||||
attributeSet: AttributeSet? = null
|
attributeSet: AttributeSet? = null
|
||||||
) : StyledPlayerView(context, attributeSet) {
|
) : StyledPlayerView(context, attributeSet) {
|
||||||
val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this)
|
val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this)
|
||||||
|
private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = null
|
||||||
|
|
||||||
private var doubleTapListener: DoubleTapInterface? = null
|
private var doubleTapListener: DoubleTapInterface? = null
|
||||||
|
private var playerViewInterface: PlayerViewInterface? = null
|
||||||
|
private lateinit var childFragmentManager: FragmentManager
|
||||||
|
|
||||||
|
private lateinit var trackSelector: TrackSelector
|
||||||
|
|
||||||
// the x-position of where the user clicked
|
// the x-position of where the user clicked
|
||||||
private var xPos = 0F
|
private var xPos = 0F
|
||||||
|
|
||||||
fun setOnDoubleTapListener(
|
var isPlayerLocked: Boolean = false
|
||||||
eventListener: DoubleTapInterface?
|
|
||||||
) {
|
/**
|
||||||
doubleTapListener = eventListener
|
* 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() {
|
private fun toggleController() {
|
||||||
if (isControllerFullyVisible) hideController() else showController()
|
if (isControllerFullyVisible) hideController() else showController()
|
||||||
@ -42,9 +77,44 @@ internal class CustomExoPlayerView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
fun initialize(
|
||||||
|
childFragmentManager: FragmentManager,
|
||||||
|
playerViewInterface: PlayerViewInterface,
|
||||||
|
doubleTapOverlayBinding: DoubleTapOverlayBinding,
|
||||||
|
trackSelector: TrackSelector
|
||||||
|
) {
|
||||||
|
this.childFragmentManager = childFragmentManager
|
||||||
|
this.playerViewInterface = playerViewInterface
|
||||||
|
this.doubleTapOverlayBinding = doubleTapOverlayBinding
|
||||||
|
this.trackSelector = trackSelector
|
||||||
|
|
||||||
// set the double click listener for rewind/forward
|
// set the double click listener for rewind/forward
|
||||||
setOnClickListener(doubleTouchListener)
|
setOnClickListener(doubleTouchListener)
|
||||||
|
|
||||||
|
enableDoubleTapToSeek()
|
||||||
|
|
||||||
|
initializeAdvancedOptions()
|
||||||
|
|
||||||
|
// 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() {
|
override fun hideController() {
|
||||||
@ -59,4 +129,230 @@ internal class CustomExoPlayerView(
|
|||||||
doubleTouchListener.onClick(this)
|
doubleTouchListener.onClick(this)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initializeAdvancedOptions() {
|
||||||
|
binding.toggleOptions.setOnClickListener {
|
||||||
|
val bottomSheetFragment = PlayerOptionsBottomSheet().apply {
|
||||||
|
setOnClickListeners(playerOptionsInterface)
|
||||||
|
// set the auto play mode
|
||||||
|
currentAutoplayMode = if (autoplayEnabled) {
|
||||||
|
context?.getString(R.string.enabled)
|
||||||
|
} else {
|
||||||
|
context?.getString(R.string.disabled)
|
||||||
|
}
|
||||||
|
// set the current caption language
|
||||||
|
currentCaptions =
|
||||||
|
if (trackSelector.parameters.preferredTextLanguages.isNotEmpty()) {
|
||||||
|
trackSelector.parameters.preferredTextLanguages[0]
|
||||||
|
} else {
|
||||||
|
context?.getString(R.string.none)
|
||||||
|
}
|
||||||
|
// set the playback speed
|
||||||
|
currentPlaybackSpeed = "${
|
||||||
|
player?.playbackParameters?.speed.toString()
|
||||||
|
.replace(".0", "")
|
||||||
|
}x"
|
||||||
|
// set the quality text
|
||||||
|
val quality = player?.videoSize?.height
|
||||||
|
if (quality != 0) {
|
||||||
|
currentQuality = "${quality}p"
|
||||||
|
}
|
||||||
|
// set the repeat mode
|
||||||
|
currentRepeatMode =
|
||||||
|
if (player?.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) {
|
||||||
|
context?.getString(R.string.repeat_mode_none)
|
||||||
|
} else {
|
||||||
|
context?.getString(R.string.repeat_mode_current)
|
||||||
|
}
|
||||||
|
// set the aspect ratio mode
|
||||||
|
currentResizeMode = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
// clear previous animation
|
||||||
|
animate().rotation(0F).setDuration(0).start()
|
||||||
|
// start new animation
|
||||||
|
animate()
|
||||||
|
.rotation(-30F)
|
||||||
|
.setDuration(100)
|
||||||
|
.withEndAction {
|
||||||
|
// reset the animation when finished
|
||||||
|
animate().rotation(0F).setDuration(100).start()
|
||||||
|
}
|
||||||
|
.start()
|
||||||
|
|
||||||
|
removeCallbacks(hideRewindButtonRunnable)
|
||||||
|
// start callback to hide the button
|
||||||
|
postDelayed(hideRewindButtonRunnable, 700)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun forward() {
|
||||||
|
player?.seekTo(player!!.currentPosition + seekIncrement)
|
||||||
|
|
||||||
|
// show the forward button
|
||||||
|
doubleTapOverlayBinding?.forwardBTN.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
// clear previous animation
|
||||||
|
animate().rotation(0F).setDuration(0).start()
|
||||||
|
// start new animation
|
||||||
|
animate()
|
||||||
|
.rotation(30F)
|
||||||
|
.setDuration(100)
|
||||||
|
.withEndAction {
|
||||||
|
// reset the animation when finished
|
||||||
|
animate().rotation(0F).setDuration(100).start()
|
||||||
|
}
|
||||||
|
.start()
|
||||||
|
|
||||||
|
// start callback to hide the button
|
||||||
|
removeCallbacks(hideForwardButtonRunnable)
|
||||||
|
postDelayed(hideForwardButtonRunnable, 700)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val hideForwardButtonRunnable = Runnable {
|
||||||
|
doubleTapOverlayBinding?.forwardBTN.apply {
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val hideRewindButtonRunnable = Runnable {
|
||||||
|
doubleTapOverlayBinding?.rewindBTN.apply {
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val playerOptionsInterface = object : PlayerOptionsInterface {
|
||||||
|
override 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCaptionClicked() {
|
||||||
|
playerViewInterface?.onCaptionClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQualityClicked() {
|
||||||
|
playerViewInterface?.onQualityClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
override 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
override 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
override 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_ALL,
|
||||||
|
RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE
|
||||||
|
)
|
||||||
|
// repeat mode options dialog
|
||||||
|
MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(R.string.repeat_mode)
|
||||||
|
.setItems(repeatModeNames) { _, index ->
|
||||||
|
player?.repeatMode = repeatModes[index]
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user