fix: autoplay countdown continues although other video selected

This commit is contained in:
Bnyro 2024-12-02 20:44:32 +01:00
parent 9458b5597b
commit 40c885316d
5 changed files with 55 additions and 26 deletions

View File

@ -7,8 +7,10 @@ import android.os.Looper
import android.view.KeyEvent import android.view.KeyEvent
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.core.os.bundleOf
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.MediaMetadata
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
@ -22,6 +24,7 @@ import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult import androidx.media3.session.SessionResult
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.Subtitle import com.github.libretube.api.obj.Subtitle
import com.github.libretube.constants.IntentData
import com.github.libretube.enums.PlayerCommand import com.github.libretube.enums.PlayerCommand
import com.github.libretube.enums.PlayerEvent import com.github.libretube.enums.PlayerEvent
import com.github.libretube.extensions.parcelable import com.github.libretube.extensions.parcelable
@ -47,6 +50,8 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
var trackSelector: DefaultTrackSelector? = null var trackSelector: DefaultTrackSelector? = null
lateinit var videoId: String lateinit var videoId: String
private set
var isTransitioning = true var isTransitioning = true
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
@ -170,7 +175,7 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
} }
private fun navigateVideo(videoId: String) { private fun navigateVideo(videoId: String) {
this.videoId = videoId setVideoId(videoId)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
startPlayback() startPlayback()
@ -185,6 +190,28 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
} }
} }
/**
* Update the [videoId] to the new videoId and change the playlist metadata
* to reflect that videoId change
*/
protected fun setVideoId(videoId: String) {
this.videoId = videoId
updatePlaylistMetadata {
setExtras(bundleOf(IntentData.videoId to videoId))
}
}
protected fun updatePlaylistMetadata(updateAction: MediaMetadata.Builder.() -> Unit) {
handler.post {
exoPlayer?.playlistMetadata = MediaMetadata.Builder()
.apply(updateAction)
// send a unique timestamp to notify that the metadata changed, even if playing the same video twice
.setTrackNumber(System.currentTimeMillis().mod(Int.MAX_VALUE))
.build()
}
}
private fun handlePlayerAction(event: PlayerEvent) { private fun handlePlayerAction(event: PlayerEvent) {
if (PlayerHelper.handlePlayerAction(exoPlayer ?: return, event)) return if (PlayerHelper.handlePlayerAction(exoPlayer ?: return, event)) return
@ -303,7 +330,7 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
*/ */
abstract suspend fun startPlayback() abstract suspend fun startPlayback()
fun saveWatchPosition() { private fun saveWatchPosition() {
if (isTransitioning || !PlayerHelper.watchPositionsVideo) return if (isTransitioning || !PlayerHelper.watchPositionsVideo) return
exoPlayer?.let { PlayerHelper.saveWatchPosition(it, videoId) } exoPlayer?.let { PlayerHelper.saveWatchPosition(it, videoId) }

View File

@ -56,13 +56,14 @@ open class OfflinePlayerService : AbstractPlayerService() {
shuffle = args.getBoolean(IntentData.shuffle, false) shuffle = args.getBoolean(IntentData.shuffle, false)
noInternetService = args.getBoolean(IntentData.noInternet, false) noInternetService = args.getBoolean(IntentData.noInternet, false)
videoId = if (shuffle) { val videoId = if (shuffle) {
runBlocking(Dispatchers.IO) { runBlocking(Dispatchers.IO) {
Database.downloadDao().getRandomVideoIdByFileType(FileType.AUDIO) Database.downloadDao().getRandomVideoIdByFileType(FileType.AUDIO)
} }
} else { } else {
args.getString(IntentData.videoId) args.getString(IntentData.videoId)
} ?: return } ?: return
setVideoId(videoId)
PlayingQueue.clear() PlayingQueue.clear()
@ -131,9 +132,7 @@ open class OfflinePlayerService : AbstractPlayerService() {
} }
private fun playNextVideo(videoId: String) { private fun playNextVideo(videoId: String) {
saveWatchPosition() setVideoId(videoId)
this.videoId = videoId
scope.launch { scope.launch {
startPlayback() startPlayback()

View File

@ -6,7 +6,6 @@ import androidx.core.net.toUri
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.SubtitleConfiguration import androidx.media3.common.MediaItem.SubtitleConfiguration
import androidx.media3.common.MediaMetadata
import androidx.media3.common.MimeTypes import androidx.media3.common.MimeTypes
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.datasource.cronet.CronetDataSource import androidx.media3.datasource.cronet.CronetDataSource
@ -106,7 +105,7 @@ open class OnlinePlayerService : AbstractPlayerService() {
} }
// get the intent arguments // get the intent arguments
videoId = playerData.videoId setVideoId(playerData.videoId)
playlistId = playerData.playlistId playlistId = playerData.playlistId
channelId = playerData.channelId channelId = playerData.channelId
startTimestamp = playerData.timestamp startTimestamp = playerData.timestamp
@ -178,8 +177,6 @@ open class OnlinePlayerService : AbstractPlayerService() {
* Plays the next video from the queue * Plays the next video from the queue
*/ */
private fun playNextVideo(nextId: String? = null) { private fun playNextVideo(nextId: String? = null) {
saveWatchPosition()
if (nextId == null) { if (nextId == null) {
if (PlayingQueue.repeatMode == Player.REPEAT_MODE_ONE) { if (PlayingQueue.repeatMode == Player.REPEAT_MODE_ONE) {
exoPlayer?.seekTo(0) exoPlayer?.seekTo(0)
@ -192,7 +189,7 @@ open class OnlinePlayerService : AbstractPlayerService() {
val nextVideo = nextId ?: PlayingQueue.getNext() ?: return val nextVideo = nextId ?: PlayingQueue.getNext() ?: return
// play new video on background // play new video on background
this.videoId = nextVideo setVideoId(nextVideo)
this.streams = null this.streams = null
this.sponsorBlockSegments = emptyList() this.sponsorBlockSegments = emptyList()
@ -214,9 +211,9 @@ open class OnlinePlayerService : AbstractPlayerService() {
).segments ).segments
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
exoPlayer?.playlistMetadata = MediaMetadata.Builder() updatePlaylistMetadata {
.setExtras(bundleOf(IntentData.segments to ArrayList(sponsorBlockSegments))) setExtras(bundleOf(IntentData.segments to ArrayList(sponsorBlockSegments)))
.build() }
checkForSegments() checkForSegments()
} }

View File

@ -305,16 +305,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
super.onMediaMetadataChanged(mediaMetadata) super.onMediaMetadataChanged(mediaMetadata)
mediaMetadata.extras?.getString(IntentData.videoId)?.let {
videoId = it
// fix: if the fragment is recreated, play the current video, and not the initial one
arguments?.run {
val playerData =
parcelable<PlayerData>(IntentData.playerData)!!.copy(videoId = videoId)
putParcelable(IntentData.playerData, playerData)
}
}
val maybeStreams: Streams? = mediaMetadata.extras?.parcelable(IntentData.streams) val maybeStreams: Streams? = mediaMetadata.extras?.parcelable(IntentData.streams)
maybeStreams?.let { streams -> maybeStreams?.let { streams ->
this@PlayerFragment.streams = streams this@PlayerFragment.streams = streams
@ -327,6 +317,18 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
override fun onPlaylistMetadataChanged(mediaMetadata: MediaMetadata) { override fun onPlaylistMetadataChanged(mediaMetadata: MediaMetadata) {
super.onPlaylistMetadataChanged(mediaMetadata) super.onPlaylistMetadataChanged(mediaMetadata)
mediaMetadata.extras?.getString(IntentData.videoId)?.let {
videoId = it
_binding?.autoplayCountdown?.cancelAndHideCountdown()
// fix: if the fragment is recreated, play the current video, and not the initial one
arguments?.run {
val playerData =
parcelable<PlayerData>(IntentData.playerData)!!.copy(videoId = videoId)
putParcelable(IntentData.playerData, playerData)
}
}
val segments: List<Segment>? = mediaMetadata.extras?.parcelableList(IntentData.segments) val segments: List<Segment>? = mediaMetadata.extras?.parcelableList(IntentData.segments)
viewModel.segments.postValue(segments.orEmpty()) viewModel.segments.postValue(segments.orEmpty())
} }

View File

@ -34,8 +34,7 @@ class AutoplayCountdownView(
init { init {
binding.cancel.setOnClickListener { binding.cancel.setOnClickListener {
handler.removeCallbacksAndMessages(TIMER_RUNNABLE_TOKEN) cancelAndHideCountdown()
hideSelf.invoke()
} }
} }
@ -78,6 +77,11 @@ class AutoplayCountdownView(
handler.postDelayed(1000, TIMER_RUNNABLE_TOKEN, this::updateCountdown) handler.postDelayed(1000, TIMER_RUNNABLE_TOKEN, this::updateCountdown)
} }
fun cancelAndHideCountdown() {
handler.removeCallbacksAndMessages(TIMER_RUNNABLE_TOKEN)
hideSelf.invoke()
}
companion object { companion object {
private const val COUNTDOWN_SECONDS = 5 private const val COUNTDOWN_SECONDS = 5
private const val TIMER_RUNNABLE_TOKEN = "timer_runnable" private const val TIMER_RUNNABLE_TOKEN = "timer_runnable"