mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-29 09:39:03 +03:00
attempt to declutter the player activity and layout
This commit is contained in:
parent
0890a21eb2
commit
6c7e012e40
4 changed files with 668 additions and 603 deletions
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
339
app/src/main/res/layout/player_controls.xml
Normal file
339
app/src/main/res/layout/player_controls.xml
Normal 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>
|
Loading…
Reference in a new issue