Merge pull request #667 from Bnyro/master

remember watch positions
This commit is contained in:
Bnyro 2022-07-02 19:04:42 +02:00 committed by GitHub
commit 3cf149be97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 34 deletions

View File

@ -34,6 +34,7 @@ import com.github.libretube.activities.hideKeyboard
import com.github.libretube.adapters.ChaptersAdapter import com.github.libretube.adapters.ChaptersAdapter
import com.github.libretube.adapters.CommentsAdapter import com.github.libretube.adapters.CommentsAdapter
import com.github.libretube.adapters.TrendingAdapter import com.github.libretube.adapters.TrendingAdapter
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
import com.github.libretube.databinding.FragmentPlayerBinding import com.github.libretube.databinding.FragmentPlayerBinding
import com.github.libretube.dialogs.AddtoPlaylistDialog import com.github.libretube.dialogs.AddtoPlaylistDialog
import com.github.libretube.dialogs.DownloadDialog import com.github.libretube.dialogs.DownloadDialog
@ -93,6 +94,7 @@ class PlayerFragment : Fragment() {
private val TAG = "PlayerFragment" private val TAG = "PlayerFragment"
private lateinit var binding: FragmentPlayerBinding private lateinit var binding: FragmentPlayerBinding
private lateinit var playerBinding: ExoStyledPlayerControlViewBinding
private var videoId: String? = null private var videoId: String? = null
private var playlistId: String? = null private var playlistId: String? = null
@ -145,6 +147,7 @@ class PlayerFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentPlayerBinding.inflate(layoutInflater, container, false) binding = FragmentPlayerBinding.inflate(layoutInflater, container, false)
playerBinding = binding.player.binding
// Inflate the layout for this fragment // Inflate the layout for this fragment
return binding.root return binding.root
} }
@ -227,7 +230,7 @@ class PlayerFragment : Fragment() {
.remove(this) .remove(this)
.commit() .commit()
} }
binding.player.binding.closeImageButton.setOnClickListener { playerBinding.closeImageButton.setOnClickListener {
isMiniPlayerVisible = false isMiniPlayerVisible = false
binding.playerMotionLayout.transitionToEnd() binding.playerMotionLayout.transitionToEnd()
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
@ -259,11 +262,8 @@ class PlayerFragment : Fragment() {
toggleComments() toggleComments()
} }
val fullScreenButton = binding.player.binding.fullscreen
val exoTitle = binding.player.binding.exoTitle
// FullScreen button trigger // FullScreen button trigger
fullScreenButton.setOnClickListener { playerBinding.fullscreen.setOnClickListener {
exoPlayerView.hideController() exoPlayerView.hideController()
if (!isFullScreen) { if (!isFullScreen) {
with(binding.playerMotionLayout) { with(binding.playerMotionLayout) {
@ -273,8 +273,8 @@ class PlayerFragment : Fragment() {
binding.mainContainer.isClickable = true binding.mainContainer.isClickable = true
binding.linLayout.visibility = View.GONE binding.linLayout.visibility = View.GONE
fullScreenButton.setImageResource(R.drawable.ic_fullscreen_exit) playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen_exit)
exoTitle.visibility = View.VISIBLE playerBinding.exoTitle.visibility = View.VISIBLE
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
@ -286,8 +286,8 @@ class PlayerFragment : Fragment() {
binding.mainContainer.isClickable = false binding.mainContainer.isClickable = false
binding.linLayout.visibility = View.VISIBLE binding.linLayout.visibility = View.VISIBLE
fullScreenButton.setImageResource(R.drawable.ic_fullscreen) playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen)
exoTitle.visibility = View.INVISIBLE playerBinding.exoTitle.visibility = View.INVISIBLE
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
@ -296,8 +296,7 @@ class PlayerFragment : Fragment() {
} }
// switching between original aspect ratio (black bars) and zoomed to fill device screen // switching between original aspect ratio (black bars) and zoomed to fill device screen
val aspectRatioButton = binding.player.binding.aspectRatioButton playerBinding.aspectRatioButton.setOnClickListener {
aspectRatioButton.setOnClickListener {
if (isZoomed) { if (isZoomed) {
exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
isZoomed = false isZoomed = false
@ -308,13 +307,12 @@ class PlayerFragment : Fragment() {
} }
// lock and unlock the player // lock and unlock the player
val lockPlayerButton = binding.player.binding.lockPlayer playerBinding.lockPlayer.setOnClickListener {
lockPlayerButton.setOnClickListener {
// change the locked/unlocked icon // change the locked/unlocked icon
if (!isPlayerLocked) { if (!isPlayerLocked) {
lockPlayerButton.setImageResource(R.drawable.ic_locked) playerBinding.lockPlayer.setImageResource(R.drawable.ic_locked)
} else { } else {
lockPlayerButton.setImageResource(R.drawable.ic_unlocked) playerBinding.lockPlayer.setImageResource(R.drawable.ic_unlocked)
} }
// show/hide all the controls // show/hide all the controls
@ -373,6 +371,7 @@ class PlayerFragment : Fragment() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
try { try {
saveWatchPosition()
mediaSession.isActive = false mediaSession.isActive = false
mediaSession.release() mediaSession.release()
mediaSessionConnector.setPlayer(null) mediaSessionConnector.setPlayer(null)
@ -386,6 +385,25 @@ class PlayerFragment : Fragment() {
} }
} }
// save the watch position if video isn't finished and option enabled
private fun saveWatchPosition() {
val watchPositionsEnabled = PreferenceHelper.getBoolean(
requireContext(),
"watch_positions_toggle",
true
)
if (watchPositionsEnabled && exoPlayer.currentPosition != exoPlayer.duration) {
PreferenceHelper.saveWatchPosition(
requireContext(),
videoId!!,
exoPlayer.currentPosition
)
} else if (watchPositionsEnabled) {
// delete watch position if video has ended
PreferenceHelper.removeWatchPosition(requireContext(), videoId!!)
}
}
private fun checkForSegments() { private fun checkForSegments() {
if (!exoPlayer.isPlaying || !sponsorBlockPrefs.sponsorBlockEnabled) return if (!exoPlayer.isPlaying || !sponsorBlockPrefs.sponsorBlockEnabled) return
@ -447,6 +465,7 @@ class PlayerFragment : Fragment() {
val position = arguments?.getLong("timeStamp")!! * 1000 val position = arguments?.getLong("timeStamp")!! * 1000
exoPlayer.seekTo(position) exoPlayer.seekTo(position)
} }
seekToWatchPosition()
exoPlayer.play() exoPlayer.play()
exoPlayerView.useController = true exoPlayerView.useController = true
initializePlayerNotification(requireContext()) initializePlayerNotification(requireContext())
@ -466,6 +485,14 @@ class PlayerFragment : Fragment() {
run() run()
} }
// seek to saved watch position if available
private fun seekToWatchPosition() {
val watchPositions = PreferenceHelper.getWatchPositions(requireContext())
watchPositions.forEach {
if (it.videoId == videoId) exoPlayer.seekTo(it.position)
}
}
// the function is working recursively // the function is working recursively
private fun initAutoPlay() { private fun initAutoPlay() {
// save related streams for autoplay // save related streams for autoplay
@ -629,7 +656,7 @@ class PlayerFragment : Fragment() {
binding.playerTitle.text = response.title binding.playerTitle.text = response.title
binding.playerDescription.text = response.description binding.playerDescription.text = response.description
binding.player.binding.exoTitle.text = response.title playerBinding.exoTitle.text = response.title
// Listener for play and pause icon change // Listener for play and pause icon change
exoPlayer.addListener(object : Player.Listener { exoPlayer.addListener(object : Player.Listener {
@ -801,9 +828,6 @@ class PlayerFragment : Fragment() {
PreferenceHelper.getString(requireContext(), "player_video_format", "WEBM") PreferenceHelper.getString(requireContext(), "player_video_format", "WEBM")
val defres = PreferenceHelper.getString(requireContext(), "default_res", "")!! val defres = PreferenceHelper.getString(requireContext(), "default_res", "")!!
val qualityText = binding.player.binding.qualityText
val qualitySelect = binding.player.binding.qualitySelect
var videosNameArray: Array<CharSequence> = arrayOf() var videosNameArray: Array<CharSequence> = arrayOf()
var videosUrlArray: Array<Uri> = arrayOf() var videosUrlArray: Array<Uri> = arrayOf()
@ -843,7 +867,7 @@ class PlayerFragment : Fragment() {
val videoUri = videosUrlArray[index] val videoUri = videosUrlArray[index]
val audioUrl = getMostBitRate(response.audioStreams!!) val audioUrl = getMostBitRate(response.audioStreams!!)
setMediaSource(subtitle, videoUri, audioUrl) setMediaSource(subtitle, videoUri, audioUrl)
qualityText.text = videosNameArray[index] playerBinding.qualityText.text = videosNameArray[index]
return@lit return@lit
} else if (response.hls != null) { } else if (response.hls != null) {
val mediaItem: MediaItem = MediaItem.Builder() val mediaItem: MediaItem = MediaItem.Builder()
@ -874,11 +898,11 @@ class PlayerFragment : Fragment() {
val videoUri = videosUrlArray[0] val videoUri = videosUrlArray[0]
val audioUrl = getMostBitRate(response.audioStreams!!) val audioUrl = getMostBitRate(response.audioStreams!!)
setMediaSource(subtitle, videoUri, audioUrl) setMediaSource(subtitle, videoUri, audioUrl)
qualityText.text = videosNameArray[0] playerBinding.qualityText.text = videosNameArray[0]
} }
} }
qualitySelect.setOnClickListener { playerBinding.qualitySelect.setOnClickListener {
// Dialog for quality selection // Dialog for quality selection
val builder: MaterialAlertDialogBuilder? = activity?.let { val builder: MaterialAlertDialogBuilder? = activity?.let {
MaterialAlertDialogBuilder(it) MaterialAlertDialogBuilder(it)
@ -905,7 +929,7 @@ class PlayerFragment : Fragment() {
setMediaSource(subtitle, videoUri, audioUrl) setMediaSource(subtitle, videoUri, audioUrl)
} }
exoPlayer.seekTo(lastPosition) exoPlayer.seekTo(lastPosition)
qualityText.text = videosNameArray[which] playerBinding.qualityText.text = videosNameArray[which]
} }
val dialog = builder.create() val dialog = builder.create()
dialog.show() dialog.show()
@ -986,12 +1010,12 @@ class PlayerFragment : Fragment() {
private fun lockPlayer(isLocked: Boolean) { private fun lockPlayer(isLocked: Boolean) {
val visibility = if (isLocked) View.VISIBLE else View.INVISIBLE val visibility = if (isLocked) View.VISIBLE else View.INVISIBLE
binding.player.binding.exoTopBarRight.visibility = visibility playerBinding.exoTopBarRight.visibility = visibility
binding.player.binding.exoPlayPause.visibility = visibility playerBinding.exoPlayPause.visibility = visibility
binding.player.binding.exoFfwdWithAmount.visibility = visibility playerBinding.exoFfwdWithAmount.visibility = visibility
binding.player.binding.exoRewWithAmount.visibility = visibility playerBinding.exoRewWithAmount.visibility = visibility
binding.player.binding.exoBottomBar.visibility = visibility playerBinding.exoBottomBar.visibility = visibility
binding.player.binding.exoTitle.visibility = playerBinding.exoTitle.visibility =
if (isLocked && isFullScreen) View.VISIBLE else View.INVISIBLE if (isLocked && isFullScreen) View.VISIBLE else View.INVISIBLE
} }
@ -1153,7 +1177,7 @@ class PlayerFragment : Fragment() {
enableTransition(R.id.yt_transition, false) enableTransition(R.id.yt_transition, false)
} }
binding.mainContainer.isClickable = true binding.mainContainer.isClickable = true
binding.player.binding.exoTopBar.visibility = View.GONE playerBinding.exoTopBar.visibility = View.GONE
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
isFullScreen = false isFullScreen = false
@ -1165,7 +1189,7 @@ class PlayerFragment : Fragment() {
exoPlayerView.showController() exoPlayerView.showController()
exoPlayerView.useController = true exoPlayerView.useController = true
binding.mainContainer.isClickable = false binding.mainContainer.isClickable = false
binding.player.binding.exoTopBar.visibility = View.VISIBLE playerBinding.exoTopBar.visibility = View.VISIBLE
} }
} }

View File

@ -0,0 +1,6 @@
package com.github.libretube.obj
data class WatchPosition(
val videoId: String,
val position: Long
)

View File

@ -25,10 +25,11 @@ class AdvancedSettings : PreferenceFragmentCompat() {
true true
} }
// clear watch history // clear watch history and positions
val clearWatchHistory = findPreference<Preference>("clear_watch_history") val clearWatchHistory = findPreference<Preference>("clear_watch_history")
clearWatchHistory?.setOnPreferenceClickListener { clearWatchHistory?.setOnPreferenceClickListener {
PreferenceHelper.removePreference(requireContext(), "watch_history") PreferenceHelper.removePreference(requireContext(), "watch_history")
PreferenceHelper.removePreference(requireContext(), "watch_positions")
true true
} }

View File

@ -6,6 +6,7 @@ import androidx.preference.PreferenceManager
import com.github.libretube.obj.CustomInstance import com.github.libretube.obj.CustomInstance
import com.github.libretube.obj.Streams import com.github.libretube.obj.Streams
import com.github.libretube.obj.WatchHistoryItem import com.github.libretube.obj.WatchHistoryItem
import com.github.libretube.obj.WatchPosition
import com.google.common.reflect.TypeToken import com.google.common.reflect.TypeToken
import com.google.gson.Gson import com.google.gson.Gson
import java.lang.reflect.Type import java.lang.reflect.Type
@ -144,11 +145,11 @@ object PreferenceHelper {
val watchHistory = getWatchHistory(context) val watchHistory = getWatchHistory(context)
// delete entries that have the same videoId // delete entries that have the same videoId
var indexToRemove = Int.MAX_VALUE var indexToRemove: Int? = null
watchHistory.forEachIndexed { index, item -> watchHistory.forEachIndexed { index, item ->
if (item.videoId == videoId) indexToRemove = index if (item.videoId == videoId) indexToRemove = index
} }
if (indexToRemove != Int.MAX_VALUE) watchHistory.removeAt(indexToRemove) if (indexToRemove != null) watchHistory.removeAt(indexToRemove!!)
watchHistory += watchHistoryItem watchHistory += watchHistoryItem
@ -168,6 +169,55 @@ object PreferenceHelper {
} }
} }
fun saveWatchPosition(context: Context, videoId: String, position: Long) {
val editor = getDefaultSharedPreferencesEditor(context)
val watchPositions = getWatchPositions(context)
val watchPositionItem = WatchPosition(videoId, position)
var indexToRemove: Int? = null
watchPositions.forEachIndexed { index, item ->
if (item.videoId == videoId) indexToRemove = index
}
if (indexToRemove != null) watchPositions.removeAt(indexToRemove!!)
watchPositions += watchPositionItem
val gson = Gson()
val json = gson.toJson(watchPositions)
editor.putString("watch_positions", json).commit()
}
fun removeWatchPosition(context: Context, videoId: String) {
val editor = getDefaultSharedPreferencesEditor(context)
val watchPositions = getWatchPositions(context)
var indexToRemove: Int? = null
watchPositions.forEachIndexed { index, item ->
if (item.videoId == videoId) indexToRemove = index
}
if (indexToRemove != null) watchPositions.removeAt(indexToRemove!!)
val gson = Gson()
val json = gson.toJson(watchPositions)
editor.putString("watch_positions", json).commit()
}
fun getWatchPositions(context: Context): ArrayList<WatchPosition> {
val settings = getDefaultSharedPreferences(context)
val gson = Gson()
val json: String = settings.getString("watch_positions", "")!!
val type: Type = object : TypeToken<List<WatchPosition?>?>() {}.type
return try {
gson.fromJson(json, type)
} catch (e: Exception) {
arrayListOf()
}
}
private fun getDefaultSharedPreferences(context: Context): SharedPreferences { private fun getDefaultSharedPreferences(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context) return PreferenceManager.getDefaultSharedPreferences(context)
} }

View File

@ -208,4 +208,6 @@
<string name="account">Account</string> <string name="account">Account</string>
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="watch_history">Watch History</string> <string name="watch_history">Watch History</string>
<string name="watch_positions">Remember position</string>
<string name="watch_positions_summary">Remember the watch position and automatically seek to it.</string>
</resources> </resources>

View File

@ -54,6 +54,13 @@
app:key="watch_history_toggle" app:key="watch_history_toggle"
app:title="@string/watch_history" /> app:title="@string/watch_history" />
<SwitchPreference
android:defaultValue="true"
android:icon="@drawable/ic_play_filled"
app:key="watch_position_toggle"
app:title="@string/watch_positions"
app:summary="@string/watch_positions_summary"/>
<Preference <Preference
android:icon="@drawable/ic_trash" android:icon="@drawable/ic_trash"
app:key="clear_watch_history" app:key="clear_watch_history"