mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 16:00:31 +05:30
PiP: Forward and Rewind actions
This commit is contained in:
parent
99182b34af
commit
07dc1a80b8
14
app/src/main/java/com/github/libretube/enums/PlayerEvent.kt
Normal file
14
app/src/main/java/com/github/libretube/enums/PlayerEvent.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.github.libretube.enums
|
||||||
|
|
||||||
|
enum class PlayerEvent(val value: Int) {
|
||||||
|
Pause(0),
|
||||||
|
Play(1),
|
||||||
|
Forward(2),
|
||||||
|
Rewind(3),
|
||||||
|
Next(5),
|
||||||
|
Prev(6);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInt(value: Int) = PlayerEvent.values().first { it.value == value }
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,10 @@ package com.github.libretube.ui.fragments
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.PictureInPictureParams
|
import android.app.PictureInPictureParams
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.media.session.PlaybackState
|
import android.media.session.PlaybackState
|
||||||
@ -50,6 +52,7 @@ import com.github.libretube.databinding.PlayerGestureControlsViewBinding
|
|||||||
import com.github.libretube.db.DatabaseHelper
|
import com.github.libretube.db.DatabaseHelper
|
||||||
import com.github.libretube.db.DatabaseHolder.Companion.Database
|
import com.github.libretube.db.DatabaseHolder.Companion.Database
|
||||||
import com.github.libretube.db.obj.WatchPosition
|
import com.github.libretube.db.obj.WatchPosition
|
||||||
|
import com.github.libretube.enums.PlayerEvent
|
||||||
import com.github.libretube.enums.ShareObjectType
|
import com.github.libretube.enums.ShareObjectType
|
||||||
import com.github.libretube.extensions.TAG
|
import com.github.libretube.extensions.TAG
|
||||||
import com.github.libretube.extensions.awaitQuery
|
import com.github.libretube.extensions.awaitQuery
|
||||||
@ -107,15 +110,15 @@ import com.google.android.exoplayer2.ui.StyledPlayerView
|
|||||||
import com.google.android.exoplayer2.upstream.DefaultDataSource
|
import com.google.android.exoplayer2.upstream.DefaultDataSource
|
||||||
import com.google.android.exoplayer2.util.MimeTypes
|
import com.google.android.exoplayer2.util.MimeTypes
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.chromium.net.CronetEngine
|
import org.chromium.net.CronetEngine
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
||||||
|
|
||||||
@ -178,6 +181,28 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
val action = intent?.getIntExtra(PlayerHelper.CONTROL_TYPE, 0) ?: return
|
||||||
|
when (PlayerEvent.fromInt(action)) {
|
||||||
|
PlayerEvent.Play -> {
|
||||||
|
exoPlayer.play()
|
||||||
|
}
|
||||||
|
PlayerEvent.Pause -> {
|
||||||
|
exoPlayer.pause()
|
||||||
|
}
|
||||||
|
PlayerEvent.Forward -> {
|
||||||
|
exoPlayer.seekTo(exoPlayer.currentPosition + PlayerHelper.seekIncrement)
|
||||||
|
}
|
||||||
|
PlayerEvent.Rewind -> {
|
||||||
|
exoPlayer.seekTo(exoPlayer.currentPosition - PlayerHelper.seekIncrement)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
arguments?.let {
|
arguments?.let {
|
||||||
@ -186,6 +211,12 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
channelId = it.getString(IntentData.channelId)
|
channelId = it.getString(IntentData.channelId)
|
||||||
keepQueue = it.getBoolean(IntentData.keepQueue, false)
|
keepQueue = it.getBoolean(IntentData.keepQueue, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// broadcast receiver for PiP actions
|
||||||
|
context?.registerReceiver(
|
||||||
|
broadcastReceiver,
|
||||||
|
IntentFilter(PlayerHelper.getIntentActon(requireContext()))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@ -529,6 +560,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
// disable the auto PiP mode for SDK >= 32
|
// disable the auto PiP mode for SDK >= 32
|
||||||
disableAutoPiP()
|
disableAutoPiP()
|
||||||
|
|
||||||
|
// unregister the receiver for player actions
|
||||||
|
context?.unregisterReceiver(broadcastReceiver)
|
||||||
|
|
||||||
saveWatchPosition()
|
saveWatchPosition()
|
||||||
|
|
||||||
// clear the playing queue and release the player
|
// clear the playing queue and release the player
|
||||||
@ -794,7 +828,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
private fun localizedDate(date: String?): String? {
|
private fun localizedDate(date: String?): String? {
|
||||||
return if (SDK_INT >= Build.VERSION_CODES.N) {
|
return if (SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
TextUtils.localizeDate(date, resources.configuration.locales[0])
|
TextUtils.localizeDate(date, resources.configuration.locales[0])
|
||||||
} else TextUtils.localizeDate(date)
|
} else {
|
||||||
|
TextUtils.localizeDate(date)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLiveVideo() {
|
private fun handleLiveVideo() {
|
||||||
@ -855,10 +891,11 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
// Listener for play and pause icon change
|
// Listener for play and pause icon change
|
||||||
exoPlayer.addListener(object : Player.Listener {
|
exoPlayer.addListener(object : Player.Listener {
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
|
if (usePiP()) activity?.setPictureInPictureParams(getPipParams())
|
||||||
|
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
// Stop [BackgroundMode] service if it is running.
|
// Stop [BackgroundMode] service if it is running.
|
||||||
BackgroundHelper.stopBackgroundPlay(requireContext())
|
BackgroundHelper.stopBackgroundPlay(requireContext())
|
||||||
if (usePiP()) activity?.setPictureInPictureParams(getPipParams())
|
|
||||||
} else {
|
} else {
|
||||||
disableAutoPiP()
|
disableAutoPiP()
|
||||||
}
|
}
|
||||||
@ -1460,7 +1497,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
fun getPipParams(): PictureInPictureParams = PictureInPictureParams.Builder()
|
fun getPipParams(): PictureInPictureParams = PictureInPictureParams.Builder()
|
||||||
.setActions(emptyList())
|
.setActions(PlayerHelper.getPIPModeActions(requireActivity(), exoPlayer.isPlaying))
|
||||||
.apply {
|
.apply {
|
||||||
if (SDK_INT >= Build.VERSION_CODES.S) {
|
if (SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
setAutoEnterEnabled(true)
|
setAutoEnterEnabled(true)
|
||||||
|
@ -1,16 +1,29 @@
|
|||||||
package com.github.libretube.util
|
package com.github.libretube.util
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.RemoteAction
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import android.os.Build
|
||||||
import android.view.accessibility.CaptioningManager
|
import android.view.accessibility.CaptioningManager
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.obj.PipedStream
|
import com.github.libretube.api.obj.PipedStream
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import com.github.libretube.enums.AudioQuality
|
import com.github.libretube.enums.AudioQuality
|
||||||
|
import com.github.libretube.enums.PlayerEvent
|
||||||
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
||||||
import com.google.android.exoplayer2.video.VideoSize
|
import com.google.android.exoplayer2.video.VideoSize
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
object PlayerHelper {
|
object PlayerHelper {
|
||||||
|
private const val ACTION_MEDIA_CONTROL = "media_control"
|
||||||
|
const val CONTROL_TYPE = "control_type"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the audio source following the users preferences
|
* Get the audio source following the users preferences
|
||||||
*/
|
*/
|
||||||
@ -43,7 +56,7 @@ object PlayerHelper {
|
|||||||
/**
|
/**
|
||||||
* Get the best or worst bitrate from a list of audio streams
|
* Get the best or worst bitrate from a list of audio streams
|
||||||
* @param audios list of the audio streams
|
* @param audios list of the audio streams
|
||||||
* @param getLeast whether the least bitrate should be returned
|
* @param quality Whether to use the best or worst quality available
|
||||||
* @return Url of the audio source
|
* @return Url of the audio source
|
||||||
*/
|
*/
|
||||||
private fun getBitRate(audios: List<PipedStream>, quality: AudioQuality): String {
|
private fun getBitRate(audios: List<PipedStream>, quality: AudioQuality): String {
|
||||||
@ -332,4 +345,74 @@ object PlayerHelper {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getIntentActon(context: Context): String {
|
||||||
|
return context.packageName + "." + ACTION_MEDIA_CONTROL
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPendingIntent(activity: Activity, code: Int): PendingIntent {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
activity,
|
||||||
|
code,
|
||||||
|
Intent(getIntentActon(activity)).putExtra(CONTROL_TYPE, code),
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
activity,
|
||||||
|
code,
|
||||||
|
Intent(getIntentActon(activity)).putExtra(CONTROL_TYPE, code),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun getRemoteAction(
|
||||||
|
activity: Activity,
|
||||||
|
id: Int,
|
||||||
|
@StringRes title: Int,
|
||||||
|
event: PlayerEvent
|
||||||
|
): RemoteAction {
|
||||||
|
val text = activity.getString(title)
|
||||||
|
return RemoteAction(
|
||||||
|
Icon.createWithResource(activity, id),
|
||||||
|
text,
|
||||||
|
text,
|
||||||
|
getPendingIntent(activity, event.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
fun getPIPModeActions(activity: Activity, isPlaying: Boolean): ArrayList<RemoteAction> {
|
||||||
|
val actions: ArrayList<RemoteAction> = ArrayList()
|
||||||
|
actions.add(
|
||||||
|
getRemoteAction(
|
||||||
|
activity,
|
||||||
|
R.drawable.ic_rewind,
|
||||||
|
R.string.rewind,
|
||||||
|
PlayerEvent.Rewind
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
actions.add(
|
||||||
|
getRemoteAction(
|
||||||
|
activity,
|
||||||
|
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
|
||||||
|
R.string.pause,
|
||||||
|
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
actions.add(
|
||||||
|
getRemoteAction(
|
||||||
|
activity,
|
||||||
|
R.drawable.ic_forward,
|
||||||
|
R.string.forward,
|
||||||
|
PlayerEvent.Forward
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return actions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,6 +425,9 @@
|
|||||||
<string name="shuffle">Shuffle</string>
|
<string name="shuffle">Shuffle</string>
|
||||||
<string name="add_to_bookmarks">Add to bookmarks</string>
|
<string name="add_to_bookmarks">Add to bookmarks</string>
|
||||||
<string name="remove_bookmark">Remove bookmark</string>
|
<string name="remove_bookmark">Remove bookmark</string>
|
||||||
|
<string name="rewind">Rewind</string>
|
||||||
|
<string name="forward">Forward</string>
|
||||||
|
<string name="pause">Pause</string>
|
||||||
|
|
||||||
<!-- Notification channel strings -->
|
<!-- Notification channel strings -->
|
||||||
<string name="download_channel_name">Download Service</string>
|
<string name="download_channel_name">Download Service</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user