mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-27 23:40:33 +05:30
Merge pull request #994 from Bnyro/notification
playlist autoplay in background mode
This commit is contained in:
commit
90d888483c
@ -51,7 +51,7 @@ class ChannelAdapter(
|
||||
}
|
||||
val videoId = trending.url.toID()
|
||||
root.setOnLongClickListener {
|
||||
VideoOptionsDialog(videoId, root.context)
|
||||
VideoOptionsDialog(videoId)
|
||||
.show(childFragmentManager, "VideoOptionsDialog")
|
||||
true
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ class PlaylistAdapter(
|
||||
}
|
||||
val videoId = streamItem.url.toID()
|
||||
root.setOnLongClickListener {
|
||||
VideoOptionsDialog(videoId, root.context)
|
||||
VideoOptionsDialog(videoId)
|
||||
.show(childFragmentManager, "VideoOptionsDialog")
|
||||
true
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ class SearchAdapter(
|
||||
}
|
||||
val videoId = item.url.toID()
|
||||
root.setOnLongClickListener {
|
||||
VideoOptionsDialog(videoId, root.context)
|
||||
VideoOptionsDialog(videoId)
|
||||
.show(childFragmentManager, "VideoOptionsDialog")
|
||||
true
|
||||
}
|
||||
@ -175,7 +175,7 @@ class SearchAdapter(
|
||||
}
|
||||
root.setOnLongClickListener {
|
||||
val playlistId = item.url!!.toID()
|
||||
PlaylistOptionsDialog(playlistId, false, root.context)
|
||||
PlaylistOptionsDialog(playlistId, false)
|
||||
.show(childFragmentManager, "PlaylistOptionsDialog")
|
||||
true
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class TrendingAdapter(
|
||||
}
|
||||
val videoId = trending.url!!.toID()
|
||||
root.setOnLongClickListener {
|
||||
VideoOptionsDialog(videoId, root.context)
|
||||
VideoOptionsDialog(videoId)
|
||||
.show(childFragmentManager, "VideoOptionsDialog")
|
||||
true
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class WatchHistoryAdapter(
|
||||
NavigationHelper.navigateVideo(root.context, video.videoId)
|
||||
}
|
||||
root.setOnLongClickListener {
|
||||
VideoOptionsDialog(video.videoId!!, root.context)
|
||||
VideoOptionsDialog(video.videoId!!)
|
||||
.show(childFragmentManager, "VideoOptionsDialog")
|
||||
true
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.github.libretube.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.ArrayAdapter
|
||||
@ -10,27 +9,31 @@ import androidx.fragment.app.DialogFragment
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.obj.PlaylistId
|
||||
import com.github.libretube.preferences.PreferenceHelper
|
||||
import com.github.libretube.util.BackgroundHelper
|
||||
import com.github.libretube.util.RetrofitInstance
|
||||
import com.github.libretube.util.toID
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
class PlaylistOptionsDialog(
|
||||
private val playlistId: String,
|
||||
private val isOwner: Boolean,
|
||||
context: Context
|
||||
private val isOwner: Boolean
|
||||
) : DialogFragment() {
|
||||
val TAG = "PlaylistOptionsDialog"
|
||||
|
||||
private var optionsList = listOf(
|
||||
context.getString(R.string.clonePlaylist),
|
||||
context.getString(R.string.share)
|
||||
)
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
// options for the dialog
|
||||
var optionsList = listOf(
|
||||
context?.getString(R.string.playOnBackground),
|
||||
context?.getString(R.string.clonePlaylist),
|
||||
context?.getString(R.string.share)
|
||||
)
|
||||
|
||||
if (isOwner) {
|
||||
optionsList = optionsList +
|
||||
context?.getString(R.string.deletePlaylist)!! -
|
||||
@ -49,6 +52,18 @@ class PlaylistOptionsDialog(
|
||||
)
|
||||
) { _, which ->
|
||||
when (optionsList[which]) {
|
||||
// play the playlist in the background
|
||||
context?.getString(R.string.playOnBackground) -> {
|
||||
runBlocking {
|
||||
val playlist = if (isOwner) RetrofitInstance.authApi.getPlaylist(playlistId)
|
||||
else RetrofitInstance.api.getPlaylist(playlistId)
|
||||
BackgroundHelper.playOnBackground(
|
||||
context = requireContext(),
|
||||
videoId = playlist.relatedStreams!![0].url.toID(),
|
||||
playlistId = playlistId
|
||||
)
|
||||
}
|
||||
}
|
||||
// Clone the playlist to the users Piped account
|
||||
context?.getString(R.string.clonePlaylist) -> {
|
||||
val token = PreferenceHelper.getToken()
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.github.libretube.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
@ -16,22 +15,24 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
*
|
||||
* Needs the [videoId] to load the content from the right video.
|
||||
*/
|
||||
class VideoOptionsDialog(private val videoId: String, context: Context) : DialogFragment() {
|
||||
class VideoOptionsDialog(
|
||||
private val videoId: String
|
||||
) : DialogFragment() {
|
||||
private val TAG = "VideoOptionsDialog"
|
||||
|
||||
/**
|
||||
* List that stores the different menu options. In the future could be add more options here.
|
||||
*/
|
||||
private val optionsList = listOf(
|
||||
context.getString(R.string.playOnBackground),
|
||||
context.getString(R.string.addToPlaylist),
|
||||
context.getString(R.string.share)
|
||||
)
|
||||
|
||||
/**
|
||||
* Dialog that returns a [MaterialAlertDialogBuilder] showing a menu of options.
|
||||
*/
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
/**
|
||||
* List that stores the different menu options. In the future could be add more options here.
|
||||
*/
|
||||
val optionsList = listOf(
|
||||
context?.getString(R.string.playOnBackground),
|
||||
context?.getString(R.string.addToPlaylist),
|
||||
context?.getString(R.string.share)
|
||||
)
|
||||
|
||||
return MaterialAlertDialogBuilder(requireContext())
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setAdapter(
|
||||
|
@ -88,7 +88,7 @@ class PlaylistFragment : Fragment() {
|
||||
// show playlist options
|
||||
binding.optionsMenu.setOnClickListener {
|
||||
val optionsDialog =
|
||||
PlaylistOptionsDialog(playlistId!!, isOwner, requireContext())
|
||||
PlaylistOptionsDialog(playlistId!!, isOwner)
|
||||
optionsDialog.show(childFragmentManager, "PlaylistOptionsDialog")
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import com.github.libretube.obj.Segments
|
||||
import com.github.libretube.obj.Streams
|
||||
import com.github.libretube.preferences.PreferenceHelper
|
||||
import com.github.libretube.preferences.PreferenceKeys
|
||||
import com.github.libretube.util.AutoPlayHelper
|
||||
import com.github.libretube.util.NowPlayingNotification
|
||||
import com.github.libretube.util.PlayerHelper
|
||||
import com.github.libretube.util.RetrofitInstance
|
||||
@ -42,10 +43,15 @@ class BackgroundMode : Service() {
|
||||
*/
|
||||
private lateinit var videoId: String
|
||||
|
||||
/**
|
||||
*PlaylistId for autoplay
|
||||
*/
|
||||
private var playlistId: String? = null
|
||||
|
||||
/**
|
||||
* The response that gets when called the Api.
|
||||
*/
|
||||
private var response: Streams? = null
|
||||
private var streams: Streams? = null
|
||||
|
||||
/**
|
||||
* The [ExoPlayer] player. Followed tutorial [here](https://developer.android.com/codelabs/exoplayer-intro)
|
||||
@ -64,10 +70,20 @@ class BackgroundMode : Service() {
|
||||
private var segmentData: Segments? = null
|
||||
|
||||
/**
|
||||
* Notification for the player
|
||||
* [Notification] for the player
|
||||
*/
|
||||
private lateinit var nowPlayingNotification: NowPlayingNotification
|
||||
|
||||
/**
|
||||
* The [videoId] of the next stream for autoplay
|
||||
*/
|
||||
private lateinit var nextStreamId: String
|
||||
|
||||
/**
|
||||
* Helper for finding the next video in the playlist
|
||||
*/
|
||||
private lateinit var autoPlayHelper: AutoPlayHelper
|
||||
|
||||
/**
|
||||
* Setting the required [notification] for running as a foreground service
|
||||
*/
|
||||
@ -96,8 +112,12 @@ class BackgroundMode : Service() {
|
||||
try {
|
||||
// get the intent arguments
|
||||
videoId = intent?.getStringExtra("videoId")!!
|
||||
playlistId = intent.getStringExtra("playlistId")
|
||||
val position = intent.getLongExtra("position", 0L)
|
||||
|
||||
// initialize the playlist autoPlay Helper
|
||||
if (playlistId != null) autoPlayHelper = AutoPlayHelper(playlistId!!)
|
||||
|
||||
// play the audio in the background
|
||||
playAudio(videoId, position)
|
||||
} catch (e: Exception) {
|
||||
@ -116,7 +136,7 @@ class BackgroundMode : Service() {
|
||||
) {
|
||||
runBlocking {
|
||||
val job = launch {
|
||||
response = RetrofitInstance.api.getStreams(videoId)
|
||||
streams = RetrofitInstance.api.getStreams(videoId)
|
||||
}
|
||||
// Wait until the job is done, to load correctly later in the player
|
||||
job.join()
|
||||
@ -128,7 +148,7 @@ class BackgroundMode : Service() {
|
||||
if (!this@BackgroundMode::nowPlayingNotification.isInitialized) {
|
||||
nowPlayingNotification = NowPlayingNotification(this@BackgroundMode, player!!)
|
||||
}
|
||||
nowPlayingNotification.updatePlayerNotification(response!!)
|
||||
nowPlayingNotification.updatePlayerNotification(streams!!)
|
||||
|
||||
player?.apply {
|
||||
playWhenReady = playWhenReadyPlayer
|
||||
@ -139,6 +159,8 @@ class BackgroundMode : Service() {
|
||||
if (seekToPosition != 0L) player?.seekTo(seekToPosition)
|
||||
|
||||
fetchSponsorBlockSegments()
|
||||
|
||||
setNextStream()
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,16 +168,15 @@ class BackgroundMode : Service() {
|
||||
* create the player
|
||||
*/
|
||||
private fun initializePlayer() {
|
||||
if (player != null) return
|
||||
|
||||
audioAttributes = AudioAttributes.Builder()
|
||||
.setUsage(C.USAGE_MEDIA)
|
||||
.setContentType(C.CONTENT_TYPE_MUSIC)
|
||||
.build()
|
||||
|
||||
if (player == null) {
|
||||
player = ExoPlayer.Builder(this)
|
||||
.setAudioAttributes(audioAttributes, true)
|
||||
.build()
|
||||
}
|
||||
player = ExoPlayer.Builder(this)
|
||||
.setAudioAttributes(audioAttributes, true)
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Listens for changed playbackStates (e.g. pause, end)
|
||||
@ -177,26 +198,40 @@ class BackgroundMode : Service() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the first related video to the current (used when the playback of the current video ended)
|
||||
* set the videoId of the next stream for autoplay
|
||||
*/
|
||||
private fun playNextVideo() {
|
||||
if (response!!.relatedStreams!!.isNotEmpty()) {
|
||||
val videoId = response!!
|
||||
.relatedStreams!![0].url.toID()
|
||||
private fun setNextStream() {
|
||||
if (streams!!.relatedStreams!!.isNotEmpty()) {
|
||||
nextStreamId = streams?.relatedStreams!![0].url.toID()
|
||||
}
|
||||
|
||||
// play new video on background
|
||||
this.videoId = videoId
|
||||
this.segmentData = null
|
||||
playAudio(videoId)
|
||||
if (playlistId == null) return
|
||||
if (!this::autoPlayHelper.isInitialized) autoPlayHelper = AutoPlayHelper(playlistId!!)
|
||||
// search for the next videoId in the playlist
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val nextId = autoPlayHelper.getNextPlaylistVideoId(videoId)
|
||||
if (nextId != null) nextStreamId = nextId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the [MediaItem] with the [response] into the [player]. Also creates a [MediaSessionConnector]
|
||||
* Plays the first related video to the current (used when the playback of the current video ended)
|
||||
*/
|
||||
private fun playNextVideo() {
|
||||
if (!this::nextStreamId.isInitialized || nextStreamId == videoId) return
|
||||
|
||||
// play new video on background
|
||||
this.videoId = nextStreamId
|
||||
this.segmentData = null
|
||||
playAudio(videoId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the [MediaItem] with the [streams] into the [player]. Also creates a [MediaSessionConnector]
|
||||
* with the [mediaSession] and attach it to the [player].
|
||||
*/
|
||||
private fun setMediaItem() {
|
||||
response?.let {
|
||||
streams?.let {
|
||||
val mediaItem = MediaItem.Builder().setUri(it.hls!!).build()
|
||||
player?.setMediaItem(mediaItem)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.github.libretube.util
|
||||
|
||||
import com.github.libretube.obj.Playlist
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@ -10,7 +9,6 @@ class AutoPlayHelper(
|
||||
private val TAG = "AutoPlayHelper"
|
||||
|
||||
private val playlistStreamIds = mutableListOf<String>()
|
||||
private lateinit var playlist: Playlist
|
||||
private var playlistNextPage: String? = null
|
||||
|
||||
suspend fun getNextPlaylistVideoId(currentVideoId: String): String? {
|
||||
@ -18,20 +16,23 @@ class AutoPlayHelper(
|
||||
if (playlistStreamIds.contains(currentVideoId)) {
|
||||
val index = playlistStreamIds.indexOf(currentVideoId)
|
||||
// check whether there's a next video
|
||||
return if (index < playlistStreamIds.size) playlistStreamIds[index + 1]
|
||||
return if (index + 1 < playlistStreamIds.size) playlistStreamIds[index + 1]
|
||||
else getNextPlaylistVideoId(currentVideoId)
|
||||
} else if (playlistStreamIds.isEmpty() || playlistNextPage != null) {
|
||||
// fetch the next page of the playlist
|
||||
return withContext(Dispatchers.IO) {
|
||||
// fetch the playlists videos
|
||||
playlist = RetrofitInstance.api.getPlaylist(playlistId)
|
||||
// save the playlist urls in the array
|
||||
// fetch the playlists or its nextPage's videos
|
||||
val playlist =
|
||||
if (playlistNextPage == null) RetrofitInstance.api.getPlaylist(playlistId)
|
||||
else RetrofitInstance.api.getPlaylistNextPage(playlistId, playlistNextPage!!)
|
||||
// save the playlist urls to the list
|
||||
playlistStreamIds += playlist.relatedStreams!!.map { it.url.toID() }
|
||||
// save playlistNextPage for usage if video is not contained
|
||||
playlistNextPage = playlist.nextpage
|
||||
return@withContext getNextPlaylistVideoId(currentVideoId)
|
||||
}
|
||||
}
|
||||
// return null when no nextPage is found
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,23 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.github.libretube.services.BackgroundMode
|
||||
|
||||
/**
|
||||
* Helper for starting a new Instance of the [BackgroundMode]
|
||||
*/
|
||||
object BackgroundHelper {
|
||||
fun playOnBackground(
|
||||
context: Context,
|
||||
videoId: String,
|
||||
position: Long? = null
|
||||
position: Long? = null,
|
||||
playlistId: String? = null
|
||||
) {
|
||||
// create an intent for the background mode service
|
||||
val intent = Intent(context, BackgroundMode::class.java)
|
||||
intent.putExtra("videoId", videoId)
|
||||
if (playlistId != null) intent.putExtra("playlistId", playlistId)
|
||||
if (position != null) intent.putExtra("position", position)
|
||||
|
||||
// start the background mode as foreground service
|
||||
context.startForegroundService(intent)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user