mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 07:50:31 +05:30
autoplay in background mode and fixes
This commit is contained in:
parent
0f679d68bc
commit
42b34b44d2
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)!! -
|
||||||
|
@ -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(
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user