Skip to content

Commit

Permalink
refactor(pip): Clean up & update code (#1404)
Browse files Browse the repository at this point in the history
  • Loading branch information
quickdesh authored Feb 8, 2024
1 parent 0bd1bb3 commit aec7893
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 111 deletions.
67 changes: 47 additions & 20 deletions app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import com.hippo.unifile.UniFile
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.model.SerializableVideo.Companion.serialize
import eu.kanade.tachiyomi.animesource.model.Track
Expand Down Expand Up @@ -173,8 +174,6 @@ class PlayerActivity : BaseActivity() {
super.onNewIntent(intent)
}

internal val pip = PictureInPictureHandler(this, playerPreferences.enablePip().get())

private val playerObserver = PlayerObserver(this)

private var mReceiver: BroadcastReceiver? = null
Expand Down Expand Up @@ -886,9 +885,6 @@ class PlayerActivity : BaseActivity() {
override fun onResume() {
super.onResume()
refreshUi()
if (pip.supportedAndEnabled && PipState.mode == PipState.ON) {
player.paused?.let { pip.update(!it) }
}
}

override fun onPause() {
Expand All @@ -901,7 +897,7 @@ class PlayerActivity : BaseActivity() {
if (!playerIsDestroyed) {
player.paused = true
}
if (pip.supportedAndEnabled && PipState.mode == PipState.ON && powerManager.isInteractive) {
if (PipState.mode == PipState.ON && powerManager.isInteractive) {
finishAndRemoveTask()
}

Expand Down Expand Up @@ -935,12 +931,11 @@ class PlayerActivity : BaseActivity() {
super.onDestroy()
}

@Suppress("DEPRECATION")
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
if (pip.supportedAndEnabled) {
if (supportedAndEnabled) {
if (player.paused == false && playerPreferences.pipOnExit().get()) {
pip.start()
updatePip(true)
} else {
finishAndRemoveTask()
super.onBackPressed()
Expand All @@ -952,7 +947,12 @@ class PlayerActivity : BaseActivity() {
}

override fun onUserLeaveHint() {
if (player.paused == false && playerPreferences.pipOnExit().get()) pip.start()
if (player.paused == false &&
playerPreferences.pipOnExit().get() &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.S
) {
updatePip(start = true)
}
super.onUserLeaveHint()
}

Expand Down Expand Up @@ -1013,7 +1013,6 @@ class PlayerActivity : BaseActivity() {
}
}
setupGestures()
if (pip.supportedAndEnabled) player.paused?.let { pip.update(!it) }
viewModel.closeDialogSheet()
}
}
Expand Down Expand Up @@ -1431,6 +1430,7 @@ class PlayerActivity : BaseActivity() {
player.timePos?.let { playerControls.updatePlaybackPos(it) }
player.duration?.let { playerControls.updatePlaybackDuration(it) }
updatePlaybackStatus(player.paused ?: return@launchUI)
updatePip(start = false)
playerControls.updateEpisodeText()
playerControls.updatePlaylistButtons()
playerControls.updateSpeedButton()
Expand All @@ -1456,7 +1456,6 @@ class PlayerActivity : BaseActivity() {
}

private fun updatePlaybackStatus(paused: Boolean) {
if (pip.supportedAndEnabled && PipState.mode == PipState.ON) pip.update(!paused)
val r = if (paused) R.drawable.ic_play_arrow_64dp else R.drawable.ic_pause_64dp
playerControls.binding.playBtn.setImageResource(r)

Expand All @@ -1467,7 +1466,35 @@ class PlayerActivity : BaseActivity() {
}
}

@Suppress("DEPRECATION")
// TODO: Move into function once compose is implemented
val supportedAndEnabled = Injekt.get<BasePreferences>().deviceHasPip() && playerPreferences.enablePip().get()
internal fun updatePip(start: Boolean) {
val anime = viewModel.currentAnime ?: return
val episode = viewModel.currentEpisode ?: return
val paused = player.paused ?: return
val videoAspect = player.videoAspect ?: return
if (supportedAndEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PictureInPictureHandler().update(
context = this,
title = anime.title,
subtitle = episode.name,
paused = paused,
replaceWithPrevious = playerPreferences.pipReplaceWithPrevious().get(),
pipOnExit = playerPreferences.pipOnExit().get() && !paused,
videoAspect = videoAspect * 10000,
playlistCount = viewModel.getCurrentEpisodeIndex(),
playlistPosition = viewModel.currentPlaylist.size,
).let {
setPictureInPictureParams(it)
if (PipState.mode == PipState.OFF && start) {
PipState.mode = PipState.STARTED
playerControls.hideControls(hide = true)
enterPictureInPictureMode(it)
}
}
}
}

@Deprecated("Deprecated in Java")
@RequiresApi(Build.VERSION_CODES.O)
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
Expand Down Expand Up @@ -1543,13 +1570,12 @@ class PlayerActivity : BaseActivity() {
if (viewModel.state.value.isLoadingEpisode) {
viewModel.currentEpisode?.let { episode ->
val preservePos = playerPreferences.preserveWatchingPosition().get()
val resumePosition = if (position != null) {
position
} else if ((episode.seen && !preservePos) || fromStart) {
0L
} else {
episode.last_second_seen
}
val resumePosition = position
?: if ((episode.seen && !preservePos) || fromStart) {
0L
} else {
episode.last_second_seen
}
MPVLib.command(arrayOf("set", "start", "${resumePosition / 1000F}"))
playerControls.updatePlaybackDuration(resumePosition.toInt() / 1000)
}
Expand Down Expand Up @@ -1881,6 +1907,7 @@ class PlayerActivity : BaseActivity() {
setAudioFocus(value)
updatePlaybackStatus(value)
updatePlaybackState(pause = true)
refreshUi()
}
}
"eof-reached" -> endFile(value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,124 @@ package eu.kanade.tachiyomi.ui.player.viewer
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Icon
import android.os.Build
import android.util.Rational
import androidx.annotation.RequiresApi
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.player.PlayerActivity
import tachiyomi.core.i18n.stringResource
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get

class PictureInPictureHandler(
private val activity: PlayerActivity,
private val pipEnabled: Boolean,
) {
class PictureInPictureHandler {

internal val supportedAndEnabled: Boolean
get() = Injekt.get<BasePreferences>().deviceHasPip() && pipEnabled
@RequiresApi(Build.VERSION_CODES.O)
fun update(
context: Context,
title: String,
subtitle: String,
paused: Boolean,
replaceWithPrevious: Boolean,
pipOnExit: Boolean,
videoAspect: Double,
playlistCount: Int,
playlistPosition: Int,
): PictureInPictureParams {
val aspectRatio = videoAspect.let { aspect ->
when {
aspect >= 23900 -> 23899
aspect <= 4184 -> 4185
else -> aspect.toInt()
}
}

val pictureInPictureParams = PictureInPictureParams.Builder()
.setActions(pipActions(context, paused, replaceWithPrevious, playlistCount, playlistPosition))
.setAspectRatio(Rational(aspectRatio, 10000))

internal fun start() {
if (PipState.mode == PipState.ON) return
if (supportedAndEnabled) {
PipState.mode = PipState.STARTED
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
pictureInPictureParams.setAutoEnterEnabled(pipOnExit).setSeamlessResizeEnabled(false)
}

activity.playerControls.hideControls(hide = true)
activity.player.paused
?.let { update(!it) }
?.let { activity.enterPictureInPictureMode(it) }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pictureInPictureParams.setTitle(title).setSubtitle(subtitle)
}

return pictureInPictureParams.build()
}

@RequiresApi(Build.VERSION_CODES.O)
private fun pipActions(
context: Context,
paused: Boolean,
replaceWithPrevious: Boolean,
playlistCount: Int,
playlistPosition: Int,
): ArrayList<RemoteAction> {
return arrayListOf(
if (replaceWithPrevious) {
createRemoteAction(
context,
R.drawable.ic_skip_previous_24dp,
MR.strings.action_previous_episode,
PIP_PREVIOUS,
PIP_PREVIOUS,
playlistPosition != 0,
)
} else {
createRemoteAction(
context,
R.drawable.ic_forward_10_24dp,
MR.strings.pref_skip_10,
PIP_SKIP,
PIP_SKIP,
)
},
if (paused) {
createRemoteAction(
context,
R.drawable.ic_play_arrow_24dp,
MR.strings.action_play,
PIP_PLAY,
PIP_PLAY,
)
} else {
createRemoteAction(
context,
R.drawable.ic_pause_24dp,
MR.strings.action_pause,
PIP_PAUSE,
PIP_PAUSE,
)
},
createRemoteAction(
context,
R.drawable.ic_skip_next_24dp,
MR.strings.action_next_episode,
PIP_NEXT,
PIP_NEXT,
playlistPosition != playlistCount - 1,
),
)
}

@RequiresApi(Build.VERSION_CODES.O)
private fun createRemoteAction(
context: Context,
iconResId: Int,
titleRes: StringResource,
requestCode: Int,
controlType: Int,
isEnabled: Boolean = true,
): RemoteAction {
val action = RemoteAction(
Icon.createWithResource(activity, iconResId),
activity.stringResource(titleRes),
activity.stringResource(titleRes),
Icon.createWithResource(context, iconResId),
context.stringResource(titleRes),
context.stringResource(titleRes),
PendingIntent.getBroadcast(
activity,
context,
requestCode,
Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_CONTROL_TYPE, controlType),
PendingIntent.FLAG_IMMUTABLE,
Expand All @@ -59,75 +129,10 @@ class PictureInPictureHandler(
action.isEnabled = isEnabled
return action
}

@RequiresApi(Build.VERSION_CODES.O)
fun update(
playing: Boolean,
): PictureInPictureParams {
var aspect: Int? = null
if (activity.player.videoAspect != null) {
aspect = if (activity.player.videoAspect!!.times(10000) >= 23900) {
23899
} else if (activity.player.videoAspect!!.times(10000) <= 4184) {
4185
} else {
activity.player.videoAspect!!.times(10000).toInt()
}
}
val plCount = activity.viewModel.currentPlaylist.size
val plPos = activity.viewModel.getCurrentEpisodeIndex()
val mPictureInPictureParams = PictureInPictureParams.Builder()
// Set action items for the picture-in-picture mode. These are the only custom controls
// available during the picture-in-picture mode.
.setActions(
arrayListOf(
if (activity.playerPreferences.pipReplaceWithPrevious().get()) {
createRemoteAction(
R.drawable.ic_skip_previous_24dp,
MR.strings.action_previous_episode,
PIP_PREVIOUS,
PIP_PREVIOUS,
plPos != 0,
)
} else {
createRemoteAction(
R.drawable.ic_forward_10_24dp,
MR.strings.pref_skip_10,
PIP_SKIP,
PIP_SKIP,
)
},
if (playing) {
createRemoteAction(
R.drawable.ic_pause_24dp,
MR.strings.action_pause,
PIP_PAUSE,
PIP_PAUSE,
)
} else {
createRemoteAction(
R.drawable.ic_play_arrow_24dp,
MR.strings.action_play,
PIP_PLAY,
PIP_PLAY,
)
},
createRemoteAction(
R.drawable.ic_skip_next_24dp,
MR.strings.action_next_episode,
PIP_NEXT,
PIP_NEXT,
plPos != plCount - 1,
),
),
)
.setAspectRatio(aspect?.let { Rational(it, 10000) })
.build()
activity.setPictureInPictureParams(mPictureInPictureParams)
return mPictureInPictureParams
}
}

// TODO: https://developer.android.com/develop/ui/views/picture-in-picture#setautoenterenabled

internal const val PIP_PLAY = 1
internal const val PIP_PAUSE = 2
internal const val PIP_PREVIOUS = 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr
binding.playBtn.setOnClickListener { playPause() }
binding.nextBtn.setOnClickListener { switchEpisode(previous = false) }

binding.pipBtn.setOnClickListener { activity.pip.start() }
binding.pipBtn.setOnClickListener { activity.updatePip(start = true) }

binding.pipBtn.isVisible = !playerPreferences.pipOnExit().get() && activity.pip.supportedAndEnabled
binding.pipBtn.isVisible = !playerPreferences.pipOnExit().get() && activity.supportedAndEnabled

binding.controlsSkipIntroBtn.setOnLongClickListener {
activity.viewModel.showSkipIntroLength()
Expand Down

0 comments on commit aec7893

Please sign in to comment.