feat: pip controls for offline player

This commit is contained in:
Bnyro 2024-04-18 18:05:48 +02:00
parent 0c42c1286c
commit 11d7b687d5
4 changed files with 88 additions and 37 deletions

View File

@ -1,8 +1,7 @@
package com.github.libretube.enums
enum class PlayerEvent {
Pause,
Play,
PlayPause,
Forward,
Rewind,
Next,

View File

@ -1,7 +1,6 @@
package com.github.libretube.helpers
import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
@ -397,30 +396,25 @@ object PlayerHelper {
}
}
fun getIntentAction(context: Context): String {
fun getIntentActionName(context: Context): String {
return context.packageName + "." + ACTION_MEDIA_CONTROL
}
private fun getPendingIntent(activity: Activity, event: PlayerEvent): PendingIntent {
val intent = Intent(getIntentAction(activity))
.setPackage(activity.packageName)
.putExtra(CONTROL_TYPE, event)
return PendingIntentCompat.getBroadcast(activity, event.ordinal, intent, 0, false)!!
}
private fun getRemoteAction(
activity: Activity,
id: Int,
@StringRes title: Int,
event: PlayerEvent
): RemoteActionCompat {
val intent = Intent(getIntentActionName(activity))
.setPackage(activity.packageName)
.putExtra(CONTROL_TYPE, event)
val pendingIntent = PendingIntentCompat.getBroadcast(activity, event.ordinal, intent, 0, false)!!
val text = activity.getString(title)
return RemoteActionCompat(
IconCompat.createWithResource(activity, id),
text,
text,
getPendingIntent(activity, event)
)
val icon = IconCompat.createWithResource(activity, id)
return RemoteActionCompat(icon, text, text, pendingIntent)
}
/**
@ -444,8 +438,8 @@ object PlayerHelper {
val playPauseAction = getRemoteAction(
activity,
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
R.string.pause,
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play
if (isPlaying) R.string.resume else R.string.pause,
PlayerEvent.PlayPause
)
val skipNextAction = getRemoteAction(

View File

@ -1,12 +1,17 @@
package com.github.libretube.ui.activities
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.media.session.PlaybackState
import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
import android.text.format.DateUtils
import android.view.KeyEvent
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
@ -30,7 +35,11 @@ import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
import com.github.libretube.db.DatabaseHolder.Database
import com.github.libretube.db.obj.DownloadChapter
import com.github.libretube.enums.FileType
import com.github.libretube.enums.PlayerEvent
import com.github.libretube.extensions.seekBy
import com.github.libretube.extensions.serializableExtra
import com.github.libretube.extensions.toAndroidUri
import com.github.libretube.extensions.togglePlayPauseState
import com.github.libretube.extensions.updateParameters
import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.WindowHelper
@ -74,6 +83,10 @@ class OfflinePlayerActivity : BaseActivity() {
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
if (PlayerHelper.pipEnabled) {
PictureInPictureCompat.setPictureInPictureParams(this@OfflinePlayerActivity, pipParams)
}
// Start or pause watch position timer
if (isPlaying) {
watchPositionTimer.resume()
@ -97,6 +110,32 @@ class OfflinePlayerActivity : BaseActivity() {
}
}
private val playerActionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.serializableExtra<PlayerEvent>(PlayerHelper.CONTROL_TYPE) ?: return) {
PlayerEvent.PlayPause -> {
player.togglePlayPauseState()
}
PlayerEvent.Forward -> {
player.seekBy(PlayerHelper.seekIncrement)
}
PlayerEvent.Rewind -> {
player.seekBy(-PlayerHelper.seekIncrement)
}
else -> Unit
}
}
}
private val pipParams get() = PictureInPictureParamsCompat.Builder()
.setActions(PlayerHelper.getPiPModeActions(this, player.isPlaying))
.setAutoEnterEnabled(PlayerHelper.pipEnabled && player.isPlaying)
.setAspectRatio(player.videoSize)
.build()
override fun onCreate(savedInstanceState: Bundle?) {
WindowHelper.toggleFullscreen(window, true)
@ -116,6 +155,17 @@ class OfflinePlayerActivity : BaseActivity() {
player.videoSize.width,
player.videoSize.height
)
ContextCompat.registerReceiver(
this,
playerActionReceiver,
IntentFilter(PlayerHelper.getIntentActionName(this)),
ContextCompat.RECEIVER_NOT_EXPORTED
)
if (PlayerHelper.pipEnabled) {
PictureInPictureCompat.setPictureInPictureParams(this, pipParams)
}
}
private fun initializePlayer() {
@ -248,6 +298,10 @@ class OfflinePlayerActivity : BaseActivity() {
override fun onPause() {
playerViewModel.isFullscreen.value = false
super.onPause()
if (PlayerHelper.pauseOnQuit) {
player.pause()
}
}
override fun onDestroy() {
@ -257,22 +311,30 @@ class OfflinePlayerActivity : BaseActivity() {
player.release()
watchPositionTimer.destroy()
unregisterReceiver(playerActionReceiver)
super.onDestroy()
}
override fun onUserLeaveHint() {
if (PlayerHelper.pipEnabled && player.playbackState != PlaybackState.STATE_PAUSED) {
PictureInPictureCompat.enterPictureInPictureMode(
this,
PictureInPictureParamsCompat.Builder()
.setAspectRatio(player.videoSize)
.build()
)
if (PlayerHelper.pipEnabled && player.isPlaying) {
PictureInPictureCompat.enterPictureInPictureMode(this, pipParams)
}
super.onUserLeaveHint()
}
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, configuration: Configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
if (isInPictureInPictureMode) {
playerView.hideController()
playerView.useController = false
} else {
playerView.useController = true
}
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
if (binding.player.onKeyBoardAction(keyCode)) {
return true

View File

@ -200,15 +200,11 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
/**
* Receiver for all actions in the PiP mode
*/
private val broadcastReceiver = object : BroadcastReceiver() {
private val playerActionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.serializableExtra<PlayerEvent>(PlayerHelper.CONTROL_TYPE) ?: return) {
PlayerEvent.Play -> {
exoPlayer.play()
}
PlayerEvent.Pause -> {
exoPlayer.pause()
PlayerEvent.PlayPause -> {
exoPlayer.togglePlayPauseState()
}
PlayerEvent.Forward -> {
@ -370,8 +366,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
// broadcast receiver for PiP actions
ContextCompat.registerReceiver(
requireContext(),
broadcastReceiver,
IntentFilter(PlayerHelper.getIntentAction(requireContext())),
playerActionReceiver,
IntentFilter(PlayerHelper.getIntentActionName(requireContext())),
ContextCompat.RECEIVER_NOT_EXPORTED
)
@ -833,7 +829,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
runCatching {
// unregister the receiver for player actions
context?.unregisterReceiver(broadcastReceiver)
context?.unregisterReceiver(playerActionReceiver)
}
_binding = null