mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 16:00:31 +05:30
feat: audio player UI for downloads
This commit is contained in:
parent
f0fb359b5d
commit
9b68e4faea
@ -48,4 +48,5 @@ object IntentData {
|
|||||||
const val videoList = "videoList"
|
const val videoList = "videoList"
|
||||||
const val nextPage = "nextPage"
|
const val nextPage = "nextPage"
|
||||||
const val videoInfo = "videoInfo"
|
const val videoInfo = "videoInfo"
|
||||||
|
const val offlinePlayer = "offlinePlayer"
|
||||||
}
|
}
|
||||||
|
@ -50,13 +50,9 @@ object BackgroundHelper {
|
|||||||
/**
|
/**
|
||||||
* Stop the [OnlinePlayerService] service if it is running.
|
* Stop the [OnlinePlayerService] service if it is running.
|
||||||
*/
|
*/
|
||||||
fun stopBackgroundPlay(
|
fun stopBackgroundPlay(context: Context) {
|
||||||
context: Context,
|
arrayOf(OnlinePlayerService::class.java, OfflinePlayerService::class.java).forEach {
|
||||||
serviceClass: Class<*> = OnlinePlayerService::class.java
|
val intent = Intent(context, it)
|
||||||
) {
|
|
||||||
if (isBackgroundServiceRunning(context, serviceClass)) {
|
|
||||||
// Intent to stop background mode service
|
|
||||||
val intent = Intent(context, serviceClass)
|
|
||||||
context.stopService(intent)
|
context.stopService(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,10 +76,11 @@ object BackgroundHelper {
|
|||||||
* @param videoId the videoId of the video or null if all available downloads should be shuffled
|
* @param videoId the videoId of the video or null if all available downloads should be shuffled
|
||||||
*/
|
*/
|
||||||
fun playOnBackgroundOffline(context: Context, videoId: String?) {
|
fun playOnBackgroundOffline(context: Context, videoId: String?) {
|
||||||
|
stopBackgroundPlay(context)
|
||||||
|
|
||||||
val playerIntent = Intent(context, OfflinePlayerService::class.java)
|
val playerIntent = Intent(context, OfflinePlayerService::class.java)
|
||||||
.putExtra(IntentData.videoId, videoId)
|
.putExtra(IntentData.videoId, videoId)
|
||||||
|
|
||||||
context.stopService(playerIntent)
|
|
||||||
ContextCompat.startForegroundService(context, playerIntent)
|
ContextCompat.startForegroundService(context, playerIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,10 +96,13 @@ object NavigationHelper {
|
|||||||
/**
|
/**
|
||||||
* Start the audio player fragment
|
* Start the audio player fragment
|
||||||
*/
|
*/
|
||||||
fun startAudioPlayer(context: Context, minimizeByDefault: Boolean = false) {
|
fun startAudioPlayer(context: Context, offlinePlayer: Boolean = false, minimizeByDefault: Boolean = false) {
|
||||||
val activity = ContextHelper.unwrapActivity(context)
|
val activity = ContextHelper.unwrapActivity(context)
|
||||||
activity.supportFragmentManager.commitNow {
|
activity.supportFragmentManager.commitNow {
|
||||||
val args = bundleOf(IntentData.minimizeByDefault to minimizeByDefault)
|
val args = bundleOf(
|
||||||
|
IntentData.minimizeByDefault to minimizeByDefault,
|
||||||
|
IntentData.offlinePlayer to offlinePlayer
|
||||||
|
)
|
||||||
replace<AudioPlayerFragment>(R.id.container, args = args)
|
replace<AudioPlayerFragment>(R.id.container, args = args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,11 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.os.Binder
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
@ -22,6 +24,8 @@ import androidx.media3.exoplayer.ExoPlayer
|
|||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME
|
import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.api.obj.ChapterSegment
|
||||||
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.enums.NotificationId
|
import com.github.libretube.enums.NotificationId
|
||||||
import com.github.libretube.enums.PlayerEvent
|
import com.github.libretube.enums.PlayerEvent
|
||||||
import com.github.libretube.extensions.serializableExtra
|
import com.github.libretube.extensions.serializableExtra
|
||||||
@ -43,6 +47,14 @@ abstract class AbstractPlayerService : LifecycleService() {
|
|||||||
|
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
private val binder = LocalBinder()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for passing playback state changes to the AudioPlayerFragment
|
||||||
|
*/
|
||||||
|
var onStateOrPlayingChanged: ((isPlaying: Boolean) -> Unit)? = null
|
||||||
|
var onNewVideoStarted: ((streamItem: StreamItem) -> Unit)? = null
|
||||||
|
|
||||||
private val watchPositionTimer = PauseableTimer(
|
private val watchPositionTimer = PauseableTimer(
|
||||||
onTick = ::saveWatchPosition,
|
onTick = ::saveWatchPosition,
|
||||||
delayMillis = PlayerHelper.WATCH_POSITION_TIMER_DELAY_MS
|
delayMillis = PlayerHelper.WATCH_POSITION_TIMER_DELAY_MS
|
||||||
@ -58,11 +70,15 @@ abstract class AbstractPlayerService : LifecycleService() {
|
|||||||
} else {
|
} else {
|
||||||
watchPositionTimer.pause()
|
watchPositionTimer.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onStateOrPlayingChanged?.let { it(isPlaying) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
super.onPlaybackStateChanged(playbackState)
|
super.onPlaybackStateChanged(playbackState)
|
||||||
|
|
||||||
|
onStateOrPlayingChanged?.let { it(player?.isPlaying ?: false) }
|
||||||
|
|
||||||
this@AbstractPlayerService.onPlaybackStateChanged(playbackState)
|
this@AbstractPlayerService.onPlaybackStateChanged(playbackState)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,8 +178,7 @@ abstract class AbstractPlayerService : LifecycleService() {
|
|||||||
|
|
||||||
nowPlayingNotification = NowPlayingNotification(
|
nowPlayingNotification = NowPlayingNotification(
|
||||||
this,
|
this,
|
||||||
player!!,
|
player!!
|
||||||
NowPlayingNotification.Companion.NowPlayingNotificationType.AUDIO_OFFLINE
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,11 +217,6 @@ abstract class AbstractPlayerService : LifecycleService() {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
super.onBind(intent)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the service when app is removed from the task manager.
|
* Stop the service when app is removed from the task manager.
|
||||||
*/
|
*/
|
||||||
@ -217,9 +227,21 @@ abstract class AbstractPlayerService : LifecycleService() {
|
|||||||
|
|
||||||
abstract fun onPlaybackStateChanged(playbackState: Int)
|
abstract fun onPlaybackStateChanged(playbackState: Int)
|
||||||
|
|
||||||
|
abstract fun getChapters(): List<ChapterSegment>
|
||||||
|
|
||||||
fun getCurrentPosition() = player?.currentPosition
|
fun getCurrentPosition() = player?.currentPosition
|
||||||
|
|
||||||
fun getDuration() = player?.duration
|
fun getDuration() = player?.duration
|
||||||
|
|
||||||
fun seekToPosition(position: Long) = player?.seekTo(position)
|
fun seekToPosition(position: Long) = player?.seekTo(position)
|
||||||
|
|
||||||
|
inner class LocalBinder : Binder() {
|
||||||
|
// Return this instance of [AbstractPlayerService] so clients can call public methods
|
||||||
|
fun getService(): AbstractPlayerService = this@AbstractPlayerService
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder {
|
||||||
|
super.onBind(intent)
|
||||||
|
return binder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
package com.github.libretube.services
|
package com.github.libretube.services
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.util.Log
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import com.github.libretube.api.obj.ChapterSegment
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.db.DatabaseHolder.Database
|
import com.github.libretube.db.DatabaseHolder.Database
|
||||||
|
import com.github.libretube.db.obj.DownloadChapter
|
||||||
|
import com.github.libretube.db.obj.DownloadWithItems
|
||||||
import com.github.libretube.enums.FileType
|
import com.github.libretube.enums.FileType
|
||||||
import com.github.libretube.extensions.toAndroidUri
|
import com.github.libretube.extensions.toAndroidUri
|
||||||
import com.github.libretube.extensions.toID
|
import com.github.libretube.extensions.toID
|
||||||
@ -24,6 +27,8 @@ import kotlin.io.path.exists
|
|||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
class OfflinePlayerService : AbstractPlayerService() {
|
class OfflinePlayerService : AbstractPlayerService() {
|
||||||
|
private var downloadWithItems: DownloadWithItems? = null
|
||||||
|
|
||||||
override suspend fun onServiceCreated(intent: Intent) {
|
override suspend fun onServiceCreated(intent: Intent) {
|
||||||
videoId = intent.getStringExtra(IntentData.videoId) ?: return
|
videoId = intent.getStringExtra(IntentData.videoId) ?: return
|
||||||
|
|
||||||
@ -43,6 +48,8 @@ class OfflinePlayerService : AbstractPlayerService() {
|
|||||||
val downloadWithItems = withContext(Dispatchers.IO) {
|
val downloadWithItems = withContext(Dispatchers.IO) {
|
||||||
Database.downloadDao().findById(videoId)
|
Database.downloadDao().findById(videoId)
|
||||||
}
|
}
|
||||||
|
this.downloadWithItems = downloadWithItems
|
||||||
|
onNewVideoStarted?.let { it(downloadWithItems.download.toStreamItem()) }
|
||||||
|
|
||||||
PlayingQueue.updateCurrent(downloadWithItems.download.toStreamItem())
|
PlayingQueue.updateCurrent(downloadWithItems.download.toStreamItem())
|
||||||
|
|
||||||
@ -96,11 +103,6 @@ class OfflinePlayerService : AbstractPlayerService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
super.onBind(intent)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the service when app is removed from the task manager.
|
* Stop the service when app is removed from the task manager.
|
||||||
*/
|
*/
|
||||||
@ -115,4 +117,7 @@ class OfflinePlayerService : AbstractPlayerService() {
|
|||||||
playNextVideo(PlayingQueue.getNext() ?: return)
|
playNextVideo(PlayingQueue.getNext() ?: return)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getChapters(): List<ChapterSegment> =
|
||||||
|
downloadWithItems?.downloadChapters.orEmpty().map(DownloadChapter::toChapterSegment)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package com.github.libretube.services
|
package com.github.libretube.services
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Binder
|
|
||||||
import android.os.IBinder
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
@ -11,6 +9,7 @@ import androidx.media3.common.Player
|
|||||||
import com.github.libretube.api.JsonHelper
|
import com.github.libretube.api.JsonHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.api.StreamsExtractor
|
import com.github.libretube.api.StreamsExtractor
|
||||||
|
import com.github.libretube.api.obj.ChapterSegment
|
||||||
import com.github.libretube.api.obj.Segment
|
import com.github.libretube.api.obj.Segment
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
@ -54,17 +53,6 @@ class OnlinePlayerService : AbstractPlayerService() {
|
|||||||
private var segments = listOf<Segment>()
|
private var segments = listOf<Segment>()
|
||||||
private var sponsorBlockConfig = PlayerHelper.getSponsorBlockCategories()
|
private var sponsorBlockConfig = PlayerHelper.getSponsorBlockCategories()
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for connecting to the AudioPlayerFragment
|
|
||||||
*/
|
|
||||||
private val binder = LocalBinder()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener for passing playback state changes to the AudioPlayerFragment
|
|
||||||
*/
|
|
||||||
var onStateOrPlayingChanged: ((isPlaying: Boolean) -> Unit)? = null
|
|
||||||
var onNewVideo: ((streams: Streams, videoId: String) -> Unit)? = null
|
|
||||||
|
|
||||||
override suspend fun onServiceCreated(intent: Intent) {
|
override suspend fun onServiceCreated(intent: Intent) {
|
||||||
val playerData = intent.parcelableExtra<PlayerData>(IntentData.playerData)
|
val playerData = intent.parcelableExtra<PlayerData>(IntentData.playerData)
|
||||||
if (playerData == null) {
|
if (playerData == null) {
|
||||||
@ -143,7 +131,7 @@ class OnlinePlayerService : AbstractPlayerService() {
|
|||||||
streams?.thumbnailUrl
|
streams?.thumbnailUrl
|
||||||
)
|
)
|
||||||
nowPlayingNotification?.updatePlayerNotification(videoId, playerNotificationData)
|
nowPlayingNotification?.updatePlayerNotification(videoId, playerNotificationData)
|
||||||
streams?.let { onNewVideo?.invoke(it, videoId) }
|
streams?.let { onNewVideoStarted?.invoke(it.toStreamItem(videoId)) }
|
||||||
|
|
||||||
player?.apply {
|
player?.apply {
|
||||||
playWhenReady = PlayerHelper.playAutomatically
|
playWhenReady = PlayerHelper.playAutomatically
|
||||||
@ -225,19 +213,7 @@ class OnlinePlayerService : AbstractPlayerService() {
|
|||||||
player?.checkForSegments(this, segments, sponsorBlockConfig)
|
player?.checkForSegments(this, segments, sponsorBlockConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class LocalBinder : Binder() {
|
|
||||||
// Return this instance of [BackgroundMode] so clients can call public methods
|
|
||||||
fun getService(): OnlinePlayerService = this@OnlinePlayerService
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder {
|
|
||||||
super.onBind(intent)
|
|
||||||
return binder
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
onStateOrPlayingChanged?.invoke(player?.isPlaying ?: false)
|
|
||||||
|
|
||||||
when (playbackState) {
|
when (playbackState) {
|
||||||
Player.STATE_ENDED -> {
|
Player.STATE_ENDED -> {
|
||||||
if (!isTransitioning) playNextVideo()
|
if (!isTransitioning) playNextVideo()
|
||||||
@ -260,4 +236,6 @@ class OnlinePlayerService : AbstractPlayerService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getChapters(): List<ChapterSegment> = streams?.chapters.orEmpty()
|
||||||
}
|
}
|
||||||
|
@ -217,8 +217,7 @@ class OfflinePlayerActivity : BaseActivity() {
|
|||||||
|
|
||||||
nowPlayingNotification = NowPlayingNotification(
|
nowPlayingNotification = NowPlayingNotification(
|
||||||
this,
|
this,
|
||||||
viewModel.player,
|
viewModel.player
|
||||||
NowPlayingNotification.Companion.NowPlayingNotificationType.VIDEO_OFFLINE
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,10 +3,13 @@ package com.github.libretube.ui.adapters
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.core.os.postDelayed
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@ -18,6 +21,7 @@ import com.github.libretube.db.obj.DownloadWithItems
|
|||||||
import com.github.libretube.extensions.formatAsFileSize
|
import com.github.libretube.extensions.formatAsFileSize
|
||||||
import com.github.libretube.helpers.BackgroundHelper
|
import com.github.libretube.helpers.BackgroundHelper
|
||||||
import com.github.libretube.helpers.ImageHelper
|
import com.github.libretube.helpers.ImageHelper
|
||||||
|
import com.github.libretube.helpers.NavigationHelper
|
||||||
import com.github.libretube.ui.activities.OfflinePlayerActivity
|
import com.github.libretube.ui.activities.OfflinePlayerActivity
|
||||||
import com.github.libretube.ui.base.BaseActivity
|
import com.github.libretube.ui.base.BaseActivity
|
||||||
import com.github.libretube.ui.fragments.DownloadTab
|
import com.github.libretube.ui.fragments.DownloadTab
|
||||||
@ -38,6 +42,8 @@ class DownloadsAdapter(
|
|||||||
private val downloads: MutableList<DownloadWithItems>,
|
private val downloads: MutableList<DownloadWithItems>,
|
||||||
private val toggleDownload: (DownloadWithItems) -> Boolean
|
private val toggleDownload: (DownloadWithItems) -> Boolean
|
||||||
) : RecyclerView.Adapter<DownloadsViewHolder>() {
|
) : RecyclerView.Adapter<DownloadsViewHolder>() {
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadsViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadsViewHolder {
|
||||||
val binding = DownloadedMediaRowBinding.inflate(
|
val binding = DownloadedMediaRowBinding.inflate(
|
||||||
LayoutInflater.from(parent.context),
|
LayoutInflater.from(parent.context),
|
||||||
@ -107,6 +113,7 @@ class DownloadsAdapter(
|
|||||||
root.context.startActivity(intent)
|
root.context.startActivity(intent)
|
||||||
} else {
|
} else {
|
||||||
BackgroundHelper.playOnBackgroundOffline(root.context, download.videoId)
|
BackgroundHelper.playOnBackgroundOffline(root.context, download.videoId)
|
||||||
|
NavigationHelper.startAudioPlayer(root.context, offlinePlayer = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package com.github.libretube.ui.fragments
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
@ -11,6 +10,7 @@ import android.os.Handler
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -23,6 +23,7 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
@ -40,6 +41,8 @@ import com.github.libretube.helpers.NavBarHelper
|
|||||||
import com.github.libretube.helpers.NavigationHelper
|
import com.github.libretube.helpers.NavigationHelper
|
||||||
import com.github.libretube.helpers.PlayerHelper
|
import com.github.libretube.helpers.PlayerHelper
|
||||||
import com.github.libretube.helpers.ThemeHelper
|
import com.github.libretube.helpers.ThemeHelper
|
||||||
|
import com.github.libretube.services.AbstractPlayerService
|
||||||
|
import com.github.libretube.services.OfflinePlayerService
|
||||||
import com.github.libretube.services.OnlinePlayerService
|
import com.github.libretube.services.OnlinePlayerService
|
||||||
import com.github.libretube.ui.activities.MainActivity
|
import com.github.libretube.ui.activities.MainActivity
|
||||||
import com.github.libretube.ui.interfaces.AudioPlayerOptions
|
import com.github.libretube.ui.interfaces.AudioPlayerOptions
|
||||||
@ -56,6 +59,7 @@ import com.github.libretube.util.PlayingQueue
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
|
class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
|
||||||
private var _binding: FragmentAudioPlayerBinding? = null
|
private var _binding: FragmentAudioPlayerBinding? = null
|
||||||
val binding get() = _binding!!
|
val binding get() = _binding!!
|
||||||
@ -72,13 +76,13 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
|
|||||||
private var handler = Handler(Looper.getMainLooper())
|
private var handler = Handler(Looper.getMainLooper())
|
||||||
private var isPaused = !PlayerHelper.playAutomatically
|
private var isPaused = !PlayerHelper.playAutomatically
|
||||||
|
|
||||||
private var playerService: OnlinePlayerService? = null
|
private var playerService: AbstractPlayerService? = null
|
||||||
|
|
||||||
/** Defines callbacks for service binding, passed to bindService() */
|
/** Defines callbacks for service binding, passed to bindService() */
|
||||||
private val connection = object : ServiceConnection {
|
private val connection = object : ServiceConnection {
|
||||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||||
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||||
val binder = service as OnlinePlayerService.LocalBinder
|
val binder = service as AbstractPlayerService.LocalBinder
|
||||||
playerService = binder.getService()
|
playerService = binder.getService()
|
||||||
handleServiceConnection()
|
handleServiceConnection()
|
||||||
}
|
}
|
||||||
@ -90,8 +94,14 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
audioHelper = AudioHelper(requireContext())
|
audioHelper = AudioHelper(requireContext())
|
||||||
Intent(activity, OnlinePlayerService::class.java).also { intent ->
|
|
||||||
activity?.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
val isOffline = requireArguments().getBoolean(IntentData.offlinePlayer)
|
||||||
|
|
||||||
|
val serviceClass =
|
||||||
|
if (isOffline) OfflinePlayerService::class.java else OnlinePlayerService::class.java
|
||||||
|
Log.e("class", serviceClass.name.toString())
|
||||||
|
Intent(activity, serviceClass).also { intent ->
|
||||||
|
activity?.bindService(intent, connection, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +198,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
|
|||||||
|
|
||||||
binding.openChapters.setOnClickListener {
|
binding.openChapters.setOnClickListener {
|
||||||
val playerService = playerService ?: return@setOnClickListener
|
val playerService = playerService ?: return@setOnClickListener
|
||||||
chaptersModel.chaptersLiveData.value = playerService.streams?.chapters.orEmpty()
|
chaptersModel.chaptersLiveData.value = playerService.getChapters()
|
||||||
|
|
||||||
ChaptersBottomSheet()
|
ChaptersBottomSheet()
|
||||||
.apply {
|
.apply {
|
||||||
@ -380,11 +390,15 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
|
|||||||
updatePlayPauseButton()
|
updatePlayPauseButton()
|
||||||
isPaused = !isPlaying
|
isPaused = !isPlaying
|
||||||
}
|
}
|
||||||
playerService?.onNewVideo = { streams, videoId ->
|
playerService?.onNewVideoStarted = { streamItem ->
|
||||||
updateStreamInfo(streams.toStreamItem(videoId))
|
updateStreamInfo(streamItem)
|
||||||
_binding?.openChapters?.isVisible = streams.chapters.isNotEmpty()
|
_binding?.openChapters?.isVisible = !playerService?.getChapters().isNullOrEmpty()
|
||||||
}
|
}
|
||||||
initializeSeekBar()
|
initializeSeekBar()
|
||||||
|
|
||||||
|
if (playerService is OfflinePlayerService) {
|
||||||
|
binding.openVideo.isGone = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@ -462,7 +476,8 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
|
|||||||
|
|
||||||
val player = playerService?.player ?: return
|
val player = playerService?.player ?: return
|
||||||
|
|
||||||
val currentIndex = PlayerHelper.getCurrentChapterIndex(player.currentPosition, chaptersModel.chapters)
|
val currentIndex =
|
||||||
|
PlayerHelper.getCurrentChapterIndex(player.currentPosition, chaptersModel.chapters)
|
||||||
chaptersModel.currentChapterIndex.updateIfChanged(currentIndex ?: return)
|
chaptersModel.currentChapterIndex.updateIfChanged(currentIndex ?: return)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1358,8 +1358,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
if (viewModel.nowPlayingNotification == null) {
|
if (viewModel.nowPlayingNotification == null) {
|
||||||
viewModel.nowPlayingNotification = NowPlayingNotification(
|
viewModel.nowPlayingNotification = NowPlayingNotification(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
viewModel.player,
|
viewModel.player
|
||||||
NowPlayingNotification.Companion.NowPlayingNotificationType.VIDEO_ONLINE
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val playerNotificationData = PlayerNotificationData(
|
val playerNotificationData = PlayerNotificationData(
|
||||||
|
@ -58,7 +58,7 @@ class VideoOptionsBottomSheet : BaseBottomSheet() {
|
|||||||
// Start the background mode
|
// Start the background mode
|
||||||
R.string.playOnBackground -> {
|
R.string.playOnBackground -> {
|
||||||
BackgroundHelper.playOnBackground(requireContext(), videoId)
|
BackgroundHelper.playOnBackground(requireContext(), videoId)
|
||||||
NavigationHelper.startAudioPlayer(requireContext(), true)
|
NavigationHelper.startAudioPlayer(requireContext(), minimizeByDefault = true)
|
||||||
}
|
}
|
||||||
// Add Video to Playlist Dialog
|
// Add Video to Playlist Dialog
|
||||||
R.string.addToPlaylist -> {
|
R.string.addToPlaylist -> {
|
||||||
|
@ -13,12 +13,10 @@ import androidx.annotation.DrawableRes
|
|||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.PendingIntentCompat
|
import androidx.core.app.PendingIntentCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
|
||||||
import androidx.media.app.NotificationCompat.MediaStyle
|
import androidx.media.app.NotificationCompat.MediaStyle
|
||||||
import androidx.media3.common.MediaMetadata
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import coil.request.ImageRequest
|
|
||||||
import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME
|
import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
@ -35,8 +33,7 @@ import java.util.UUID
|
|||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
class NowPlayingNotification(
|
class NowPlayingNotification(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val player: ExoPlayer,
|
private val player: ExoPlayer
|
||||||
private val notificationType: NowPlayingNotificationType
|
|
||||||
) {
|
) {
|
||||||
private var videoId: String? = null
|
private var videoId: String? = null
|
||||||
private val nManager = context.getSystemService<NotificationManager>()!!
|
private val nManager = context.getSystemService<NotificationManager>()!!
|
||||||
@ -77,11 +74,9 @@ class NowPlayingNotification(
|
|||||||
// is set to "singleTop" in the AndroidManifest (important!!!)
|
// is set to "singleTop" in the AndroidManifest (important!!!)
|
||||||
// that's the only way to launch back into the previous activity (e.g. the player view
|
// that's the only way to launch back into the previous activity (e.g. the player view
|
||||||
val intent = Intent(context, MainActivity::class.java).apply {
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
if (notificationType == NowPlayingNotificationType.AUDIO_ONLINE) {
|
|
||||||
putExtra(IntentData.openAudioPlayer, true)
|
putExtra(IntentData.openAudioPlayer, true)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return PendingIntentCompat
|
return PendingIntentCompat
|
||||||
.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, false)
|
.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, false)
|
||||||
@ -355,13 +350,4 @@ class NowPlayingNotification(
|
|||||||
fun refreshNotification() {
|
fun refreshNotification() {
|
||||||
createOrUpdateNotification()
|
createOrUpdateNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
enum class NowPlayingNotificationType {
|
|
||||||
VIDEO_ONLINE,
|
|
||||||
VIDEO_OFFLINE,
|
|
||||||
AUDIO_ONLINE,
|
|
||||||
AUDIO_OFFLINE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user