attempt to declutter the player activity and layout

This commit is contained in:
jmir1 2022-04-14 11:53:31 +02:00
parent 0890a21eb2
commit 6c7e012e40
4 changed files with 668 additions and 603 deletions

View file

@ -29,12 +29,8 @@ import android.view.View
import android.view.ViewAnimationUtils
import android.view.WindowManager
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.SeekBar
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.WindowInsetsCompat
@ -54,9 +50,6 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import `is`.xyz.mpv.MPVLib
import `is`.xyz.mpv.PickerDialog
import `is`.xyz.mpv.SpeedPickerDialog
import `is`.xyz.mpv.StateRestoreCallback
import `is`.xyz.mpv.Utils
import logcat.LogPriority
import nucleus.factory.RequiresPresenter
@ -85,17 +78,6 @@ class PlayerActivity :
super.onNewIntent(intent)
}
private val ACTION_MEDIA_CONTROL = "media_control"
private val EXTRA_CONTROL_TYPE = "control_type"
private val REQUEST_PLAY = 1
private val REQUEST_PAUSE = 2
private val CONTROL_TYPE_PLAY = 1
private val CONTROL_TYPE_PAUSE = 2
private val REQUEST_PREVIOUS = 3
private val REQUEST_NEXT = 4
private val CONTROL_TYPE_PREVIOUS = 3
private val CONTROL_TYPE_NEXT = 4
private var isInPipMode: Boolean = false
private var mReceiver: BroadcastReceiver? = null
@ -104,7 +86,7 @@ class PlayerActivity :
private val langName = LocaleHelper.getSimpleLocaleDisplay(preferences.lang().get())
private val player get() = binding.player
internal val player get() = binding.player
private var audioManager: AudioManager? = null
private var fineVolume = 0F
@ -117,7 +99,7 @@ class PlayerActivity :
internal var isLocked = false
private val windowInsetsController by lazy { WindowInsetsControllerCompat(window, binding.root) }
internal val windowInsetsController by lazy { WindowInsetsControllerCompat(window, binding.root) }
private var audioFocusRestore: () -> Unit = {}
@ -192,8 +174,6 @@ class PlayerActivity :
private var initialSeek = -1
private var userIsOperatingSeekbar = false
private lateinit var mDetector: GestureDetectorCompat
private val animationHandler = Handler(Looper.getMainLooper())
@ -201,7 +181,7 @@ class PlayerActivity :
// Fade out seek text
private val seekTextRunnable = Runnable {
AnimationUtils.loadAnimation(this, R.anim.fade_out_medium).also { fadeAnimation ->
findViewById<LinearLayout>(R.id.seekView).startAnimation(fadeAnimation)
binding.seekView.startAnimation(fadeAnimation)
binding.seekView.visibility = View.GONE
}
}
@ -209,7 +189,7 @@ class PlayerActivity :
// Fade out Volume Bar
private val volumeViewRunnable = Runnable {
AnimationUtils.loadAnimation(this, R.anim.fade_out_medium).also { fadeAnimation ->
findViewById<LinearLayout>(R.id.volumeView).startAnimation(fadeAnimation)
binding.volumeView.startAnimation(fadeAnimation)
binding.volumeView.visibility = View.GONE
}
}
@ -217,24 +197,11 @@ class PlayerActivity :
// Fade out Brightness Bar
private val brightnessViewRunnable = Runnable {
AnimationUtils.loadAnimation(this, R.anim.fade_out_medium).also { fadeAnimation ->
findViewById<LinearLayout>(R.id.brightnessView).startAnimation(fadeAnimation)
binding.brightnessView.startAnimation(fadeAnimation)
binding.brightnessView.visibility = View.GONE
}
}
// Fade out Player controls
private val controlsViewRunnable = Runnable {
AnimationUtils.loadAnimation(this, R.anim.fade_out_medium).also { fadeAnimation ->
if (!isLocked) {
findViewById<LinearLayout>(R.id.controlsView).startAnimation(fadeAnimation)
binding.controlsView.visibility = View.GONE
} else {
findViewById<LinearLayout>(R.id.lockedView).startAnimation(fadeAnimation)
binding.lockedView.visibility = View.GONE
}
}
}
private fun showGestureView(type: String) {
val callback: Runnable
val itemView: LinearLayout
@ -255,11 +222,6 @@ class PlayerActivity :
itemView = binding.brightnessView
delay = 500L
}
"controls" -> {
callback = controlsViewRunnable
itemView = if (!isLocked) binding.controlsView else binding.lockedView
delay = 3500L
}
else -> return
}
@ -268,39 +230,21 @@ class PlayerActivity :
animationHandler.postDelayed(callback, delay)
}
private val seekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (!fromUser) {
return
}
player.timePos = progress
updatePlaybackPos(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
userIsOperatingSeekbar = true
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
userIsOperatingSeekbar = false
}
}
private var currentVideoList: List<Video>? = null
private var playerViewMode: Int = preferences.getPlayerViewMode()
private var playerIsDestroyed = true
private var subTracks: Array<Track> = emptyArray()
internal var subTracks: Array<Track> = emptyArray()
private var selectedSub = 0
internal var selectedSub = 0
private var hadPreviousSubs = false
private var audioTracks: Array<Track> = emptyArray()
internal var audioTracks: Array<Track> = emptyArray()
private var selectedAudio = 0
internal var selectedAudio = 0
private var hadPreviousAudio = false
@ -320,7 +264,7 @@ class PlayerActivity :
}
setVisibilities()
showGestureView("controls")
binding.playerControls.showAndFadeControls()
player.initialize(applicationContext.filesDir.path)
MPVLib.setOptionString("keep-open", "always")
@ -351,29 +295,6 @@ class PlayerActivity :
mDetector.onTouchEvent(event)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
binding.pipBtn.isVisible = packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
}
binding.backArrowBtn.setOnClickListener { finishAndRemoveTask() }
binding.pipBtn.setOnClickListener { startPiP() }
// Lock and Unlock controls
binding.lockBtn.setOnClickListener { isLocked = true; toggleControls() }
binding.unlockBtn.setOnClickListener { isLocked = false; toggleControls() }
// Cycle, Long click controls
binding.cycleAudioBtn.setOnLongClickListener { pickAudio(); true }
binding.cycleSpeedBtn.setOnLongClickListener { pickSpeed(); true }
binding.cycleSubsBtn.setOnLongClickListener { pickSub(); true }
binding.playbackSeekbar.setOnSeekBarChangeListener(seekBarChangeListener)
// player.playFile(currentVideoList!!.first().videoUrl!!.toString())
binding.nextBtn.setOnClickListener { switchEpisode(false) }
binding.prevBtn.setOnClickListener { switchEpisode(true) }
if (presenter?.needsInit() == true) {
val anime = intent.extras!!.getLong("anime", -1)
val episode = intent.extras!!.getLong("episode", -1)
@ -391,7 +312,7 @@ class PlayerActivity :
* Switches to the previous episode if [previous] is true,
* to the next episode if [previous] is false
*/
private fun switchEpisode(previous: Boolean) {
internal fun switchEpisode(previous: Boolean) {
val switchMethod = if (previous) presenter::previousEpisode else presenter::nextEpisode
val errorRes = if (previous) R.string.no_previous_episode else R.string.no_next_episode
@ -414,92 +335,15 @@ class PlayerActivity :
}
}
fun toggleControls() {
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
if (isLocked) {
// Hide controls
binding.controlsView.isVisible = false
if (!binding.lockedView.isVisible && !player.paused!!) showGestureView("controls")
else if (!binding.lockedView.isVisible && player.paused!!) binding.lockedView.visibility = View.VISIBLE
else {
animationHandler.removeCallbacks(controlsViewRunnable)
AnimationUtils.loadAnimation(this, R.anim.fade_out_medium).also { fadeAnimation ->
findViewById<LinearLayout>(R.id.lockedView).startAnimation(fadeAnimation)
binding.lockedView.visibility = View.GONE
}
}
} else {
if (!binding.controlsView.isVisible && !player.paused!!) showGestureView("controls")
else if (!binding.controlsView.isVisible && player.paused!!) binding.controlsView.visibility = View.VISIBLE
else {
animationHandler.removeCallbacks(controlsViewRunnable)
AnimationUtils.loadAnimation(this, R.anim.fade_out_medium).also { fadeAnimation ->
findViewById<LinearLayout>(R.id.controlsView).startAnimation(fadeAnimation)
binding.controlsView.visibility = View.GONE
}
}
binding.lockedView.isVisible = false
}
}
private fun hideControls(hide: Boolean) {
binding.controlsView.isVisible = !hide
}
fun toggleControls() = binding.playerControls.toggleControls()
private fun showLoadingIndicator(visible: Boolean) {
if (binding.loadingIndicator.isVisible == visible) return
binding.playBtn.isVisible = !visible
binding.playerControls.binding.playBtn.isVisible = !visible
binding.loadingIndicator.isVisible = visible
}
private fun pickAudio() {
val restore = pauseForDialog()
with(MaterialAlertDialogBuilder(this)) {
setSingleChoiceItems(
audioTracks.map { it.lang }.toTypedArray(),
selectedAudio,
) { dialog, item ->
if (item == selectedSub) return@setSingleChoiceItems
if (item == 0) {
selectedAudio = 0
player.aid = -1
return@setSingleChoiceItems
}
setAudio(item)
dialog.dismiss()
}
setOnDismissListener { restore() }
create().show()
}
}
private fun pickSub() {
val restore = pauseForDialog()
with(MaterialAlertDialogBuilder(this)) {
setSingleChoiceItems(
subTracks.map { it.lang }.toTypedArray(),
selectedSub,
) { dialog, item ->
if (item == 0) {
selectedSub = 0
player.sid = -1
return@setSingleChoiceItems
}
setSub(item)
dialog.dismiss()
}
setOnDismissListener { restore() }
create().show()
}
}
private fun setSub(index: Int) {
internal fun setSub(index: Int) {
if (selectedSub == index || selectedSub > subTracks.lastIndex) return
selectedSub = index
if (index == 0) {
@ -515,7 +359,7 @@ class PlayerActivity :
?: MPVLib.command(arrayOf("sub-add", subTracks[index].url, "select", subTracks[index].url))
}
private fun setAudio(index: Int) {
internal fun setAudio(index: Int) {
if (selectedAudio == index || selectedAudio > audioTracks.lastIndex) return
selectedAudio = index
if (index == 0) {
@ -531,53 +375,6 @@ class PlayerActivity :
?: MPVLib.command(arrayOf("audio-add", audioTracks[index].url, "select", audioTracks[index].url))
}
private fun pauseForDialog(): StateRestoreCallback {
val wasPlayerPaused = player.paused ?: true // default to not changing state
player.paused = true
return {
if (!wasPlayerPaused) {
player.paused = false
}
}
}
private fun pickSpeed() {
// TODO: replace this with SliderPickerDialog
val picker = SpeedPickerDialog()
val restore = pauseForDialog()
speedPickerDialog(picker, R.string.title_speed_dialog) {
updateSpeedButton()
restore()
}
}
private fun speedPickerDialog(
picker: PickerDialog,
@StringRes titleRes: Int,
restoreState: StateRestoreCallback,
) {
val dialog = with(AlertDialog.Builder(this)) {
setTitle(titleRes)
setView(picker.buildView(layoutInflater))
setPositiveButton(R.string.dialog_ok) { _, _ ->
picker.number?.let {
if (picker.isInteger()) {
MPVLib.setPropertyInt("speed", it.toInt())
} else {
MPVLib.setPropertyDouble("speed", it)
}
}
}
setNegativeButton(R.string.dialog_cancel) { dialog, _ -> dialog.cancel() }
setOnDismissListener { restoreState() }
create()
}
picker.number = MPVLib.getPropertyDouble("speed")
dialog.show()
}
private fun setViewMode() {
when (playerViewMode) {
2 -> {
@ -610,67 +407,35 @@ class PlayerActivity :
}
}
fun updatePlaybackPos(position: Int) {
binding.playbackPositionTxt.text = Utils.prettyTime(position)
if (!userIsOperatingSeekbar) {
binding.playbackSeekbar.progress = position
}
updateDecoderButton()
updateSpeedButton()
}
private fun updatePlaybackDuration(duration: Int) {
binding.playbackDurationTxt.text = Utils.prettyTime(duration)
if (!userIsOperatingSeekbar) {
binding.playbackSeekbar.max = duration
}
}
private fun updateDecoderButton() {
if (binding.cycleDecoderBtn.visibility != View.VISIBLE) {
return
}
binding.cycleDecoderBtn.text = if (player.hwdecActive) "HW" else "SW"
}
private fun updateSpeedButton() {
binding.cycleSpeedBtn.text = getString(R.string.ui_speed, player.playbackSpeed)
}
@Suppress("UNUSED_PARAMETER")
fun playPause(view: View) {
player.cyclePause()
when {
player.paused!! -> animationHandler.removeCallbacks(controlsViewRunnable)
binding.controlsView.isVisible -> showGestureView("controls")
}
binding.playerControls.playPause()
}
val playPauseRunnable = Runnable {
private val doubleTapPlayPauseRunnable = Runnable {
AnimationUtils.loadAnimation(this, R.anim.fade_out_medium).also { fadeAnimation ->
findViewById<ImageView>(R.id.playPauseView).startAnimation(fadeAnimation)
binding.playPauseView.startAnimation(fadeAnimation)
binding.playPauseView.visibility = View.GONE
}
}
fun doubleTapPlayPause() {
animationHandler.removeCallbacks(playPauseRunnable)
playPause(binding.playBtn)
animationHandler.removeCallbacks(doubleTapPlayPauseRunnable)
playPause(binding.playerControls.binding.playBtn)
if (!binding.controlsView.isVisible) {
if (!binding.playerControls.binding.controlsView.isVisible) {
when {
player.paused!! -> { binding.playPauseView.setImageResource(R.drawable.ic_pause_80dp) }
!player.paused!! -> { binding.playPauseView.setImageResource(R.drawable.ic_play_arrow_80dp) }
}
// if (binding.controlsView.isVisible) { binding.playPauseView.visibility = View.GONE; binding.playPauseView.setBackgroundColor(0x00000000) } else { binding.playPauseView.visibility = View.VISIBLE; binding.playPauseView.setBackgroundColor(0x70000000) }
AnimationUtils.loadAnimation(this, R.anim.fade_in_medium).also { fadeAnimation ->
findViewById<ImageView>(R.id.playPauseView).startAnimation(fadeAnimation)
binding.playPauseView.startAnimation(fadeAnimation)
binding.playPauseView.visibility = View.VISIBLE
}
animationHandler.postDelayed(playPauseRunnable, 500L)
animationHandler.postDelayed(doubleTapPlayPauseRunnable, 500L)
} else binding.playPauseView.visibility = View.GONE
}
@ -740,7 +505,7 @@ class PlayerActivity :
val newDiff = newPos - initialSeek
// seek faster than assigning to timePos but less precise
MPVLib.command(arrayOf("seek", newPos.toString(), "absolute+keyframes"))
updatePlaybackPos(newPos)
binding.playerControls.updatePlaybackPos(newPos)
val diffText = Utils.prettyTime(newDiff, true)
binding.seekText.text = getString(R.string.ui_seek_distance, Utils.prettyTime(newPos), diffText)
@ -828,13 +593,13 @@ class PlayerActivity :
fun switchDecoder(view: View) {
player.cycleHwdec()
preferences.getPlayerViewMode()
updateDecoderButton()
binding.playerControls.updateDecoderButton()
}
@Suppress("UNUSED_PARAMETER")
fun cycleSpeed(view: View) {
player.cycleSpeed()
updateSpeedButton()
binding.playerControls.updateSpeedButton()
}
@Suppress("UNUSED_PARAMETER")
@ -846,16 +611,16 @@ class PlayerActivity :
// forces update of entire UI, used when resuming the activity
val paused = player.paused ?: return
updatePlaybackStatus(paused)
player.timePos?.let { updatePlaybackPos(it) }
player.duration?.let { updatePlaybackDuration(it) }
player.timePos?.let { binding.playerControls.updatePlaybackPos(it) }
player.duration?.let { binding.playerControls.updatePlaybackDuration(it) }
updatePlaylistButtons()
updateEpisodeText()
player.loadTracks()
}
private fun updateEpisodeText() {
binding.titleMainTxt.text = presenter.anime?.title
binding.titleSecondaryTxt.text = presenter.currentEpisode?.name
binding.playerControls.binding.titleMainTxt.text = presenter.anime?.title
binding.playerControls.binding.titleSecondaryTxt.text = presenter.currentEpisode?.name
}
private fun updatePlaylistButtons() {
@ -864,13 +629,13 @@ class PlayerActivity :
val g = ContextCompat.getColor(this, R.color.tint_disabled)
val w = ContextCompat.getColor(this, R.color.tint_normal)
binding.prevBtn.imageTintList = ColorStateList.valueOf(if (plPos == 0) g else w)
binding.nextBtn.imageTintList = ColorStateList.valueOf(if (plPos == plCount - 1) g else w)
binding.playerControls.binding.prevBtn.imageTintList = ColorStateList.valueOf(if (plPos == 0) g else w)
binding.playerControls.binding.nextBtn.imageTintList = ColorStateList.valueOf(if (plPos == plCount - 1) g else w)
}
private fun updatePlaybackStatus(paused: Boolean) {
val r = if (paused) R.drawable.ic_play_arrow_80dp else R.drawable.ic_pause_80dp
binding.playBtn.setImageResource(r)
binding.playerControls.binding.playBtn.setImageResource(r)
if (paused) {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
@ -912,14 +677,14 @@ class PlayerActivity :
@RequiresApi(Build.VERSION_CODES.O)
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
isInPipMode = isInPictureInPictureMode
hideControls(!isInPictureInPictureMode)
binding.playerControls.hideControls(!isInPictureInPictureMode)
if (isInPictureInPictureMode) binding.loadingIndicator.indicatorSize = binding.loadingIndicator.indicatorSize / 2
else binding.loadingIndicator.indicatorSize = binding.loadingIndicator.indicatorSize * 2
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
// On Android TV it is required to hide controller in this PIP change callback
hideControls(true)
binding.playerControls.hideControls(true)
mReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null || ACTION_MEDIA_CONTROL != intent.action) {
@ -951,14 +716,14 @@ class PlayerActivity :
unregisterReceiver(mReceiver)
mReceiver = null
}
hideControls(false)
binding.playerControls.hideControls(false)
}
}
@Suppress("DEPRECATION")
private fun startPiP() {
internal fun startPiP() {
if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
hideControls(true)
binding.playerControls.hideControls(true)
player.paused?.let { updatePictureInPictureActions(!it) }
?.let { this.enterPictureInPictureMode(it) }
}
@ -1191,8 +956,8 @@ class PlayerActivity :
private fun eventPropertyUi(property: String, value: Long) {
when (property) {
"time-pos" -> updatePlaybackPos(value.toInt())
"duration" -> updatePlaybackDuration(value.toInt())
"time-pos" -> binding.playerControls.updatePlaybackPos(value.toInt())
"duration" -> binding.playerControls.updatePlaybackDuration(value.toInt())
}
}
@ -1224,3 +989,14 @@ class PlayerActivity :
}
}
}
private const val ACTION_MEDIA_CONTROL = "media_control"
private const val EXTRA_CONTROL_TYPE = "control_type"
private const val REQUEST_PLAY = 1
private const val REQUEST_PAUSE = 2
private const val CONTROL_TYPE_PLAY = 1
private const val CONTROL_TYPE_PAUSE = 2
private const val REQUEST_PREVIOUS = 3
private const val REQUEST_NEXT = 4
private const val CONTROL_TYPE_PREVIOUS = 3
private const val CONTROL_TYPE_NEXT = 4

View file

@ -0,0 +1,274 @@
package eu.kanade.tachiyomi.ui.player
import android.content.Context
import android.content.ContextWrapper
import android.content.pm.PackageManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.animation.AnimationUtils
import android.widget.LinearLayout
import android.widget.SeekBar
import androidx.annotation.StringRes
import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.PlayerControlsBinding
import `is`.xyz.mpv.MPVLib
import `is`.xyz.mpv.PickerDialog
import `is`.xyz.mpv.SpeedPickerDialog
import `is`.xyz.mpv.StateRestoreCallback
import `is`.xyz.mpv.Utils
class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs) {
internal val binding: PlayerControlsBinding =
PlayerControlsBinding.inflate(LayoutInflater.from(context), this, false)
val activity: PlayerActivity = context.getActivity()!!
private var userIsOperatingSeekbar = false
private val seekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (!fromUser) {
return
}
activity.player.timePos = progress
updatePlaybackPos(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
userIsOperatingSeekbar = true
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
userIsOperatingSeekbar = false
}
}
private tailrec fun Context.getActivity(): PlayerActivity? = this as? PlayerActivity
?: (this as? ContextWrapper)?.baseContext?.getActivity()
init {
addView(binding.root)
}
override fun onViewAdded(child: View?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
binding.pipBtn.isVisible = context.packageManager.hasSystemFeature(
PackageManager.FEATURE_PICTURE_IN_PICTURE,
) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
}
binding.backArrowBtn.setOnClickListener { activity.finishAndRemoveTask() }
binding.pipBtn.setOnClickListener { activity.startPiP() }
// Lock and Unlock controls
binding.lockBtn.setOnClickListener { activity.isLocked = true; toggleControls() }
binding.unlockBtn.setOnClickListener { activity.isLocked = false; toggleControls() }
// Cycle, Long click controls
binding.cycleAudioBtn.setOnLongClickListener { pickAudio(); true }
binding.cycleSpeedBtn.setOnLongClickListener { pickSpeed(); true }
binding.cycleSubsBtn.setOnLongClickListener { pickSub(); true }
binding.playbackSeekbar.setOnSeekBarChangeListener(seekBarChangeListener)
binding.nextBtn.setOnClickListener { activity.switchEpisode(false) }
binding.prevBtn.setOnClickListener { activity.switchEpisode(true) }
}
private val animationHandler = Handler(Looper.getMainLooper())
// Fade out Player controls
private val controlsViewRunnable = Runnable {
if (activity.isLocked) {
fadeOutView(binding.lockedView)
} else {
fadeOutView(binding.controlsView)
}
}
internal fun toggleControls() {
if (activity.isLocked) {
// Hide controls
binding.controlsView.isVisible = false
if (!binding.lockedView.isVisible && !activity.player.paused!!) {
showAndFadeControls()
} else if (!binding.lockedView.isVisible && activity.player.paused!!) {
fadeInView(binding.lockedView)
} else {
fadeOutView(binding.lockedView)
}
} else {
if (!binding.controlsView.isVisible && !activity.player.paused!!) {
showAndFadeControls()
} else if (!binding.controlsView.isVisible && activity.player.paused!!) {
fadeInView(binding.controlsView)
} else {
fadeOutView(binding.controlsView)
}
binding.lockedView.isVisible = false
}
}
internal fun hideControls(hide: Boolean) {
binding.controlsView.isVisible = !hide
}
internal fun updatePlaybackPos(position: Int) {
binding.playbackPositionTxt.text = Utils.prettyTime(position)
if (!userIsOperatingSeekbar) {
binding.playbackSeekbar.progress = position
}
updateDecoderButton()
updateSpeedButton()
}
internal fun updatePlaybackDuration(duration: Int) {
binding.playbackDurationTxt.text = Utils.prettyTime(duration)
if (!userIsOperatingSeekbar) {
binding.playbackSeekbar.max = duration
}
}
internal fun updateDecoderButton() {
if (binding.cycleDecoderBtn.visibility != View.VISIBLE) {
return
}
binding.cycleDecoderBtn.text = if (activity.player.hwdecActive) "HW" else "SW"
}
internal fun updateSpeedButton() {
binding.cycleSpeedBtn.text = context.getString(R.string.ui_speed, activity.player.playbackSpeed)
}
internal fun showAndFadeControls() {
val itemView = if (!activity.isLocked) binding.controlsView else binding.lockedView
animationHandler.removeCallbacks(controlsViewRunnable)
itemView.visibility = View.VISIBLE
animationHandler.postDelayed(controlsViewRunnable, 3500L)
}
private fun fadeOutView(view: View) {
animationHandler.removeCallbacks(controlsViewRunnable)
AnimationUtils.loadAnimation(context, R.anim.fade_out_medium).also { fadeAnimation ->
view.startAnimation(fadeAnimation)
view.visibility = View.GONE
}
}
private fun fadeInView(view: View) {
animationHandler.removeCallbacks(controlsViewRunnable)
AnimationUtils.loadAnimation(context, R.anim.fade_in_short).also { fadeAnimation ->
view.startAnimation(fadeAnimation)
view.visibility = View.VISIBLE
}
}
private fun pauseForDialog(): StateRestoreCallback {
val wasPlayerPaused = activity.player.paused ?: true // default to not changing state
activity.player.paused = true
return {
if (!wasPlayerPaused) {
activity.player.paused = false
}
}
}
internal fun playPause() {
when {
activity.player.paused!! -> animationHandler.removeCallbacks(controlsViewRunnable)
binding.controlsView.isVisible -> {
showAndFadeControls()
}
}
}
private fun pickAudio() {
val restore = pauseForDialog()
with(MaterialAlertDialogBuilder(context)) {
setSingleChoiceItems(
activity.audioTracks.map { it.lang }.toTypedArray(),
activity.selectedAudio,
) { dialog, item ->
if (item == activity.selectedAudio) return@setSingleChoiceItems
if (item == 0) {
activity.selectedAudio = 0
activity.player.aid = -1
return@setSingleChoiceItems
}
activity.setAudio(item)
dialog.dismiss()
}
setOnDismissListener { restore() }
create().show()
}
}
private fun pickSub() {
val restore = pauseForDialog()
with(MaterialAlertDialogBuilder(context)) {
setSingleChoiceItems(
activity.subTracks.map { it.lang }.toTypedArray(),
activity.selectedSub,
) { dialog, item ->
if (item == 0) {
activity.selectedSub = 0
activity.player.sid = -1
return@setSingleChoiceItems
}
activity.setSub(item)
dialog.dismiss()
}
setOnDismissListener { restore() }
create().show()
}
}
private fun pickSpeed() {
// TODO: replace this with SliderPickerDialog
val picker = SpeedPickerDialog()
val restore = pauseForDialog()
speedPickerDialog(picker, R.string.title_speed_dialog) {
updateSpeedButton()
restore()
}
}
private fun speedPickerDialog(
picker: PickerDialog,
@StringRes titleRes: Int,
restoreState: StateRestoreCallback,
) {
val dialog = with(MaterialAlertDialogBuilder(context)) {
setTitle(titleRes)
setView(picker.buildView(LayoutInflater.from(context)))
setPositiveButton(R.string.dialog_ok) { _, _ ->
picker.number?.let {
if (picker.isInteger()) {
MPVLib.setPropertyInt("speed", it.toInt())
} else {
MPVLib.setPropertyDouble("speed", it)
}
}
}
setNegativeButton(R.string.dialog_cancel) { dialog, _ -> dialog.cancel() }
setOnDismissListener { restoreState() }
create()
}
picker.number = MPVLib.getPropertyDouble("speed")
dialog.show()
}
}

View file

@ -48,334 +48,10 @@
app:tint="?attr/colorAccent" />
</LinearLayout>
<LinearLayout
android:id="@+id/lockedView"
<eu.kanade.tachiyomi.ui.player.PlayerControlsView
android:id="@+id/player_controls"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:visibility="gone">
<ImageButton
android:id="@+id/unlockBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Unlock player"
android:src="@drawable/ic_lock_open_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
app:tint="?attr/colorOnPrimarySurface" />
</LinearLayout>
<!-- Double layout for consistency in code -->
<LinearLayout
android:id="@+id/controlsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#70000000">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="UselessParent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/controls_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<ImageButton
android:id="@+id/backArrowBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Go back"
android:src="@drawable/ic_arrow_back_24dp"
android:layout_marginHorizontal="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
app:tint="?attr/colorOnPrimarySurface"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/fullTitleTxt"
app:layout_constraintTop_toTopOf="parent"/>
<LinearLayout
android:id="@+id/fullTitleTxt"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintLeft_toRightOf="@id/backArrowBtn"
app:layout_constraintRight_toLeftOf="@id/cycleDecoderBtn"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/titleMainTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnPrimarySurface"
android:text=""
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/titleSecondaryTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnPrimarySurface"
android:alpha = "0.5"
android:textSize="12sp"
android:text="" />
</LinearLayout>
<ImageButton
android:id="@+id/settingsBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Settings"
android:background="?attr/selectableItemBackground"
android:onClick="openSettings"
android:layout_marginRight="10dp"
android:src="@drawable/ic_settings_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:tint="?attr/colorOnPrimarySurface" />
<ImageButton
android:id="@+id/cycleSubsBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Subtitles"
android:onClick="cycleSub"
android:src="@drawable/ic_subtitles_black_24dp"
android:background="?attr/selectableItemBackground"
app:tint="?attr/colorOnPrimarySurface"
app:layout_constraintRight_toRightOf="@id/cycleAudioBtn"
app:layout_constraintRight_toLeftOf="@id/settingsBtn"
app:layout_constraintTop_toTopOf="parent"/>
<ImageButton
android:id="@+id/cycleAudioBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Audio"
android:background="?attr/selectableItemBackground"
android:onClick="cycleAudio"
android:src="@drawable/ic_audiotrack_black_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="@id/cycleDecoderBtn"
app:layout_constraintRight_toLeftOf="@id/cycleSubsBtn"
app:tint="?attr/colorOnPrimarySurface" />
<Button
android:id="@+id/cycleDecoderBtn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_toLeftOf="@id/cycleAudioBtn"
android:background="?attr/selectableItemBackground"
android:onClick="switchDecoder"
android:text=".."
android:textColor="?attr/colorOnPrimarySurface"
app:layout_constraintRight_toLeftOf="@id/cycleAudioBtn"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<RelativeLayout
android:id="@+id/controls_bottom"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:visibility="visible">
<ImageButton
android:id="@+id/play_btn"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Play/Pause"
android:onClick="playPause"
android:textColor="@android:color/white"
android:visibility="gone"
app:tint="?attr/colorOnPrimarySurface"
tools:src="@drawable/ic_play_arrow_80dp"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/control_bar"
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:orientation="vertical"
tools:visibility="visible">
<LinearLayout
android:id="@+id/controls_title_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="visible">
<!-- These two are only used for audio -->
<TextView
android:id="@+id/titleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="-"
android:textColor="@color/tint_normal"
android:textSize="24sp"
android:visibility="gone" />
<TextView
android:id="@+id/minorTitleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="-"
android:textColor="@color/tint_normal"
android:textSize="14sp"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageButton
android:id="@+id/lockBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginLeft="10dp"
android:contentDescription="Lock player"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_lock_24dp"
app:tint="?attr/colorOnPrimarySurface" />
<Button
android:id="@+id/cycleSpeedBtn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:onClick="cycleSpeed"
android:text=".."
android:textColor="?attr/colorOnPrimarySurface" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:gravity="right"
android:orientation="horizontal">
<Button
android:id="@+id/controls_skip_intro_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:onClick="skipIntro"
android:text="@string/player_controls_skip_intro_text"
android:textColor="?attr/colorOnPrimarySurface" />
<ImageButton
android:id="@+id/cycleViewModeBtn"
android:layout_width="48dp"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:contentDescription="Cycle view modes"
android:onClick="cycleViewMode"
android:src="@drawable/ic_fullscreen_black_24dp"
app:tint="?attr/colorOnPrimarySurface" />
<ImageButton
android:id="@+id/pipBtn"
android:layout_width="48dp"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_player_pip"
android:src="@drawable/ic_picture_in_picture_24dp"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/controls_seekbar_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="100">
<ImageButton
android:id="@+id/prevBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginLeft="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_previous_episode"
android:padding="@dimen/screen_edge_margin"
app:srcCompat="@drawable/ic_skip_previous_24dp"
app:tint="?attr/colorOnPrimarySurface" />
<TextView
android:id="@+id/playbackPositionTxt"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="10"
android:gravity="center"
android:text="0:00"
android:textColor="@android:color/white" />
<SeekBar
android:id="@+id/playbackSeekbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="80"
android:progressBackgroundTint="@color/tint_seekbar_bg" />
<TextView
android:id="@+id/playbackDurationTxt"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="10"
android:gravity="center"
android:text="0:00"
android:textColor="@android:color/white" />
<ImageButton
android:id="@+id/nextBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginRight="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_next_episode"
android:padding="@dimen/screen_edge_margin"
app:srcCompat="@drawable/ic_skip_next_24dp"
app:tint="?attr/colorOnPrimarySurface" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
</LinearLayout>
android:layout_height="match_parent"/>
<LinearLayout
android:id="@+id/volumeView"
@ -491,7 +167,7 @@
android:layout_centerInParent="true"
android:contentDescription="Play/Pause"
android:textColor="@android:color/white"
android:background="#70000000"
android:background="#00000000"
android:visibility="gone"
app:tint="?attr/colorOnPrimarySurface"
tools:src="@drawable/ic_play_arrow_80dp" />

View file

@ -0,0 +1,339 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="RtlHardcoded,HardcodedText" >
<LinearLayout
android:id="@+id/lockedView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:visibility="gone">
<ImageButton
android:id="@+id/unlockBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Unlock player"
android:src="@drawable/ic_lock_open_24dp"
android:background="?attr/selectableItemBackgroundBorderless"
app:tint="?attr/colorOnPrimarySurface" />
</LinearLayout>
<!-- Double layout for consistency in code -->
<LinearLayout
android:id="@+id/controlsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#70000000">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="UselessParent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/controls_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<ImageButton
android:id="@+id/backArrowBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Go back"
android:src="@drawable/ic_arrow_back_24dp"
android:layout_marginHorizontal="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
app:tint="?attr/colorOnPrimarySurface"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/fullTitleTxt"
app:layout_constraintTop_toTopOf="parent"/>
<LinearLayout
android:id="@+id/fullTitleTxt"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintLeft_toRightOf="@id/backArrowBtn"
app:layout_constraintRight_toLeftOf="@id/cycleDecoderBtn"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/titleMainTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnPrimarySurface"
android:text=""
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/titleSecondaryTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnPrimarySurface"
android:alpha = "0.5"
android:textSize="12sp"
android:text="" />
</LinearLayout>
<ImageButton
android:id="@+id/settingsBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Settings"
android:background="?attr/selectableItemBackground"
android:onClick="openSettings"
android:layout_marginRight="10dp"
android:src="@drawable/ic_settings_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:tint="?attr/colorOnPrimarySurface" />
<ImageButton
android:id="@+id/cycleSubsBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Subtitles"
android:onClick="cycleSub"
android:src="@drawable/ic_subtitles_black_24dp"
android:background="?attr/selectableItemBackground"
app:tint="?attr/colorOnPrimarySurface"
app:layout_constraintRight_toRightOf="@id/cycleAudioBtn"
app:layout_constraintRight_toLeftOf="@id/settingsBtn"
app:layout_constraintTop_toTopOf="parent"/>
<ImageButton
android:id="@+id/cycleAudioBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="Audio"
android:background="?attr/selectableItemBackground"
android:onClick="cycleAudio"
android:src="@drawable/ic_audiotrack_black_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="@id/cycleDecoderBtn"
app:layout_constraintRight_toLeftOf="@id/cycleSubsBtn"
app:tint="?attr/colorOnPrimarySurface" />
<Button
android:id="@+id/cycleDecoderBtn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_toLeftOf="@id/cycleAudioBtn"
android:background="?attr/selectableItemBackground"
android:onClick="switchDecoder"
android:text=".."
android:textColor="?attr/colorOnPrimarySurface"
app:layout_constraintRight_toLeftOf="@id/cycleAudioBtn"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<RelativeLayout
android:id="@+id/controls_bottom"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:visibility="visible">
<ImageButton
android:id="@+id/play_btn"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Play/Pause"
android:onClick="playPause"
android:textColor="@android:color/white"
android:visibility="gone"
app:tint="?attr/colorOnPrimarySurface"
tools:src="@drawable/ic_play_arrow_80dp"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/control_bar"
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:orientation="vertical"
tools:visibility="visible">
<LinearLayout
android:id="@+id/controls_title_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="visible">
<!-- These two are only used for audio -->
<TextView
android:id="@+id/titleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="-"
android:textColor="@color/tint_normal"
android:textSize="24sp"
android:visibility="gone" />
<TextView
android:id="@+id/minorTitleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="-"
android:textColor="@color/tint_normal"
android:textSize="14sp"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageButton
android:id="@+id/lockBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginLeft="10dp"
android:contentDescription="Lock player"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_lock_24dp"
app:tint="?attr/colorOnPrimarySurface" />
<Button
android:id="@+id/cycleSpeedBtn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:onClick="cycleSpeed"
android:text=".."
android:textColor="?attr/colorOnPrimarySurface" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:gravity="right"
android:orientation="horizontal">
<Button
android:id="@+id/controls_skip_intro_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:onClick="skipIntro"
android:text="@string/player_controls_skip_intro_text"
android:textColor="?attr/colorOnPrimarySurface" />
<ImageButton
android:id="@+id/cycleViewModeBtn"
android:layout_width="48dp"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:contentDescription="Cycle view modes"
android:onClick="cycleViewMode"
android:src="@drawable/ic_fullscreen_black_24dp"
app:tint="?attr/colorOnPrimarySurface" />
<ImageButton
android:id="@+id/pipBtn"
android:layout_width="48dp"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/action_player_pip"
android:src="@drawable/ic_picture_in_picture_24dp"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/controls_seekbar_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="100">
<ImageButton
android:id="@+id/prevBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginLeft="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_previous_episode"
android:padding="@dimen/screen_edge_margin"
app:srcCompat="@drawable/ic_skip_previous_24dp"
app:tint="?attr/colorOnPrimarySurface" />
<TextView
android:id="@+id/playbackPositionTxt"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="10"
android:gravity="center"
android:text="0:00"
android:textColor="@android:color/white" />
<SeekBar
android:id="@+id/playbackSeekbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="80"
android:progressBackgroundTint="@color/tint_seekbar_bg" />
<TextView
android:id="@+id/playbackDurationTxt"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="10"
android:gravity="center"
android:text="0:00"
android:textColor="@android:color/white" />
<ImageButton
android:id="@+id/nextBtn"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginRight="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_next_episode"
android:padding="@dimen/screen_edge_margin"
app:srcCompat="@drawable/ic_skip_next_24dp"
app:tint="?attr/colorOnPrimarySurface" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
</LinearLayout>
</RelativeLayout>