diff --git a/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt b/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt index 0eaa75b15..be0128e1c 100644 --- a/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt +++ b/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt @@ -330,7 +330,10 @@ object PlayerHelper { ) fun shouldPlayNextVideo(isPlaylist: Boolean = false): Boolean { - return autoPlayEnabled || (isPlaylist && PreferenceHelper.getBoolean(PreferenceKeys.AUTOPLAY_PLAYLISTS, false)) + return autoPlayEnabled || (isPlaylist && PreferenceHelper.getBoolean( + PreferenceKeys.AUTOPLAY_PLAYLISTS, + false + )) } private val handleAudioFocus @@ -442,7 +445,11 @@ object PlayerHelper { * Create a basic player, that is used for all types of playback situations inside the app */ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) - fun createPlayer(context: Context, trackSelector: DefaultTrackSelector, isBackgroundMode: Boolean): ExoPlayer { + fun createPlayer( + context: Context, + trackSelector: DefaultTrackSelector, + isBackgroundMode: Boolean + ): ExoPlayer { val cronetDataSourceFactory = CronetDataSource.Factory( CronetHelper.cronetEngine, Executors.newCachedThreadPool() @@ -579,15 +586,16 @@ object PlayerHelper { fun getCurrentChapterIndex(currentPositionMs: Long, chapters: List): Int? { val currentPositionSeconds = currentPositionMs / 1000 return chapters - .filter { - it.highlightDrawable == null || - // remove the video highlight if it's already longer ago than [ChapterSegment.HIGHLIGHT_LENGTH], - // otherwise the SponsorBlock highlight would be shown from its starting point to the end - (currentPositionSeconds - it.start) < ChapterSegment.HIGHLIGHT_LENGTH - } .sortedBy { it.start } .indexOfLast { currentPositionSeconds >= it.start } .takeIf { it >= 0 } + ?.takeIf { index -> + val chapter = chapters[index] + // remove the video highlight if it's already longer ago than [ChapterSegment.HIGHLIGHT_LENGTH], + // otherwise the SponsorBlock highlight would be shown from its starting point to the end + val isWithinMaxHighlightDuration = (currentPositionSeconds - chapter.start) < ChapterSegment.HIGHLIGHT_LENGTH + chapter.highlightDrawable == null || isWithinMaxHighlightDuration + } } fun getPosition(videoId: String, duration: Long?): Long? { diff --git a/app/src/main/java/com/github/libretube/ui/adapters/ChaptersAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/ChaptersAdapter.kt index 223b6c5cd..09c76cc92 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/ChaptersAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/ChaptersAdapter.kt @@ -13,7 +13,7 @@ import com.github.libretube.helpers.ThemeHelper import com.github.libretube.ui.viewholders.ChaptersViewHolder class ChaptersAdapter( - private val chapters: List, + var chapters: List, private val videoDuration: Long, private val seekTo: (Long) -> Unit ) : RecyclerView.Adapter() { diff --git a/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt index a251c90b7..3cbb74d93 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/AudioPlayerFragment.kt @@ -191,7 +191,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions { binding.chapters.setOnClickListener { val playerService = playerService ?: return@setOnClickListener - viewModel.chapters = playerService.streams?.chapters.orEmpty().toMutableList() + viewModel.chaptersLiveData.value = playerService.streams?.chapters.orEmpty() ChaptersBottomSheet() .show(childFragmentManager) diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index b92a25eab..0411c58c7 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -15,6 +15,7 @@ import android.os.Looper import android.os.PowerManager import android.text.format.DateUtils import android.text.util.Linkify +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -44,11 +45,9 @@ import androidx.media3.common.MediaItem.SubtitleConfiguration import androidx.media3.common.MimeTypes import androidx.media3.common.PlaybackException import androidx.media3.common.Player -import androidx.media3.datasource.DefaultDataSource import androidx.media3.datasource.cronet.CronetDataSource import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.hls.HlsMediaSource -import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.recyclerview.widget.LinearLayoutManager import com.github.libretube.R @@ -90,7 +89,6 @@ import com.github.libretube.helpers.PlayerHelper.SPONSOR_HIGHLIGHT_CATEGORY import com.github.libretube.helpers.PlayerHelper.checkForSegments import com.github.libretube.helpers.PlayerHelper.getVideoStats import com.github.libretube.helpers.PlayerHelper.isInSegment -import com.github.libretube.helpers.PlayerHelper.loadPlaybackParams import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.helpers.ProxyHelper import com.github.libretube.obj.PlayerNotificationData @@ -922,7 +920,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { playerBinding.exoTitle.text = streams.title // init the chapters recyclerview - viewModel.chapters = streams.chapters.toMutableList() + viewModel.chaptersLiveData.value = streams.chapters // Listener for play and pause icon change exoPlayer.addListener(object : Player.Listener { @@ -1212,16 +1210,18 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { private suspend fun initializeHighlight(highlight: Segment) { val frameReceiver = OnlineTimeFrameReceiver(requireContext(), streams.previewFrames) + val highlightStart = highlight.segmentStartAndEnd.first.toLong() val frame = withContext(Dispatchers.IO) { - frameReceiver.getFrameAtTime(highlight.segmentStartAndEnd.first.toLong() * 1000) + frameReceiver.getFrameAtTime(highlightStart * 1000) } val highlightChapter = ChapterSegment( title = getString(R.string.chapters_videoHighlight), - start = highlight.segmentStartAndEnd.first.toLong(), + start = highlightStart, highlightDrawable = frame?.toDrawable(requireContext().resources) ) - viewModel.chapters.add(highlightChapter) - viewModel.chapters.sortBy { it.start } + viewModel.chaptersLiveData.postValue( + viewModel.chapters.plus(highlightChapter).sortedBy { it.start } + ) withContext(Dispatchers.Main) { setCurrentChapterName() diff --git a/app/src/main/java/com/github/libretube/ui/models/PlayerViewModel.kt b/app/src/main/java/com/github/libretube/ui/models/PlayerViewModel.kt index ac069587b..39ad60042 100644 --- a/app/src/main/java/com/github/libretube/ui/models/PlayerViewModel.kt +++ b/app/src/main/java/com/github/libretube/ui/models/PlayerViewModel.kt @@ -17,5 +17,7 @@ class PlayerViewModel : ViewModel() { var maxSheetHeightPx = 0 - var chapters: MutableList = mutableListOf() + val chaptersLiveData = MutableLiveData>() + + val chapters get() = chaptersLiveData.value.orEmpty() } diff --git a/app/src/main/java/com/github/libretube/ui/sheets/ChaptersBottomSheet.kt b/app/src/main/java/com/github/libretube/ui/sheets/ChaptersBottomSheet.kt index 5a729132b..f3f64f1d0 100644 --- a/app/src/main/java/com/github/libretube/ui/sheets/ChaptersBottomSheet.kt +++ b/app/src/main/java/com/github/libretube/ui/sheets/ChaptersBottomSheet.kt @@ -1,5 +1,6 @@ package com.github.libretube.ui.sheets +import android.annotation.SuppressLint import android.os.Bundle import android.os.Handler import android.os.Looper @@ -10,7 +11,6 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.LinearLayoutManager import com.github.libretube.R -import com.github.libretube.api.obj.ChapterSegment import com.github.libretube.databinding.BottomSheetBinding import com.github.libretube.helpers.PlayerHelper import com.github.libretube.ui.adapters.ChaptersAdapter @@ -19,14 +19,9 @@ import com.github.libretube.ui.models.PlayerViewModel class ChaptersBottomSheet : UndimmedBottomSheet() { private var _binding: BottomSheetBinding? = null private val binding get() = _binding!! + private val handler = Handler(Looper.getMainLooper()) private val playerViewModel: PlayerViewModel by activityViewModels() - private lateinit var chapters: List - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - chapters = playerViewModel.chapters - } override fun onCreateView( inflater: LayoutInflater, @@ -37,11 +32,28 @@ class ChaptersBottomSheet : UndimmedBottomSheet() { return binding.root } + private val updatePosition = object : Runnable { + override fun run() { + val binding = _binding ?: return + handler.postDelayed(this, 200) + + val player = playerViewModel.player ?: return + val currentIndex = PlayerHelper.getCurrentChapterIndex( + player.currentPosition, + playerViewModel.chapters + ) ?: return + + val adapter = binding.optionsRecycler.adapter as ChaptersAdapter + adapter.updateSelectedPosition(currentIndex) + } + } + + @SuppressLint("NotifyDataSetChanged") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.optionsRecycler.layoutManager = LinearLayoutManager(context) - val adapter = ChaptersAdapter(chapters, playerViewModel.player?.duration ?: 0) { + val adapter = ChaptersAdapter(playerViewModel.chapters, playerViewModel.player?.duration ?: 0) { playerViewModel.player?.seekTo(it) } binding.optionsRecycler.adapter = adapter @@ -49,17 +61,11 @@ class ChaptersBottomSheet : UndimmedBottomSheet() { binding.bottomSheetTitle.text = context?.getString(R.string.chapters) binding.bottomSheetTitleLayout.isVisible = true - val handler = Handler(Looper.getMainLooper()) - - val updatePosition = object : Runnable { - override fun run() { - if (_binding == null) return - handler.postDelayed(this, 200) - val player = playerViewModel.player ?: return - val currentIndex = PlayerHelper.getCurrentChapterIndex(player.currentPosition, chapters) ?: return - adapter.updateSelectedPosition(currentIndex) - } + playerViewModel.chaptersLiveData.observe(viewLifecycleOwner) { + adapter.chapters = it + adapter.notifyDataSetChanged() } + updatePosition.run() }