Merge pull request #858 from Bnyro/master

improve background play
This commit is contained in:
Bnyro 2022-07-23 15:42:22 +02:00 committed by GitHub
commit 62874360c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 83 additions and 56 deletions

View File

@ -1,7 +1,5 @@
package com.github.libretube package com.github.libretube
import android.content.Intent
/** /**
* Global variables can be stored here * Global variables can be stored here
*/ */
@ -15,7 +13,4 @@ object Globals {
// for downloads // for downloads
var IS_DOWNLOAD_RUNNING = false var IS_DOWNLOAD_RUNNING = false
// background mode intent
var backgroundModeIntent: Intent? = null
} }

View File

@ -2,15 +2,13 @@ package com.github.libretube.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.github.libretube.Globals
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.services.BackgroundMode import com.github.libretube.util.BackgroundHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
/** /**
@ -48,13 +46,7 @@ class VideoOptionsDialog(private val videoId: String, context: Context) : Dialog
when (optionsList[which]) { when (optionsList[which]) {
// Start the background mode // Start the background mode
context?.getString(R.string.playOnBackground) -> { context?.getString(R.string.playOnBackground) -> {
if (Globals.backgroundModeIntent != null) { BackgroundHelper.playOnBackground(requireContext(), videoId)
activity?.stopService(Globals.backgroundModeIntent)
}
val intent = Intent(context, BackgroundMode::class.java)
intent.putExtra("videoId", videoId)
Globals.backgroundModeIntent = intent
activity?.startService(intent)
} }
// Add Video to Playlist Dialog // Add Video to Playlist Dialog
context?.getString(R.string.addToPlaylist) -> { context?.getString(R.string.addToPlaylist) -> {

View File

@ -58,7 +58,7 @@ import com.github.libretube.obj.Streams
import com.github.libretube.obj.Subscribe import com.github.libretube.obj.Subscribe
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.services.BackgroundMode import com.github.libretube.util.BackgroundHelper
import com.github.libretube.util.ConnectionHelper import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.CronetHelper import com.github.libretube.util.CronetHelper
import com.github.libretube.util.DescriptionAdapter import com.github.libretube.util.DescriptionAdapter
@ -528,13 +528,7 @@ class PlayerFragment : Fragment() {
exoPlayer.pause() exoPlayer.pause()
// start the background mode // start the background mode
if (Globals.backgroundModeIntent != null) { BackgroundHelper.playOnBackground(requireContext(), videoId!!)
activity?.stopService(Globals.backgroundModeIntent)
}
val intent = Intent(context, BackgroundMode::class.java)
intent.putExtra("videoId", videoId)
Globals.backgroundModeIntent = intent
activity?.startService(intent)
} }
binding.playerScrollView.viewTreeObserver binding.playerScrollView.viewTreeObserver

View File

@ -66,6 +66,7 @@ class SearchFragment : Fragment() {
binding.clearSearchImageView.setOnClickListener { binding.clearSearchImageView.setOnClickListener {
binding.autoCompleteTextView.text.clear() binding.autoCompleteTextView.text.clear()
showHistory()
} }
binding.filterMenuImageView.setOnClickListener { binding.filterMenuImageView.setOnClickListener {

View File

@ -1,11 +1,15 @@
package com.github.libretube.services package com.github.libretube.services
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
import com.github.libretube.R
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
@ -20,7 +24,6 @@ import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ui.PlayerNotificationManager import com.google.android.exoplayer2.ui.PlayerNotificationManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.lang.Exception
/** /**
* Loads the selected videos audio in background mode with a notification area. * Loads the selected videos audio in background mode with a notification area.
@ -57,27 +60,44 @@ class BackgroundMode : Service() {
*/ */
private lateinit var audioAttributes: AudioAttributes private lateinit var audioAttributes: AudioAttributes
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= 26) {
val channelId = "background service"
val channel = NotificationChannel(
channelId,
"BackgroundPlay Service",
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
val notification: Notification = Notification.Builder(this, channelId)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.playingOnBackground)).build()
startForeground(1, notification)
}
}
/** /**
* Initializes the [player] with the [MediaItem]. * Initializes the [player] with the [MediaItem].
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
try { // destroy the old player
val videoId = intent?.getStringExtra("videoId")!! destroyPlayer()
val seekToPosition = intent.getLongExtra("seekToPosition", 0L)
playOnBackgroundMode(this, videoId, seekToPosition) // get the intent arguments
} catch (e: Exception) { val videoId = intent?.getStringExtra("videoId")!!
try { val position = intent.getLongExtra("position", 0L)
stopService(intent)
} catch (e: Exception) {} // play the audio in the background
} playAudio(videoId, position)
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
} }
/** /**
* Gets the video data and prepares the [player]. * Gets the video data and prepares the [player].
*/ */
private fun playOnBackgroundMode( private fun playAudio(
c: Context,
videoId: String, videoId: String,
seekToPosition: Long = 0 seekToPosition: Long = 0
) { ) {
@ -88,14 +108,15 @@ class BackgroundMode : Service() {
// 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()
initializePlayer(c) initializePlayer()
initializePlayerNotification(c) initializePlayerNotification()
player?.apply { player?.apply {
playWhenReady = playWhenReadyPlayer playWhenReady = playWhenReadyPlayer
prepare() prepare()
} }
// seek to the previous position if available
if (seekToPosition != 0L) player?.seekTo(seekToPosition) if (seekToPosition != 0L) player?.seekTo(seekToPosition)
} }
} }
@ -103,14 +124,14 @@ class BackgroundMode : Service() {
/** /**
* create the player * create the player
*/ */
private fun initializePlayer(c: Context) { private fun initializePlayer() {
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()
if (player == null) { if (player == null) {
player = ExoPlayer.Builder(c) player = ExoPlayer.Builder(this)
.setAudioAttributes(audioAttributes, true) .setAudioAttributes(audioAttributes, true)
.build() .build()
} }
@ -123,49 +144,43 @@ class BackgroundMode : Service() {
override fun onPlaybackStateChanged(@Player.State state: Int) { override fun onPlaybackStateChanged(@Player.State state: Int) {
val autoplay = PreferenceHelper.getBoolean(PreferenceKeys.AUTO_PLAY, false) val autoplay = PreferenceHelper.getBoolean(PreferenceKeys.AUTO_PLAY, false)
if (state == Player.STATE_ENDED) { if (state == Player.STATE_ENDED) {
if (autoplay) playNextVideo(c) if (autoplay) playNextVideo()
} }
} }
}) })
setMediaItem(c) setMediaItem()
} }
/** /**
* Plays the first related video to the current (used when the playback of the current video ended) * Plays the first related video to the current (used when the playback of the current video ended)
*/ */
private fun playNextVideo(c: Context) { private fun playNextVideo() {
if (response!!.relatedStreams!!.isNotEmpty()) { if (response!!.relatedStreams!!.isNotEmpty()) {
val videoId = response!! val videoId = response!!
.relatedStreams!![0].url!! .relatedStreams!![0].url!!
.replace("/watch?v=", "") .replace("/watch?v=", "")
// destroy old player and its notification // destroy previous notification and player
playerNotification = null destroyPlayer()
player = null
// kill old notification
val notificationManager = c.getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
notificationManager.cancel(1)
// play new video on background // play new video on background
playOnBackgroundMode(c, videoId) playAudio(videoId)
} }
} }
/** /**
* Initializes the [playerNotification] attached to the [player] and shows it. * Initializes the [playerNotification] attached to the [player] and shows it.
*/ */
private fun initializePlayerNotification(c: Context) { private fun initializePlayerNotification() {
playerNotification = PlayerNotificationManager playerNotification = PlayerNotificationManager
.Builder(c, 1, "background_mode") .Builder(this, 1, "background_mode")
// set the description of the notification // set the description of the notification
.setMediaDescriptionAdapter( .setMediaDescriptionAdapter(
DescriptionAdapter( DescriptionAdapter(
response?.title!!, response?.title!!,
response?.uploader!!, response?.uploader!!,
response?.thumbnailUrl!!, response?.thumbnailUrl!!,
c this
) )
) )
.build() .build()
@ -183,19 +198,30 @@ class BackgroundMode : Service() {
* Sets the [MediaItem] with the [response] into the [player]. Also creates a [MediaSessionConnector] * Sets the [MediaItem] with the [response] 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(c: Context) { private fun setMediaItem() {
response?.let { response?.let {
val mediaItem = MediaItem.Builder().setUri(it.hls!!).build() val mediaItem = MediaItem.Builder().setUri(it.hls!!).build()
player?.setMediaItem(mediaItem) player?.setMediaItem(mediaItem)
} }
mediaSession = MediaSessionCompat(c, this.javaClass.name) mediaSession = MediaSessionCompat(this, this.javaClass.name)
mediaSession.isActive = true mediaSession.isActive = true
mediaSessionConnector = MediaSessionConnector(mediaSession) mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector.setPlayer(player) mediaSessionConnector.setPlayer(player)
} }
private fun destroyPlayer() {
// clear old player and its notification
playerNotification = null
player = null
// kill old notification
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
notificationManager.cancel(1)
}
override fun onBind(p0: Intent?): IBinder? { override fun onBind(p0: Intent?): IBinder? {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@ -0,0 +1,18 @@
package com.github.libretube.util
import android.content.Context
import android.content.Intent
import com.github.libretube.services.BackgroundMode
object BackgroundHelper {
fun playOnBackground(
context: Context,
videoId: String,
position: Int? = null
) {
val intent = Intent(context, BackgroundMode::class.java)
intent.putExtra("videoId", videoId)
if (position != null) intent.putExtra("position", position)
context.startForegroundService(intent)
}
}

View File

@ -257,4 +257,5 @@
<string name="seekbar_preview_summary">Preview the video by seeking to the position when scrubbing the seekbar.</string> <string name="seekbar_preview_summary">Preview the video by seeking to the position when scrubbing the seekbar.</string>
<string name="general">General</string> <string name="general">General</string>
<string name="general_summary">Language, region</string> <string name="general_summary">Language, region</string>
<string name="playingOnBackground">Playing on background …</string>
</resources> </resources>