mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 14:20: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.constants.IntentData
|
||||
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.FragmentPlayerBinding
|
||||
@ -61,10 +59,8 @@ import com.github.libretube.extensions.TAG
|
||||
import com.github.libretube.extensions.await
|
||||
import com.github.libretube.extensions.formatShort
|
||||
import com.github.libretube.extensions.hideKeyboard
|
||||
import com.github.libretube.extensions.setSliderRangeAndValue
|
||||
import com.github.libretube.extensions.toID
|
||||
import com.github.libretube.interfaces.DoubleTapInterface
|
||||
import com.github.libretube.interfaces.PlayerOptionsInterface
|
||||
import com.github.libretube.interfaces.PlayerViewInterface
|
||||
import com.github.libretube.models.PlayerViewModel
|
||||
import com.github.libretube.obj.ChapterSegment
|
||||
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.PlayerHelper
|
||||
import com.github.libretube.util.PreferenceHelper
|
||||
import com.github.libretube.views.PlayerOptionsBottomSheet
|
||||
import com.google.android.exoplayer2.C
|
||||
import com.google.android.exoplayer2.DefaultLoadControl
|
||||
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.ProgressiveMediaSource
|
||||
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.StyledPlayerView
|
||||
import com.google.android.exoplayer2.upstream.DataSource
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSource
|
||||
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.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -153,7 +146,6 @@ class PlayerFragment : BaseFragment() {
|
||||
* for the player view
|
||||
*/
|
||||
private lateinit var exoPlayerView: StyledPlayerView
|
||||
private var isPlayerLocked: Boolean = false
|
||||
private var subtitle = mutableListOf<SubtitleConfiguration>()
|
||||
|
||||
/**
|
||||
@ -161,7 +153,6 @@ class PlayerFragment : BaseFragment() {
|
||||
*/
|
||||
private var token = ""
|
||||
private var relatedStreamsEnabled = true
|
||||
private var autoplayEnabled = false
|
||||
private var autoRotationEnabled = true
|
||||
private var playbackSpeed = "1F"
|
||||
private var pausePlayerOnScreenOffEnabled = false
|
||||
@ -169,7 +160,6 @@ class PlayerFragment : BaseFragment() {
|
||||
private var watchHistoryEnabled = true
|
||||
private var watchPositionsEnabled = true
|
||||
private var useSystemCaptionStyle = true
|
||||
private var seekIncrement = 5L
|
||||
private var videoFormatPreference = "webm"
|
||||
private var defRes = ""
|
||||
private var bufferingGoal = 50000
|
||||
@ -178,7 +168,6 @@ class PlayerFragment : BaseFragment() {
|
||||
private var sponsorBlockNotifications = true
|
||||
private var skipButtonsEnabled = false
|
||||
private var pipEnabled = true
|
||||
private var resizeModePref = "fit"
|
||||
|
||||
/**
|
||||
* for autoplay
|
||||
@ -245,7 +234,7 @@ class PlayerFragment : BaseFragment() {
|
||||
* somehow the bottom bar is invisible on low screen resolutions, this fixes it
|
||||
*/
|
||||
private fun showBottomBar() {
|
||||
if (this::playerBinding.isInitialized && !isPlayerLocked) {
|
||||
if (this::playerBinding.isInitialized && !binding.player.isPlayerLocked) {
|
||||
playerBinding.exoBottomBar.visibility = View.VISIBLE
|
||||
}
|
||||
Handler(Looper.getMainLooper()).postDelayed(this::showBottomBar, 100)
|
||||
@ -260,11 +249,7 @@ class PlayerFragment : BaseFragment() {
|
||||
false
|
||||
)
|
||||
|
||||
// save whether related streams and autoplay are enabled
|
||||
autoplayEnabled = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.AUTO_PLAY,
|
||||
false
|
||||
)
|
||||
// save whether related streams are enabled
|
||||
relatedStreamsEnabled = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.RELATED_STREAMS,
|
||||
true
|
||||
@ -300,11 +285,6 @@ class PlayerFragment : BaseFragment() {
|
||||
true
|
||||
)
|
||||
|
||||
seekIncrement = PreferenceHelper.getString(
|
||||
PreferenceKeys.SEEK_INCREMENT,
|
||||
"5"
|
||||
).toLong() * 1000
|
||||
|
||||
videoFormatPreference = PreferenceHelper.getString(
|
||||
PreferenceKeys.PLAYER_VIDEO_FORMAT,
|
||||
"webm"
|
||||
@ -348,11 +328,6 @@ class PlayerFragment : BaseFragment() {
|
||||
PreferenceKeys.PICTURE_IN_PICTURE,
|
||||
true
|
||||
)
|
||||
|
||||
resizeModePref = PreferenceHelper.getString(
|
||||
PreferenceKeys.PLAYER_RESIZE_MODE,
|
||||
"fit"
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@ -424,25 +399,7 @@ class PlayerFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private val playerOptionsInterface = object : PlayerOptionsInterface {
|
||||
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()
|
||||
}
|
||||
|
||||
private val playerViewInterface = object : PlayerViewInterface {
|
||||
override fun onCaptionClicked() {
|
||||
if (!this@PlayerFragment::streams.isInitialized ||
|
||||
streams.subtitles == null ||
|
||||
@ -515,62 +472,6 @@ class PlayerFragment : BaseFragment() {
|
||||
}
|
||||
.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
|
||||
@ -591,55 +492,6 @@ class PlayerFragment : BaseFragment() {
|
||||
.remove(this)
|
||||
.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 {
|
||||
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
|
||||
exoPlayer.setPlaybackSpeed(playbackSpeed.toFloat())
|
||||
|
||||
@ -919,7 +755,7 @@ class PlayerFragment : BaseFragment() {
|
||||
// show comments if related streams disabled
|
||||
if (!relatedStreamsEnabled) toggleComments()
|
||||
// prepare for autoplay
|
||||
if (autoplayEnabled) setNextStream()
|
||||
if (binding.player.autoplayEnabled) setNextStream()
|
||||
|
||||
// add the video to the watch history
|
||||
if (watchHistoryEnabled) DatabaseHelper.addToWatchHistory(videoId!!, streams)
|
||||
@ -1023,11 +859,6 @@ class PlayerFragment : BaseFragment() {
|
||||
controllerHideOnTouch = true
|
||||
useController = false
|
||||
player = exoPlayer
|
||||
resizeMode = when (resizeModePref) {
|
||||
"fill" -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
||||
"zoom" -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||
}
|
||||
}
|
||||
|
||||
if (useSystemCaptionStyle) {
|
||||
@ -1049,6 +880,14 @@ class PlayerFragment : BaseFragment() {
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun initializePlayerView(response: Streams) {
|
||||
// initialize the player view actions
|
||||
binding.player.initialize(
|
||||
childFragmentManager,
|
||||
playerViewInterface,
|
||||
doubleTapOverlayBinding,
|
||||
trackSelector
|
||||
)
|
||||
|
||||
binding.apply {
|
||||
playerViewsInfo.text =
|
||||
context?.getString(R.string.views, response.views.formatShort()) +
|
||||
@ -1073,8 +912,6 @@ class PlayerFragment : BaseFragment() {
|
||||
|
||||
playerBinding.exoTitle.text = response.title
|
||||
|
||||
enableDoubleTapToSeek()
|
||||
|
||||
// init the chapters recyclerview
|
||||
if (response.chapters != null) {
|
||||
chapters = response.chapters
|
||||
@ -1120,11 +957,11 @@ class PlayerFragment : BaseFragment() {
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
nextStreamId != null &&
|
||||
!transitioning &&
|
||||
autoplayEnabled
|
||||
binding.player.autoplayEnabled
|
||||
) {
|
||||
transitioning = true
|
||||
// check whether autoplay is enabled
|
||||
if (autoplayEnabled) playNextVideo()
|
||||
if (binding.player.autoplayEnabled) playNextVideo()
|
||||
}
|
||||
|
||||
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() {
|
||||
if (chapters.isEmpty()) {
|
||||
binding.chaptersRecView.visibility = View.GONE
|
||||
@ -1533,7 +1293,7 @@ class PlayerFragment : BaseFragment() {
|
||||
// handles the audio focus
|
||||
val audioAttributes = AudioAttributes.Builder()
|
||||
.setUsage(C.USAGE_MEDIA)
|
||||
.setContentType(C.CONTENT_TYPE_MOVIE)
|
||||
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
|
||||
.build()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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() {
|
||||
fun run() {
|
||||
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.content.Context
|
||||
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.interfaces.PlayerViewInterface
|
||||
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(
|
||||
@ -16,17 +32,36 @@ internal class CustomExoPlayerView(
|
||||
attributeSet: AttributeSet? = null
|
||||
) : StyledPlayerView(context, attributeSet) {
|
||||
val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this)
|
||||
private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = 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
|
||||
private var xPos = 0F
|
||||
|
||||
fun setOnDoubleTapListener(
|
||||
eventListener: DoubleTapInterface?
|
||||
) {
|
||||
doubleTapListener = eventListener
|
||||
}
|
||||
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()
|
||||
@ -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
|
||||
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() {
|
||||
@ -59,4 +129,230 @@ internal class CustomExoPlayerView(
|
||||
doubleTouchListener.onClick(this)
|
||||
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