mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 00:10:32 +05:30
Merge pull request #4916 from Bnyro/master
fix: conflicts between chapters and video highlight
This commit is contained in:
commit
bc048e015b
@ -330,7 +330,10 @@ object PlayerHelper {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun shouldPlayNextVideo(isPlaylist: Boolean = false): Boolean {
|
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
|
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
|
* 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)
|
@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(
|
val cronetDataSourceFactory = CronetDataSource.Factory(
|
||||||
CronetHelper.cronetEngine,
|
CronetHelper.cronetEngine,
|
||||||
Executors.newCachedThreadPool()
|
Executors.newCachedThreadPool()
|
||||||
@ -579,15 +586,16 @@ object PlayerHelper {
|
|||||||
fun getCurrentChapterIndex(currentPositionMs: Long, chapters: List<ChapterSegment>): Int? {
|
fun getCurrentChapterIndex(currentPositionMs: Long, chapters: List<ChapterSegment>): Int? {
|
||||||
val currentPositionSeconds = currentPositionMs / 1000
|
val currentPositionSeconds = currentPositionMs / 1000
|
||||||
return chapters
|
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 }
|
.sortedBy { it.start }
|
||||||
.indexOfLast { currentPositionSeconds >= it.start }
|
.indexOfLast { currentPositionSeconds >= it.start }
|
||||||
.takeIf { it >= 0 }
|
.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? {
|
fun getPosition(videoId: String, duration: Long?): Long? {
|
||||||
|
@ -13,7 +13,7 @@ import com.github.libretube.helpers.ThemeHelper
|
|||||||
import com.github.libretube.ui.viewholders.ChaptersViewHolder
|
import com.github.libretube.ui.viewholders.ChaptersViewHolder
|
||||||
|
|
||||||
class ChaptersAdapter(
|
class ChaptersAdapter(
|
||||||
private val chapters: List<ChapterSegment>,
|
var chapters: List<ChapterSegment>,
|
||||||
private val videoDuration: Long,
|
private val videoDuration: Long,
|
||||||
private val seekTo: (Long) -> Unit
|
private val seekTo: (Long) -> Unit
|
||||||
) : RecyclerView.Adapter<ChaptersViewHolder>() {
|
) : RecyclerView.Adapter<ChaptersViewHolder>() {
|
||||||
|
@ -191,7 +191,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
|
|||||||
|
|
||||||
binding.chapters.setOnClickListener {
|
binding.chapters.setOnClickListener {
|
||||||
val playerService = playerService ?: return@setOnClickListener
|
val playerService = playerService ?: return@setOnClickListener
|
||||||
viewModel.chapters = playerService.streams?.chapters.orEmpty().toMutableList()
|
viewModel.chaptersLiveData.value = playerService.streams?.chapters.orEmpty()
|
||||||
|
|
||||||
ChaptersBottomSheet()
|
ChaptersBottomSheet()
|
||||||
.show(childFragmentManager)
|
.show(childFragmentManager)
|
||||||
|
@ -15,6 +15,7 @@ import android.os.Looper
|
|||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.text.util.Linkify
|
import android.text.util.Linkify
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -44,11 +45,9 @@ import androidx.media3.common.MediaItem.SubtitleConfiguration
|
|||||||
import androidx.media3.common.MimeTypes
|
import androidx.media3.common.MimeTypes
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.datasource.DefaultDataSource
|
|
||||||
import androidx.media3.datasource.cronet.CronetDataSource
|
import androidx.media3.datasource.cronet.CronetDataSource
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.hls.HlsMediaSource
|
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.github.libretube.R
|
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.checkForSegments
|
||||||
import com.github.libretube.helpers.PlayerHelper.getVideoStats
|
import com.github.libretube.helpers.PlayerHelper.getVideoStats
|
||||||
import com.github.libretube.helpers.PlayerHelper.isInSegment
|
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.PreferenceHelper
|
||||||
import com.github.libretube.helpers.ProxyHelper
|
import com.github.libretube.helpers.ProxyHelper
|
||||||
import com.github.libretube.obj.PlayerNotificationData
|
import com.github.libretube.obj.PlayerNotificationData
|
||||||
@ -922,7 +920,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
playerBinding.exoTitle.text = streams.title
|
playerBinding.exoTitle.text = streams.title
|
||||||
|
|
||||||
// init the chapters recyclerview
|
// init the chapters recyclerview
|
||||||
viewModel.chapters = streams.chapters.toMutableList()
|
viewModel.chaptersLiveData.value = streams.chapters
|
||||||
|
|
||||||
// Listener for play and pause icon change
|
// Listener for play and pause icon change
|
||||||
exoPlayer.addListener(object : Player.Listener {
|
exoPlayer.addListener(object : Player.Listener {
|
||||||
@ -1212,16 +1210,18 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
|
|
||||||
private suspend fun initializeHighlight(highlight: Segment) {
|
private suspend fun initializeHighlight(highlight: Segment) {
|
||||||
val frameReceiver = OnlineTimeFrameReceiver(requireContext(), streams.previewFrames)
|
val frameReceiver = OnlineTimeFrameReceiver(requireContext(), streams.previewFrames)
|
||||||
|
val highlightStart = highlight.segmentStartAndEnd.first.toLong()
|
||||||
val frame = withContext(Dispatchers.IO) {
|
val frame = withContext(Dispatchers.IO) {
|
||||||
frameReceiver.getFrameAtTime(highlight.segmentStartAndEnd.first.toLong() * 1000)
|
frameReceiver.getFrameAtTime(highlightStart * 1000)
|
||||||
}
|
}
|
||||||
val highlightChapter = ChapterSegment(
|
val highlightChapter = ChapterSegment(
|
||||||
title = getString(R.string.chapters_videoHighlight),
|
title = getString(R.string.chapters_videoHighlight),
|
||||||
start = highlight.segmentStartAndEnd.first.toLong(),
|
start = highlightStart,
|
||||||
highlightDrawable = frame?.toDrawable(requireContext().resources)
|
highlightDrawable = frame?.toDrawable(requireContext().resources)
|
||||||
)
|
)
|
||||||
viewModel.chapters.add(highlightChapter)
|
viewModel.chaptersLiveData.postValue(
|
||||||
viewModel.chapters.sortBy { it.start }
|
viewModel.chapters.plus(highlightChapter).sortedBy { it.start }
|
||||||
|
)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
setCurrentChapterName()
|
setCurrentChapterName()
|
||||||
|
@ -17,5 +17,7 @@ class PlayerViewModel : ViewModel() {
|
|||||||
|
|
||||||
var maxSheetHeightPx = 0
|
var maxSheetHeightPx = 0
|
||||||
|
|
||||||
var chapters: MutableList<ChapterSegment> = mutableListOf()
|
val chaptersLiveData = MutableLiveData<List<ChapterSegment>>()
|
||||||
|
|
||||||
|
val chapters get() = chaptersLiveData.value.orEmpty()
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.github.libretube.ui.sheets
|
package com.github.libretube.ui.sheets
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
@ -10,7 +11,6 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.obj.ChapterSegment
|
|
||||||
import com.github.libretube.databinding.BottomSheetBinding
|
import com.github.libretube.databinding.BottomSheetBinding
|
||||||
import com.github.libretube.helpers.PlayerHelper
|
import com.github.libretube.helpers.PlayerHelper
|
||||||
import com.github.libretube.ui.adapters.ChaptersAdapter
|
import com.github.libretube.ui.adapters.ChaptersAdapter
|
||||||
@ -19,14 +19,9 @@ import com.github.libretube.ui.models.PlayerViewModel
|
|||||||
class ChaptersBottomSheet : UndimmedBottomSheet() {
|
class ChaptersBottomSheet : UndimmedBottomSheet() {
|
||||||
private var _binding: BottomSheetBinding? = null
|
private var _binding: BottomSheetBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
private val playerViewModel: PlayerViewModel by activityViewModels()
|
private val playerViewModel: PlayerViewModel by activityViewModels()
|
||||||
private lateinit var chapters: List<ChapterSegment>
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
chapters = playerViewModel.chapters
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@ -37,11 +32,28 @@ class ChaptersBottomSheet : UndimmedBottomSheet() {
|
|||||||
return binding.root
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
binding.optionsRecycler.layoutManager = LinearLayoutManager(context)
|
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)
|
playerViewModel.player?.seekTo(it)
|
||||||
}
|
}
|
||||||
binding.optionsRecycler.adapter = adapter
|
binding.optionsRecycler.adapter = adapter
|
||||||
@ -49,17 +61,11 @@ class ChaptersBottomSheet : UndimmedBottomSheet() {
|
|||||||
binding.bottomSheetTitle.text = context?.getString(R.string.chapters)
|
binding.bottomSheetTitle.text = context?.getString(R.string.chapters)
|
||||||
binding.bottomSheetTitleLayout.isVisible = true
|
binding.bottomSheetTitleLayout.isVisible = true
|
||||||
|
|
||||||
val handler = Handler(Looper.getMainLooper())
|
playerViewModel.chaptersLiveData.observe(viewLifecycleOwner) {
|
||||||
|
adapter.chapters = it
|
||||||
val updatePosition = object : Runnable {
|
adapter.notifyDataSetChanged()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition.run()
|
updatePosition.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user