autoplay in background mode and fixes

This commit is contained in:
Bnyro 2022-08-08 10:43:46 +02:00
parent 0f679d68bc
commit 42b34b44d2
11 changed files with 96 additions and 53 deletions

View File

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

View File

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

View File

@ -102,7 +102,7 @@ class SearchAdapter(
} }
val videoId = item.url.toID() val videoId = item.url.toID()
root.setOnLongClickListener { root.setOnLongClickListener {
VideoOptionsDialog(videoId, root.context) VideoOptionsDialog(videoId)
.show(childFragmentManager, "VideoOptionsDialog") .show(childFragmentManager, "VideoOptionsDialog")
true true
} }
@ -175,7 +175,7 @@ class SearchAdapter(
} }
root.setOnLongClickListener { root.setOnLongClickListener {
val playlistId = item.url!!.toID() val playlistId = item.url!!.toID()
PlaylistOptionsDialog(playlistId, false, root.context) PlaylistOptionsDialog(playlistId, false)
.show(childFragmentManager, "PlaylistOptionsDialog") .show(childFragmentManager, "PlaylistOptionsDialog")
true true
} }

View File

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

View File

@ -50,7 +50,7 @@ class WatchHistoryAdapter(
NavigationHelper.navigateVideo(root.context, video.videoId) NavigationHelper.navigateVideo(root.context, video.videoId)
} }
root.setOnLongClickListener { root.setOnLongClickListener {
VideoOptionsDialog(video.videoId!!, root.context) VideoOptionsDialog(video.videoId!!)
.show(childFragmentManager, "VideoOptionsDialog") .show(childFragmentManager, "VideoOptionsDialog")
true true
} }

View File

