mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 00:10:32 +05:30
refactor: improve watch position handling
This commit is contained in:
parent
6a8de0dc6d
commit
f764ef4fc3
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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) }
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user