Merge pull request #5923 from Bnyro/master

feat: pip controls for offline player
This commit is contained in:
Bnyro 2024-04-18 18:06:15 +02:00 committed by GitHub
commit b9a761de68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 88 additions and 37 deletions

View File

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

View File

@ -1,7 +1,6 @@
package com.github.libretube.helpers package com.github.libretube.helpers
import android.app.Activity import android.app.Activity
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo 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 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( private fun getRemoteAction(
activity: Activity, activity: Activity,
id: Int, id: Int,
@StringRes title: Int, @StringRes title: Int,
event: PlayerEvent event: PlayerEvent
): RemoteActionCompat { ): 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) val text = activity.getString(title)
return RemoteActionCompat( val icon = IconCompat.createWithResource(activity, id)
IconCompat.createWithResource(activity, id),
text, return RemoteActionCompat(icon, text, text, pendingIntent)
text,
getPendingIntent(activity, event)
)
} }
/** /**
@ -444,8 +438,8 @@ object PlayerHelper {
val playPauseAction = getRemoteAction( val playPauseAction = getRemoteAction(
activity, activity,
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play, if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
R.string.pause, if (isPlaying) R.string.resume else R.string.pause,
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play PlayerEvent.PlayPause
) )
val skipNextAction = getRemoteAction( val skipNextAction = getRemoteAction(

View File

@ -1,12 +1,17 @@
package com.github.libretube.ui.activities 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.content.pm.ActivityInfo
import android.media.session.PlaybackState import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.KeyEvent import android.view.KeyEvent
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope 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.DatabaseHolder.Database
import com.github.libretube.db.obj.DownloadChapter import com.github.libretube.db.obj.DownloadChapter
import com.github.libretube.enums.FileType 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.toAndroidUri
import com.github.libretube.extensions.togglePlayPauseState
import com.github.libretube.extensions.updateParameters import com.github.libretube.extensions.updateParameters
import com.github.libretube.helpers.PlayerHelper import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.WindowHelper import com.github.libretube.helpers.WindowHelper
@ -74,6 +83,10 @@ class OfflinePlayerActivity : BaseActivity() {
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying) super.onIsPlayingChanged(isPlaying)
if (PlayerHelper.pipEnabled) {
PictureInPictureCompat.setPictureInPictureParams(this@OfflinePlayerActivity, pipParams)
}
// Start or pause watch position timer // Start or pause watch position timer
if (isPlaying) { if (isPlaying) {
watchPositionTimer.resume() 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?) { override fun onCreate(savedInstanceState: Bundle?) {
WindowHelper.toggleFullscreen(window, true) WindowHelper.toggleFullscreen(window, true)
@ -116,6 +155,17 @@ class OfflinePlayerActivity : BaseActivity() {
player.videoSize.width, player.videoSize.width,
player.videoSize.height 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() { private fun initializePlayer() {
@ -248,6 +298,10 @@ class OfflinePlayerActivity : BaseActivity() {
override fun onPause() { override fun onPause() {
playerViewModel.isFullscreen.value = false playerViewModel.isFullscreen.value = false
super.onPause() super.onPause()
if (PlayerHelper.pauseOnQuit) {
player.pause()
}
} }
override fun onDestroy() { override fun onDestroy() {
@ -257,22 +311,30 @@ class OfflinePlayerActivity : BaseActivity() {
player.release() player.release()
watchPositionTimer.destroy() watchPositionTimer.destroy()
unregisterReceiver(playerActionReceiver)
super.onDestroy() super.onDestroy()
} }
override fun onUserLeaveHint() { override fun onUserLeaveHint() {
if (PlayerHelper.pipEnabled && player.playbackState != PlaybackState.STATE_PAUSED) { if (PlayerHelper.pipEnabled && player.isPlaying) {
PictureInPictureCompat.enterPictureInPictureMode( PictureInPictureCompat.enterPictureInPictureMode(this, pipParams)
this,
PictureInPictureParamsCompat.Builder()
.setAspectRatio(player.videoSize)
.build()
)
} }
super.onUserLeaveHint() 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 { override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
if (binding.player.onKeyBoardAction(keyCode)) { if (binding.player.onKeyBoardAction(keyCode)) {
return true return true

View File

@ -202,15 +202,11 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
/** /**
* Receiver for all actions in the PiP mode * 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) { override fun onReceive(context: Context, intent: Intent) {
when (intent.serializableExtra<PlayerEvent>(PlayerHelper.CONTROL_TYPE) ?: return) { when (intent.serializableExtra<PlayerEvent>(PlayerHelper.CONTROL_TYPE) ?: return) {
PlayerEvent.Play -> { PlayerEvent.PlayPause -> {
exoPlayer.play() exoPlayer.togglePlayPauseState()
}
PlayerEvent.Pause -> {
exoPlayer.pause()
} }
PlayerEvent.Forward -> { PlayerEvent.Forward -> {
@ -372,8 +368,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
// broadcast receiver for PiP actions // broadcast receiver for PiP actions
ContextCompat.registerReceiver( ContextCompat.registerReceiver(
requireContext(), requireContext(),
broadcastReceiver, playerActionReceiver,
IntentFilter(PlayerHelper.getIntentAction(requireContext())), IntentFilter(PlayerHelper.getIntentActionName(requireContext())),
ContextCompat.RECEIVER_NOT_EXPORTED ContextCompat.RECEIVER_NOT_EXPORTED
) )
@ -835,7 +831,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
runCatching { runCatching {
// unregister the receiver for player actions // unregister the receiver for player actions
context?.unregisterReceiver(broadcastReceiver) context?.unregisterReceiver(playerActionReceiver)
} }
_binding = null _binding = null