@ -20,17 +20,17 @@ import java.io.IOException
class PlaylistOptionsDialog( class PlaylistOptionsDialog(
private val playlistId: String, private val playlistId: String,
private val isOwner: Boolean, private val isOwner: Boolean
context: Context
) : DialogFragment() { ) : DialogFragment() {
val TAG = "PlaylistOptionsDialog" val TAG = "PlaylistOptionsDialog"
private var optionsList = listOf(
context.getString(R.string.clonePlaylist),
context.getString(R.string.share)
)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// options for the dialog
var optionsList = listOf(
context?.getString(R.string.clonePlaylist),
context?.getString(R.string.share)
)
if (isOwner) { if (isOwner) {
optionsList = optionsList + optionsList = optionsList +
context?.getString(R.string.deletePlaylist)!! - context?.getString(R.string.deletePlaylist)!! -

View File

@ -1,7 +1,6 @@
package com.github.libretube.dialogs package com.github.libretube.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Toast 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. * 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" 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. * Dialog that returns a [MaterialAlertDialogBuilder] showing a menu of options.
*/ */
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 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()) return MaterialAlertDialogBuilder(requireContext())
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setAdapter( .setAdapter(

View File

@ -88,7 +88,7 @@ class PlaylistFragment : Fragment() {
// show playlist options // show playlist options
binding.optionsMenu.setOnClickListener { binding.optionsMenu.setOnClickListener {
val optionsDialog = val optionsDialog =
PlaylistOptionsDialog(playlistId!!, isOwner, requireContext()) PlaylistOptionsDialog(playlistId!!, isOwner)
optionsDialog.show(childFragmentManager, "PlaylistOptionsDialog") 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.obj.Streams
import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.preferences.PreferenceKeys import com.github.libretube.preferences.PreferenceKeys
import com.github.libretube.util.AutoPlayHelper
import com.github.libretube.util.NowPlayingNotification import com.github.libretube.util.NowPlayingNotification
import com.github.libretube.util.PlayerHelper import com.github.libretube.util.PlayerHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
@ -42,10 +43,15 @@ class BackgroundMode : Service() {
*/ */
private lateinit var videoId: String private lateinit var videoId: String
/**
*PlaylistId for autoplay
*/
private var playlistId: String? = null
/** /**
* The response that gets when called the Api. * 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) * 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 private var segmentData: Segments? = null
/** /**
* Notification for the player * [Notification] for the player
*/ */
private lateinit var nowPlayingNotification: NowPlayingNotification 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 * Setting the required [notification] for running as a foreground service
*/ */
@ -96,8 +112,12 @@ class BackgroundMode : Service() {
try { try {
// get the intent arguments // get the intent arguments
videoId = intent?.getStringExtra("videoId")!! videoId = intent?.getStringExtra("videoId")!!
playlistId = intent.getStringExtra(playlistId)
val position = intent.getLongExtra("position", 0L) val position = intent.getLongExtra("position", 0L)
// initialize the playlist autoPlay Helper
autoPlayHelper = AutoPlayHelper(playlistId!!)
// play the audio in the background // play the audio in the background
playAudio(videoId, position) playAudio(videoId, position)
} catch (e: Exception) { } catch (e: Exception) {
@ -116,7 +136,7 @@ class BackgroundMode : Service() {
) { ) {
runBlocking { runBlocking {
val job = launch { 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 // Wait until the job is done, to load correctly later in the player
job.join() job.join()
@ -128,7 +148,7 @@ class BackgroundMode : Service() {
if (!this@BackgroundMode::nowPlayingNotification.isInitialized) { if (!this@BackgroundMode::nowPlayingNotification.isInitialized) {
nowPlayingNotification = NowPlayingNotification(this@BackgroundMode, player!!) nowPlayingNotification = NowPlayingNotification(this@BackgroundMode, player!!)
} }
nowPlayingNotification.updatePlayerNotification(response!!) nowPlayingNotification.updatePlayerNotification(streams!!)
player?.apply { player?.apply {
playWhenReady = playWhenReadyPlayer playWhenReady = playWhenReadyPlayer
@ -146,16 +166,15 @@ class BackgroundMode : Service() {
* create the player * create the player
*/ */
private fun initializePlayer() { private fun initializePlayer() {
if (player != null) return
audioAttributes = AudioAttributes.Builder() audioAttributes = AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA) .setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MUSIC) .setContentType(C.CONTENT_TYPE_MUSIC)
.build() .build()
player = ExoPlayer.Builder(this)
if (player == null) { .setAudioAttributes(audioAttributes, true)
player = ExoPlayer.Builder(this) .build()
.setAudioAttributes(audioAttributes, true)
.build()
}
/** /**
* Listens for changed playbackStates (e.g. pause, end) * Listens for changed playbackStates (e.g. pause, end)
@ -177,26 +196,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() { private fun setNextStream() {
if (response!!.relatedStreams!!.isNotEmpty()) { if (streams!!.relatedStreams!!.isNotEmpty()) {
val videoId = response!! nextStreamId = streams?.relatedStreams!![0].url.toID()
.relatedStreams!![0].url.toID() }
// play new video on background if (playlistId == null) return
this.videoId = videoId if (!this::autoPlayHelper.isInitialized) autoPlayHelper = AutoPlayHelper(playlistId!!)
this.segmentData = null // search for the next videoId in the playlist
playAudio(videoId) 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 (nextStreamId == videoId || !this::nextStreamId.isInitialized) 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]. * with the [mediaSession] and attach it to the [player].
*/ */
private fun setMediaItem() { private fun setMediaItem() {
response?.let { streams?.let {
val mediaItem = MediaItem.Builder().setUri(it.hls!!).build() val mediaItem = MediaItem.Builder().setUri(it.hls!!).build()
player?.setMediaItem(mediaItem) player?.setMediaItem(mediaItem)
} }

View File

@ -1,6 +1,5 @@
package com.github.libretube.util package com.github.libretube.util
import com.github.libretube.obj.Playlist
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -10,7 +9,6 @@ class AutoPlayHelper(
private val TAG = "AutoPlayHelper" private val TAG = "AutoPlayHelper"
private val playlistStreamIds = mutableListOf<String>() private val playlistStreamIds = mutableListOf<String>()
private lateinit var playlist: Playlist
private var playlistNextPage: String? = null private var playlistNextPage: String? = null
suspend fun getNextPlaylistVideoId(currentVideoId: String): String? { suspend fun getNextPlaylistVideoId(currentVideoId: String): String? {
@ -18,20 +16,23 @@ class AutoPlayHelper(
if (playlistStreamIds.contains(currentVideoId)) { if (playlistStreamIds.contains(currentVideoId)) {
val index = playlistStreamIds.indexOf(currentVideoId) val index = playlistStreamIds.indexOf(currentVideoId)
// check whether there's a next video // 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 getNextPlaylistVideoId(currentVideoId)
} else if (playlistStreamIds.isEmpty() || playlistNextPage != null) { } else if (playlistStreamIds.isEmpty() || playlistNextPage != null) {
// fetch the next page of the playlist // fetch the next page of the playlist
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
// fetch the playlists videos // fetch the playlists or its nextPage's videos
playlist = RetrofitInstance.api.getPlaylist(playlistId) val playlist =
// save the playlist urls in the array 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() } playlistStreamIds += playlist.relatedStreams!!.map { it.url.toID() }
// save playlistNextPage for usage if video is not contained // save playlistNextPage for usage if video is not contained
playlistNextPage = playlist.nextpage playlistNextPage = playlist.nextpage
return@withContext getNextPlaylistVideoId(currentVideoId) return@withContext getNextPlaylistVideoId(currentVideoId)
} }
} }
// return null when no nextPage is found
return null return null
} }
} }

View File

@ -4,15 +4,23 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import com.github.libretube.services.BackgroundMode import com.github.libretube.services.BackgroundMode
/**
* Helper for starting a new Instance of the [BackgroundMode]
*/
object BackgroundHelper { object BackgroundHelper {
fun playOnBackground( fun playOnBackground(
context: Context, context: Context,
videoId: String, 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) val intent = Intent(context, BackgroundMode::class.java)
intent.putExtra("videoId", videoId) intent.putExtra("videoId", videoId)
if (playlistId != null) intent.putExtra("playlistId", playlistId)
if (position != null) intent.putExtra("position", position) if (position != null) intent.putExtra("position", position)
// start the background mode as foreground service
context.startForegroundService(intent) context.startForegroundService(intent)
} }
} }