diff --git a/app/src/main/java/com/github/libretube/adapters/ChannelAdapter.kt b/app/src/main/java/com/github/libretube/adapters/ChannelAdapter.kt index dfafc725c..895929166 100644 --- a/app/src/main/java/com/github/libretube/adapters/ChannelAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/ChannelAdapter.kt @@ -51,7 +51,7 @@ class ChannelAdapter( } val videoId = trending.url.toID() root.setOnLongClickListener { - VideoOptionsDialog(videoId, root.context) + VideoOptionsDialog(videoId) .show(childFragmentManager, "VideoOptionsDialog") true } diff --git a/app/src/main/java/com/github/libretube/adapters/PlaylistAdapter.kt b/app/src/main/java/com/github/libretube/adapters/PlaylistAdapter.kt index 74e44b6a6..baa0031f4 100644 --- a/app/src/main/java/com/github/libretube/adapters/PlaylistAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/PlaylistAdapter.kt @@ -60,7 +60,7 @@ class PlaylistAdapter( } val videoId = streamItem.url.toID() root.setOnLongClickListener { - VideoOptionsDialog(videoId, root.context) + VideoOptionsDialog(videoId) .show(childFragmentManager, "VideoOptionsDialog") true } diff --git a/app/src/main/java/com/github/libretube/adapters/SearchAdapter.kt b/app/src/main/java/com/github/libretube/adapters/SearchAdapter.kt index 793440518..4c2709281 100644 --- a/app/src/main/java/com/github/libretube/adapters/SearchAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/SearchAdapter.kt @@ -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 } diff --git a/app/src/main/java/com/github/libretube/adapters/TrendingAdapter.kt b/app/src/main/java/com/github/libretube/adapters/TrendingAdapter.kt index c1eebc108..28ecce85b 100644 --- a/app/src/main/java/com/github/libretube/adapters/TrendingAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/TrendingAdapter.kt @@ -65,7 +65,7 @@ class TrendingAdapter( } val videoId = trending.url!!.toID() root.setOnLongClickListener { - VideoOptionsDialog(videoId, root.context) + VideoOptionsDialog(videoId) .show(childFragmentManager, "VideoOptionsDialog") true } diff --git a/app/src/main/java/com/github/libretube/adapters/WatchHistoryAdapter.kt b/app/src/main/java/com/github/libretube/adapters/WatchHistoryAdapter.kt index e5bbf67e7..27bc9b694 100644 --- a/app/src/main/java/com/github/libretube/adapters/WatchHistoryAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/WatchHistoryAdapter.kt @@ -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 } diff --git a/app/src/main/java/com/github/libretube/dialogs/PlaylistOptionsDialog.kt b/app/src/main/java/com/github/libretube/dialogs/PlaylistOptionsDialog.kt index c2514c993..298b4c490 100644 --- a/app/src/main/java/com/github/libretube/dialogs/PlaylistOptionsDialog.kt +++ b/app/src/main/java/com/github/libretube/dialogs/PlaylistOptionsDialog.kt @@ -20,17 +20,17 @@ 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.clonePlaylist), + context?.getString(R.string.share) + ) + if (isOwner) { optionsList = optionsList + context?.getString(R.string.deletePlaylist)!! - diff --git a/app/src/main/java/com/github/libretube/dialogs/VideoOptionsDialog.kt b/app/src/main/java/com/github/libretube/dialogs/VideoOptionsDialog.kt index 5091ac576..63adf531e 100644 --- a/app/src/main/java/com/github/libretube/dialogs/VideoOptionsDialog.kt +++ b/app/src/main/java/com/github/libretube/dialogs/VideoOptionsDialog.kt @@ -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( diff --git a/app/src/main/java/com/github/libretube/fragments/PlaylistFragment.kt b/app/src/main/java/com/github/libretube/fragments/PlaylistFragment.kt index ea673d5b5..c1f435021 100644 --- a/app/src/main/java/com/github/libretube/fragments/PlaylistFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/PlaylistFragment.kt @@ -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") } diff --git a/app/src/main/java/com/github/libretube/services/BackgroundMode.kt b/app/src/main/java/com/github/libretube/services/BackgroundMode.kt index 28fefb948..c2d281630 100644 --- a/app/src/main/java/com/github/libretube/services/BackgroundMode.kt +++ b/app/src/main/java/com/github/libretube/services/BackgroundMode.kt @@ -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 + 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 @@ -146,16 +166,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 +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() { - 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 (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]. */ private fun setMediaItem() { - response?.let { + streams?.let { val mediaItem = MediaItem.Builder().setUri(it.hls!!).build() player?.setMediaItem(mediaItem) } diff --git a/app/src/main/java/com/github/libretube/util/AutoPlayHelper.kt b/app/src/main/java/com/github/libretube/util/AutoPlayHelper.kt index c1e82a491..d89a146ac 100644 --- a/app/src/main/java/com/github/libretube/util/AutoPlayHelper.kt +++ b/app/src/main/java/com/github/libretube/util/AutoPlayHelper.kt @@ -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() - 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 } } diff --git a/app/src/main/java/com/github/libretube/util/BackgroundHelper.kt b/app/src/main/java/com/github/libretube/util/BackgroundHelper.kt index 499b4ca33..5bb55909c 100644 --- a/app/src/main/java/com/github/libretube/util/BackgroundHelper.kt +++ b/app/src/main/java/com/github/libretube/util/BackgroundHelper.kt @@ -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) } }