refactor: improve watch position handling

This commit is contained in:
Bnyro 2025-01-22 16:45:51 +01:00
parent 6a8de0dc6d
commit f764ef4fc3
8 changed files with 47 additions and 56 deletions

View File

@ -58,16 +58,32 @@ object DatabaseHelper {
while (searchHistory.size > MAX_SEARCH_HISTORY_SIZE) { while (searchHistory.size > MAX_SEARCH_HISTORY_SIZE) {
Database.searchHistoryDao().delete(searchHistory.first()) Database.searchHistoryDao().delete(searchHistory.first())
searchHistory.removeFirst() searchHistory.removeAt(0)
} }
} }
suspend fun isVideoWatched(videoId: String, duration: Long): Boolean = withContext(Dispatchers.IO) { suspend fun getWatchPosition(videoId: String) = Database.watchPositionDao().findById(videoId)?.position
val historyItem = Database.watchPositionDao()
.findById(videoId) ?: return@withContext false fun getWatchPositionBlocking(videoId: String): Long? = runBlocking(Dispatchers.IO) {
val progress = historyItem.position / 1000 getWatchPosition(videoId)
}
fun isVideoWatchedBlocking(videoId: String, duration: Long) =
runBlocking { isVideoWatched(videoId, duration) }
suspend fun isVideoWatched(videoId: String, duration: Long): Boolean =
withContext(Dispatchers.IO) {
val position = getWatchPosition(videoId) ?: return@withContext false
return@withContext isVideoWatched(position, duration)
}
fun isVideoWatched(positionMillis: Long, durationSeconds: Long?): Boolean {
if (durationSeconds == null) return false
val progress = positionMillis / 1000
// show video only in feed when watched less than 90% // show video only in feed when watched less than 90%
return@withContext progress > 0.9f * duration return progress > 0.9f * durationSeconds
} }
suspend fun filterUnwatched(streams: List<StreamItem>): List<StreamItem> { suspend fun filterUnwatched(streams: List<StreamItem>): List<StreamItem> {
@ -85,7 +101,10 @@ object DatabaseHelper {
} }
} }
fun filterByStatusAndWatchPosition(streams: List<StreamItem>, hideWatched: Boolean): List<StreamItem> { fun filterByStatusAndWatchPosition(
streams: List<StreamItem>,
hideWatched: Boolean
): List<StreamItem> {
val streamItems = streams.filter { val streamItems = streams.filter {
val isVideo = !it.isShort && !it.isLive val isVideo = !it.isShort && !it.isLive

View File

@ -49,7 +49,6 @@ import com.github.libretube.util.TextUtils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.Locale import java.util.Locale
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -666,20 +665,6 @@ object PlayerHelper {
} }
} }
fun getStoredWatchPosition(videoId: String, duration: Long?): Long? {
if (duration == null) return null
runCatching {
val watchPosition = runBlocking {
DatabaseHolder.Database.watchPositionDao().findById(videoId)
}
if (watchPosition != null && watchPosition.position < duration * 1000 * 0.9) {
return watchPosition.position
}
}
return null
}
/** /**
* Get the track type string resource corresponding to ExoPlayer role flags used for audio * Get the track type string resource corresponding to ExoPlayer role flags used for audio
* track types. * track types.

View File

@ -230,6 +230,9 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
abstract val isOfflinePlayer: Boolean abstract val isOfflinePlayer: Boolean
abstract val isAudioOnlyPlayer: Boolean abstract val isAudioOnlyPlayer: Boolean
val watchPositionsEnabled get() =
(PlayerHelper.watchPositionsAudio && isAudioOnlyPlayer) || (PlayerHelper.watchPositionsVideo && !isAudioOnlyPlayer)
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? = override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession? =
mediaLibrarySession mediaLibrarySession
@ -313,9 +316,6 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
abstract suspend fun startPlayback() abstract suspend fun startPlayback()
private fun saveWatchPosition() { private fun saveWatchPosition() {
val watchPositionsEnabled =
(PlayerHelper.watchPositionsAudio && isAudioOnlyPlayer) || (PlayerHelper.watchPositionsVideo && !isAudioOnlyPlayer)
if (isTransitioning || !watchPositionsEnabled) return if (isTransitioning || !watchPositionsEnabled) return
exoPlayer?.let { PlayerHelper.saveWatchPosition(it, videoId) } exoPlayer?.let { PlayerHelper.saveWatchPosition(it, videoId) }

View File

@ -7,6 +7,7 @@ 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.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.db.DatabaseHelper
import com.github.libretube.db.DatabaseHolder.Database import com.github.libretube.db.DatabaseHolder.Database
import com.github.libretube.db.obj.DownloadWithItems import com.github.libretube.db.obj.DownloadWithItems
import com.github.libretube.db.obj.filterByTab import com.github.libretube.db.obj.filterByTab
@ -92,9 +93,9 @@ open class OfflinePlayerService : AbstractPlayerService() {
exoPlayer?.playWhenReady = PlayerHelper.playAutomatically exoPlayer?.playWhenReady = PlayerHelper.playAutomatically
exoPlayer?.prepare() exoPlayer?.prepare()
if (PlayerHelper.watchPositionsAudio) { if (watchPositionsEnabled) {
PlayerHelper.getStoredWatchPosition(videoId, downloadWithItems.download.duration)?.let { DatabaseHelper.getWatchPosition(videoId)?.let {
exoPlayer?.seekTo(it) if (!DatabaseHelper.isVideoWatched(it, downloadWithItems.download.duration)) exoPlayer?.seekTo(it)
} }
} }
} }

View File

@ -163,9 +163,9 @@ open class OnlinePlayerService : AbstractPlayerService() {
// seek to the previous position if available // seek to the previous position if available
if (seekToPosition != 0L) { if (seekToPosition != 0L) {
exoPlayer?.seekTo(seekToPosition) exoPlayer?.seekTo(seekToPosition)
} else if (PlayerHelper.watchPositionsAudio) { } else if (watchPositionsEnabled) {
PlayerHelper.getStoredWatchPosition(videoId, streams?.duration)?.let { DatabaseHelper.getWatchPositionBlocking(videoId)?.let {
exoPlayer?.seekTo(it) if (!DatabaseHelper.isVideoWatched(it, streams?.duration)) exoPlayer?.seekTo(it)
} }
} }

View File

@ -10,6 +10,7 @@ import com.github.libretube.R
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.constants.PreferenceKeys import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.DialogShareBinding import com.github.libretube.databinding.DialogShareBinding
import com.github.libretube.db.DatabaseHelper
import com.github.libretube.db.DatabaseHolder.Database import com.github.libretube.db.DatabaseHolder.Database
import com.github.libretube.enums.ShareObjectType import com.github.libretube.enums.ShareObjectType
import com.github.libretube.extensions.parcelable import com.github.libretube.extensions.parcelable
@ -82,7 +83,8 @@ class ShareDialog : DialogFragment() {
binding.timeStamp.addTextChangedListener { binding.timeStamp.addTextChangedListener {
binding.linkPreview.text = generateLinkText(binding, customInstanceUrl) binding.linkPreview.text = generateLinkText(binding, customInstanceUrl)
} }
binding.timeStamp.setText((shareData.currentPosition ?: getWatchPosition(id) ?: 0L).toString()) val timeStamp = shareData.currentPosition ?: DatabaseHelper.getWatchPositionBlocking(id)?.div(1000)
binding.timeStamp.setText((timeStamp ?: 0L).toString())
if (binding.timeCodeSwitch.isChecked) { if (binding.timeCodeSwitch.isChecked) {
binding.timeStampInputLayout.isVisible = true binding.timeStampInputLayout.isVisible = true
} }
@ -145,10 +147,6 @@ class ShareDialog : DialogFragment() {
return url return url
} }
private fun getWatchPosition(videoId: String) = runBlocking {
Database.watchPositionDao().findById(videoId)
}?.position?.div(1000)
companion object { companion object {
const val YOUTUBE_FRONTEND_URL = "https://www.youtube.com" const val YOUTUBE_FRONTEND_URL = "https://www.youtube.com"
const val YOUTUBE_SHORT_URL = "https://youtu.be" const val YOUTUBE_SHORT_URL = "https://youtu.be"

View File

@ -7,9 +7,8 @@ import androidx.core.graphics.ColorUtils
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import com.github.libretube.db.DatabaseHolder.Database import com.github.libretube.db.DatabaseHelper
import com.github.libretube.helpers.ThemeHelper import com.github.libretube.helpers.ThemeHelper
import kotlinx.coroutines.runBlocking
/** /**
* Shows the already watched time under the video * Shows the already watched time under the video
@ -35,13 +34,7 @@ fun View.setWatchProgressLength(videoId: String, duration: Long) {
return return
} }
val progress = runCatching { val progress = DatabaseHelper.getWatchPositionBlocking(videoId)?.div(1000)?.toFloat() ?: 0f
runBlocking {
Database.watchPositionDao().findById(videoId)?.position
// divide by 1000 to convert ms to seconds
?.toFloat()?.div(1000)
}
}.getOrNull() ?: return
updateLayoutParams<ConstraintLayout.LayoutParams> { updateLayoutParams<ConstraintLayout.LayoutParams> {
matchConstraintPercentWidth = progress / duration.toFloat() matchConstraintPercentWidth = progress / duration.toFloat()

View File

@ -142,22 +142,17 @@ class VideoOptionsBottomSheet : BaseBottomSheet() {
// show the mark as watched or unwatched option if watch positions are enabled // show the mark as watched or unwatched option if watch positions are enabled
if (PlayerHelper.watchPositionsAny || PlayerHelper.watchHistoryEnabled) { if (PlayerHelper.watchPositionsAny || PlayerHelper.watchHistoryEnabled) {
val watchPositionEntry = runBlocking(Dispatchers.IO) {
DatabaseHolder.Database.watchPositionDao().findById(videoId)
}
val watchHistoryEntry = runBlocking(Dispatchers.IO) { val watchHistoryEntry = runBlocking(Dispatchers.IO) {
DatabaseHolder.Database.watchHistoryDao().findById(videoId) DatabaseHolder.Database.watchHistoryDao().findById(videoId)
} }
if (streamItem.duration == null || val isWatched = DatabaseHelper.isVideoWatchedBlocking(videoId, streamItem.duration ?: 0)
watchPositionEntry == null || if (isWatched || watchHistoryEntry != null) {
watchPositionEntry.position < streamItem.duration!! * 1000 * 0.9 optionsList += R.string.mark_as_unwatched
) {
optionsList += R.string.mark_as_watched
} }
if (watchHistoryEntry != null || watchPositionEntry != null) { if (!isWatched || watchHistoryEntry == null) {
optionsList += R.string.mark_as_unwatched R.string.mark_as_watched
} }
} }