Merge pull request #994 from Bnyro/notification

playlist autoplay in background mode
This commit is contained in:
Bnyro 2022-08-08 11:09:15 +02:00 committed by GitHub
commit 90d888483c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 114 additions and 54 deletions

View File

@ -51,7 +51,7 @@ class ChannelAdapter(
}
val videoId = trending.url.toID()
root.setOnLongClickListener {
VideoOptionsDialog(videoId, root.context)
VideoOptionsDialog(videoId)
.show(childFragmentManager, "VideoOptionsDialog")
true
}

View File

@ -60,7 +60,7 @@ class PlaylistAdapter(
}
val videoId = streamItem.url.toID()
root.setOnLongClickListener {
VideoOptionsDialog(videoId, root.context)
VideoOptionsDialog(videoId)
.show(childFragmentManager, "VideoOptionsDialog")
true
}

View File

@ -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
}

View File

@ -65,7 +65,7 @@ class TrendingAdapter(
}
val videoId = trending.url!!.toID()
root.setOnLongClickListener {
VideoOptionsDialog(videoId, root.context)
VideoOptionsDialog(videoId)
.show(childFragmentManager, "VideoOptionsDialog")
true
}

View File

@ -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
}

View File

@ -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()

View File

@ -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(

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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)
}
}