Merge pull request #6055 from Bnyro/master

fix: chapters dont work in audio mode
This commit is contained in:
Bnyro 2024-05-18 21:37:26 +02:00 committed by GitHub
commit 5e1e19fb8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 129 additions and 77 deletions

View File

@ -45,6 +45,7 @@ import com.github.libretube.obj.PlayerNotificationData
import com.github.libretube.ui.base.BaseActivity import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.interfaces.TimeFrameReceiver import com.github.libretube.ui.interfaces.TimeFrameReceiver
import com.github.libretube.ui.listeners.SeekbarPreviewListener import com.github.libretube.ui.listeners.SeekbarPreviewListener
import com.github.libretube.ui.models.ChaptersViewModel
import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.util.NowPlayingNotification import com.github.libretube.util.NowPlayingNotification
import com.github.libretube.util.OfflineTimeFrameReceiver import com.github.libretube.util.OfflineTimeFrameReceiver
@ -66,6 +67,7 @@ class OfflinePlayerActivity : BaseActivity() {
private lateinit var playerBinding: ExoStyledPlayerControlViewBinding private lateinit var playerBinding: ExoStyledPlayerControlViewBinding
private val playerViewModel: PlayerViewModel by viewModels() private val playerViewModel: PlayerViewModel by viewModels()
private val chaptersViewModel: ChaptersViewModel by viewModels()
private val watchPositionTimer = PauseableTimer( private val watchPositionTimer = PauseableTimer(
onTick = this::saveWatchPosition, onTick = this::saveWatchPosition,
@ -177,7 +179,8 @@ class OfflinePlayerActivity : BaseActivity() {
binding.player.initialize( binding.player.initialize(
binding.doubleTapOverlay.binding, binding.doubleTapOverlay.binding,
binding.playerGestureControlsView.binding binding.playerGestureControlsView.binding,
chaptersViewModel
) )
nowPlayingNotification = NowPlayingNotification(this, player, NowPlayingNotification.Companion.NowPlayingNotificationType.VIDEO_OFFLINE) nowPlayingNotification = NowPlayingNotification(this, player, NowPlayingNotification.Companion.NowPlayingNotificationType.VIDEO_OFFLINE)
@ -189,7 +192,7 @@ class OfflinePlayerActivity : BaseActivity() {
Database.downloadDao().findById(videoId) Database.downloadDao().findById(videoId)
} }
val chapters = downloadChapters.map(DownloadChapter::toChapterSegment) val chapters = downloadChapters.map(DownloadChapter::toChapterSegment)
playerViewModel.chaptersLiveData.value = chapters chaptersViewModel.chaptersLiveData.value = chapters
binding.player.setChapters(chapters) binding.player.setChapters(chapters)
val downloadFiles = downloadItems.filter { it.path.exists() } val downloadFiles = downloadItems.filter { it.path.exists() }

View File

@ -14,7 +14,7 @@ import com.github.libretube.ui.viewholders.ChaptersViewHolder
class ChaptersAdapter( class ChaptersAdapter(
var chapters: List<ChapterSegment>, var chapters: List<ChapterSegment>,
private val videoDuration: Long, private val videoDurationSeconds: Long,
private val seekTo: (Long) -> Unit private val seekTo: (Long) -> Unit
) : RecyclerView.Adapter<ChaptersViewHolder>() { ) : RecyclerView.Adapter<ChaptersViewHolder>() {
private var selectedPosition = 0 private var selectedPosition = 0
@ -36,12 +36,11 @@ class ChaptersAdapter(
chapterTitle.text = chapter.title chapterTitle.text = chapter.title
timeStamp.text = DateUtils.formatElapsedTime(chapter.start) timeStamp.text = DateUtils.formatElapsedTime(chapter.start)
val playerDurationSeconds = videoDuration / 1000
val chapterEnd = if (chapter.highlightDrawable == null) { val chapterEnd = if (chapter.highlightDrawable == null) {
chapters.getOrNull(position + 1)?.start ?: playerDurationSeconds chapters.getOrNull(position + 1)?.start ?: videoDurationSeconds
} else { } else {
// the duration for chapters is hardcoded, since it's not provided by the SB API // the duration for chapters is hardcoded, since it's not provided by the SB API
minOf(chapter.start + ChapterSegment.HIGHLIGHT_LENGTH, playerDurationSeconds) minOf(chapter.start + ChapterSegment.HIGHLIGHT_LENGTH, videoDurationSeconds)
} }
val durationSpan = chapterEnd - chapter.start val durationSpan = chapterEnd - chapter.start
duration.text = root.context.getString( duration.text = root.context.getString(

View File

@ -31,6 +31,7 @@ import com.github.libretube.extensions.normalize
import com.github.libretube.extensions.seekBy import com.github.libretube.extensions.seekBy
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.extensions.togglePlayPauseState import com.github.libretube.extensions.togglePlayPauseState
import com.github.libretube.extensions.updateIfChanged
import com.github.libretube.helpers.AudioHelper import com.github.libretube.helpers.AudioHelper
import com.github.libretube.helpers.BackgroundHelper import com.github.libretube.helpers.BackgroundHelper
import com.github.libretube.helpers.ImageHelper import com.github.libretube.helpers.ImageHelper
@ -42,6 +43,7 @@ import com.github.libretube.services.OnlinePlayerService
import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.interfaces.AudioPlayerOptions import com.github.libretube.ui.interfaces.AudioPlayerOptions
import com.github.libretube.ui.listeners.AudioPlayerThumbnailListener import com.github.libretube.ui.listeners.AudioPlayerThumbnailListener
import com.github.libretube.ui.models.ChaptersViewModel
import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.ui.sheets.ChaptersBottomSheet import com.github.libretube.ui.sheets.ChaptersBottomSheet
import com.github.libretube.ui.sheets.PlaybackOptionsSheet import com.github.libretube.ui.sheets.PlaybackOptionsSheet
@ -61,6 +63,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
private lateinit var audioHelper: AudioHelper private lateinit var audioHelper: AudioHelper
private val mainActivity get() = context as MainActivity private val mainActivity get() = context as MainActivity
private val viewModel: PlayerViewModel by activityViewModels() private val viewModel: PlayerViewModel by activityViewModels()
private val chaptersModel: ChaptersViewModel by activityViewModels()
// for the transition // for the transition
private var transitionStartId = 0 private var transitionStartId = 0
@ -167,12 +170,24 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
) )
} }
requireActivity().supportFragmentManager.setFragmentResultListener(
ChaptersBottomSheet.SEEK_TO_POSITION_REQUEST_KEY,
viewLifecycleOwner
) { _, bundle ->
playerService?.player?.seekTo(bundle.getLong(IntentData.currentPosition))
}
binding.openChapters.setOnClickListener { binding.openChapters.setOnClickListener {
val playerService = playerService ?: return@setOnClickListener val playerService = playerService ?: return@setOnClickListener
viewModel.chaptersLiveData.value = playerService.streams?.chapters.orEmpty() chaptersModel.chaptersLiveData.value = playerService.streams?.chapters.orEmpty()
ChaptersBottomSheet() ChaptersBottomSheet()
.show(childFragmentManager) .apply {
arguments = bundleOf(
IntentData.duration to playerService.player?.duration?.div(1000)
)
}
.show(requireActivity().supportFragmentManager)
} }
binding.miniPlayerClose.setOnClickListener { binding.miniPlayerClose.setOnClickListener {
@ -205,6 +220,8 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
} }
if (!PlayerHelper.playAutomatically) updatePlayPauseButton() if (!PlayerHelper.playAutomatically) updatePlayPauseButton()
updateChapterIndex()
} }
private fun killFragment() { private fun killFragment() {
@ -435,4 +452,14 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
binding.volumeTextView.text = "${bar.progress.normalize(0, bar.max, 0, 100)}" binding.volumeTextView.text = "${bar.progress.normalize(0, bar.max, 0, 100)}"
} }
private fun updateChapterIndex() {
if (_binding == null) return
handler.postDelayed(this::updateChapterIndex, 100)
val player = playerService?.player ?: return
val currentIndex = PlayerHelper.getCurrentChapterIndex(player.currentPosition, chaptersModel.chapters)
chaptersModel.currentChapterIndex.updateIfChanged(currentIndex ?: return)
}
} }

View File

@ -97,6 +97,7 @@ import com.github.libretube.ui.extensions.animateDown
import com.github.libretube.ui.extensions.setupSubscriptionButton import com.github.libretube.ui.extensions.setupSubscriptionButton
import com.github.libretube.ui.interfaces.OnlinePlayerOptions import com.github.libretube.ui.interfaces.OnlinePlayerOptions
import com.github.libretube.ui.listeners.SeekbarPreviewListener import com.github.libretube.ui.listeners.SeekbarPreviewListener
import com.github.libretube.ui.models.ChaptersViewModel
import com.github.libretube.ui.models.CommentsViewModel import com.github.libretube.ui.models.CommentsViewModel
import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.PlayerViewModel
import com.github.libretube.ui.sheets.BaseBottomSheet import com.github.libretube.ui.sheets.BaseBottomSheet
@ -127,6 +128,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
private val viewModel: PlayerViewModel by activityViewModels() private val viewModel: PlayerViewModel by activityViewModels()
private val commentsViewModel: CommentsViewModel by activityViewModels() private val commentsViewModel: CommentsViewModel by activityViewModels()
private val chaptersViewModel: ChaptersViewModel by activityViewModels()
// Video information passed by the intent // Video information passed by the intent
private lateinit var videoId: String private lateinit var videoId: String
@ -467,6 +469,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
} }
(activity as MainActivity).requestOrientationChange() (activity as MainActivity).requestOrientationChange()
} }
updateMaxSheetHeight()
} }
}) })
@ -634,7 +638,9 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
} }
private fun updateMaxSheetHeight() { private fun updateMaxSheetHeight() {
viewModel.maxSheetHeightPx = binding.root.height - binding.player.height val maxHeight = binding.root.height - binding.player.height
viewModel.maxSheetHeightPx = viewModel.maxSheetHeightPx
chaptersViewModel.maxSheetHeightPx = maxHeight
} }
private fun playOnBackground() { private fun playOnBackground() {
@ -1022,7 +1028,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun initializePlayerView() { private fun initializePlayerView() {
// initialize the player view actions // initialize the player view actions
binding.player.initialize(doubleTapOverlayBinding, playerGestureControlsViewBinding) binding.player.initialize(doubleTapOverlayBinding, playerGestureControlsViewBinding, chaptersViewModel)
binding.player.initPlayerOptions(viewModel, viewLifecycleOwner, trackSelector, this) binding.player.initPlayerOptions(viewModel, viewLifecycleOwner, trackSelector, this)
binding.descriptionLayout.setStreams(streams) binding.descriptionLayout.setStreams(streams)
@ -1040,7 +1046,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
playerBinding.exoTitle.text = streams.title playerBinding.exoTitle.text = streams.title
// init the chapters recyclerview // init the chapters recyclerview
viewModel.chaptersLiveData.value = streams.chapters chaptersViewModel.chaptersLiveData.value = streams.chapters
if (PlayerHelper.relatedStreamsEnabled) { if (PlayerHelper.relatedStreamsEnabled) {
val relatedLayoutManager = binding.relatedRecView.layoutManager as LinearLayoutManager val relatedLayoutManager = binding.relatedRecView.layoutManager as LinearLayoutManager
@ -1132,8 +1138,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
start = highlightStart, start = highlightStart,
highlightDrawable = frame?.toDrawable(requireContext().resources) highlightDrawable = frame?.toDrawable(requireContext().resources)
) )
viewModel.chaptersLiveData.postValue( chaptersViewModel.chaptersLiveData.postValue(
viewModel.chapters.plus(highlightChapter).sortedBy { it.start } chaptersViewModel.chapters.plus(highlightChapter).sortedBy { it.start }
) )
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View File

@ -0,0 +1,12 @@
package com.github.libretube.ui.models
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.github.libretube.api.obj.ChapterSegment
class ChaptersViewModel: ViewModel() {
val chaptersLiveData = MutableLiveData<List<ChapterSegment>>()
val chapters get() = chaptersLiveData.value.orEmpty()
val currentChapterIndex = MutableLiveData<Int>()
var maxSheetHeightPx = 0
}

View File

@ -10,7 +10,6 @@ import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.JsonHelper import com.github.libretube.api.JsonHelper
import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.ChapterSegment
import com.github.libretube.api.obj.Message import com.github.libretube.api.obj.Message
import com.github.libretube.api.obj.Segment import com.github.libretube.api.obj.Segment
import com.github.libretube.api.obj.Streams import com.github.libretube.api.obj.Streams
@ -48,9 +47,6 @@ class PlayerViewModel : ViewModel() {
var maxSheetHeightPx = 0 var maxSheetHeightPx = 0
val chaptersLiveData = MutableLiveData<List<ChapterSegment>>()
val chapters get() = chaptersLiveData.value.orEmpty()
var sponsorBlockEnabled = PlayerHelper.sponsorBlockEnabled var sponsorBlockEnabled = PlayerHelper.sponsorBlockEnabled
/** /**

View File

@ -2,29 +2,32 @@ package com.github.libretube.ui.sheets
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import androidx.core.os.bundleOf
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.media3.common.util.UnstableApi import androidx.fragment.app.setFragmentResult
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.BottomSheetBinding import com.github.libretube.databinding.BottomSheetBinding
import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.ui.adapters.ChaptersAdapter import com.github.libretube.ui.adapters.ChaptersAdapter
import com.github.libretube.ui.models.PlayerViewModel import com.github.libretube.ui.models.ChaptersViewModel
@UnstableApi
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 chaptersViewModel: ChaptersViewModel by activityViewModels()
private var duration = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
duration = requireArguments().getLong(IntentData.duration, 0L)
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -35,61 +38,45 @@ 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 currentIndex = getCurrentIndex() ?: return
val adapter = binding.optionsRecycler.adapter as ChaptersAdapter
adapter.updateSelectedPosition(currentIndex)
}
}
@SuppressLint("NotifyDataSetChanged") @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 = val adapter =
ChaptersAdapter(playerViewModel.chapters, playerViewModel.player?.duration ?: 0) { ChaptersAdapter(chaptersViewModel.chapters, duration) {
playerViewModel.player?.seekTo(it) setFragmentResult(SEEK_TO_POSITION_REQUEST_KEY, bundleOf(IntentData.currentPosition to it))
} }
binding.optionsRecycler.adapter = adapter binding.optionsRecycler.adapter = adapter
binding.optionsRecycler.viewTreeObserver.addOnGlobalLayoutListener( binding.optionsRecycler.viewTreeObserver.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener { object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
val currentIndex = getCurrentIndex() ?: return chaptersViewModel.currentChapterIndex.value?.let {
binding.optionsRecycler.scrollToPosition(currentIndex) binding.optionsRecycler.scrollToPosition(it)
}
binding.optionsRecycler.viewTreeObserver.removeOnGlobalLayoutListener(this) binding.optionsRecycler.viewTreeObserver.removeOnGlobalLayoutListener(this)
} }
} }
) )
chaptersViewModel.currentChapterIndex.observe(viewLifecycleOwner) { currentIndex ->
if (_binding == null) return@observe
adapter.updateSelectedPosition(currentIndex)
}
binding.bottomSheetTitle.text = context?.getString(R.string.chapters) binding.bottomSheetTitle.text = context?.getString(R.string.chapters)
binding.bottomSheetTitleLayout.isVisible = true binding.bottomSheetTitleLayout.isVisible = true
playerViewModel.chaptersLiveData.observe(viewLifecycleOwner) { chaptersViewModel.chaptersLiveData.observe(viewLifecycleOwner) {
adapter.chapters = it adapter.chapters = it
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
updatePosition.run()
} }
private fun getCurrentIndex(): Int? { override fun getSheetMaxHeightPx() = chaptersViewModel.maxSheetHeightPx
val player = playerViewModel.player ?: return null
return PlayerHelper.getCurrentChapterIndex(
player.currentPosition,
playerViewModel.chapters
)
}
override fun getSheetMaxHeightPx() = playerViewModel.maxSheetHeightPx
override fun getDragHandle() = binding.dragHandle override fun getDragHandle() = binding.dragHandle
@ -99,4 +86,8 @@ class ChaptersBottomSheet : UndimmedBottomSheet() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
} }
companion object {
const val SEEK_TO_POSITION_REQUEST_KEY = "seek_to_position_request_key"
}
} }

View File

@ -19,6 +19,7 @@ import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.widget.TooltipCompat import androidx.appcompat.widget.TooltipCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isGone import androidx.core.view.isGone
@ -26,6 +27,7 @@ import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.marginStart import androidx.core.view.marginStart
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.text.Cue import androidx.media3.common.text.Cue
@ -36,7 +38,7 @@ import androidx.media3.ui.PlayerView
import androidx.media3.ui.SubtitleView import androidx.media3.ui.SubtitleView
import androidx.media3.ui.TimeBar import androidx.media3.ui.TimeBar
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.ChapterSegment import com.github.libretube.constants.IntentData
import com.github.libretube.constants.PreferenceKeys import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.DoubleTapOverlayBinding
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
@ -46,6 +48,7 @@ import com.github.libretube.extensions.normalize
import com.github.libretube.extensions.round import com.github.libretube.extensions.round
import com.github.libretube.extensions.seekBy import com.github.libretube.extensions.seekBy
import com.github.libretube.extensions.togglePlayPauseState import com.github.libretube.extensions.togglePlayPauseState
import com.github.libretube.extensions.updateIfChanged
import com.github.libretube.helpers.AudioHelper import com.github.libretube.helpers.AudioHelper
import com.github.libretube.helpers.BrightnessHelper import com.github.libretube.helpers.BrightnessHelper
import com.github.libretube.helpers.ContextHelper import com.github.libretube.helpers.ContextHelper
@ -59,6 +62,7 @@ import com.github.libretube.ui.fragments.PlayerFragment
import com.github.libretube.ui.interfaces.PlayerGestureOptions import com.github.libretube.ui.interfaces.PlayerGestureOptions
import com.github.libretube.ui.interfaces.PlayerOptions import com.github.libretube.ui.interfaces.PlayerOptions
import com.github.libretube.ui.listeners.PlayerGestureController import com.github.libretube.ui.listeners.PlayerGestureController
import com.github.libretube.ui.models.ChaptersViewModel
import com.github.libretube.ui.sheets.BaseBottomSheet import com.github.libretube.ui.sheets.BaseBottomSheet
import com.github.libretube.ui.sheets.ChaptersBottomSheet import com.github.libretube.ui.sheets.ChaptersBottomSheet
import com.github.libretube.ui.sheets.PlaybackOptionsSheet import com.github.libretube.ui.sheets.PlaybackOptionsSheet
@ -81,6 +85,7 @@ abstract class CustomExoPlayerView(
private lateinit var playerGestureController: PlayerGestureController private lateinit var playerGestureController: PlayerGestureController
private lateinit var brightnessHelper: BrightnessHelper private lateinit var brightnessHelper: BrightnessHelper
private lateinit var audioHelper: AudioHelper private lateinit var audioHelper: AudioHelper
private lateinit var chaptersViewModel: ChaptersViewModel
private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = null private var doubleTapOverlayBinding: DoubleTapOverlayBinding? = null
private var chaptersBottomSheet: ChaptersBottomSheet? = null private var chaptersBottomSheet: ChaptersBottomSheet? = null
private var scrubbingTimeBar = false private var scrubbingTimeBar = false
@ -114,10 +119,12 @@ abstract class CustomExoPlayerView(
fun initialize( fun initialize(
doubleTapOverlayBinding: DoubleTapOverlayBinding, doubleTapOverlayBinding: DoubleTapOverlayBinding,
playerGestureControlsViewBinding: PlayerGestureControlsViewBinding playerGestureControlsViewBinding: PlayerGestureControlsViewBinding,
chaptersViewModel: ChaptersViewModel
) { ) {
this.doubleTapOverlayBinding = doubleTapOverlayBinding this.doubleTapOverlayBinding = doubleTapOverlayBinding
this.gestureViewBinding = playerGestureControlsViewBinding this.gestureViewBinding = playerGestureControlsViewBinding
this.chaptersViewModel = chaptersViewModel
this.playerGestureController = PlayerGestureController(context as BaseActivity, this) this.playerGestureController = PlayerGestureController(context as BaseActivity, this)
this.brightnessHelper = BrightnessHelper(context as Activity) this.brightnessHelper = BrightnessHelper(context as Activity)
this.audioHelper = AudioHelper(context) this.audioHelper = AudioHelper(context)
@ -216,11 +223,24 @@ abstract class CustomExoPlayerView(
updateCurrentPosition() updateCurrentPosition()
activity.supportFragmentManager.setFragmentResultListener(
ChaptersBottomSheet.SEEK_TO_POSITION_REQUEST_KEY,
findViewTreeLifecycleOwner() ?: activity
) { _, bundle ->
player?.seekTo(bundle.getLong(IntentData.currentPosition))
}
// enable the chapters dialog in the player // enable the chapters dialog in the player
binding.chapterName.setOnClickListener { binding.chapterName.setOnClickListener {
val sheet = chaptersBottomSheet ?: ChaptersBottomSheet().also { val sheet = chaptersBottomSheet ?: ChaptersBottomSheet()
chaptersBottomSheet = it .apply {
} arguments = bundleOf(
IntentData.duration to player?.duration?.div(1000)
)
}
.also {
chaptersBottomSheet = it
}
if (sheet.isVisible) { if (sheet.isVisible) {
sheet.dismiss() sheet.dismiss()
@ -231,9 +251,9 @@ abstract class CustomExoPlayerView(
} }
// set the name of the video chapter in the exoPlayerView // set the name of the video chapter in the exoPlayerView
fun setCurrentChapterName(forceUpdate: Boolean = false, enqueueNew: Boolean = true) { fun setCurrentChapterName(forceUpdate: Boolean = false, enqueueNew: Boolean = true) {
val player = player ?: return val player = player ?: return
val chapters = getChapters() val chapters = chaptersViewModel.chapters
binding.chapterName.isInvisible = chapters.isEmpty() binding.chapterName.isInvisible = chapters.isEmpty()
@ -246,10 +266,9 @@ abstract class CustomExoPlayerView(
// if the user is scrubbing the time bar, don't update // if the user is scrubbing the time bar, don't update
if (scrubbingTimeBar && !forceUpdate) return if (scrubbingTimeBar && !forceUpdate) return
val newChapterName = val currentIndex = PlayerHelper.getCurrentChapterIndex(player.currentPosition, chapters)
PlayerHelper.getCurrentChapterIndex(player.currentPosition, chapters) val newChapterName = currentIndex?.let { chapters[it].title.trim() } ?: context.getString(R.string.no_chapter)
?.let { chapters[it].title.trim() } chaptersViewModel.currentChapterIndex.updateIfChanged(currentIndex ?: return)
?: context.getString(R.string.no_chapter)
// change the chapter name textView text to the chapterName // change the chapter name textView text to the chapterName
if (newChapterName != binding.chapterName.text) { if (newChapterName != binding.chapterName.text) {
@ -627,8 +646,10 @@ abstract class CustomExoPlayerView(
if (!activity.hasCutout && binding.topBar.marginStart == LANDSCAPE_MARGIN_HORIZONTAL_NONE) return if (!activity.hasCutout && binding.topBar.marginStart == LANDSCAPE_MARGIN_HORIZONTAL_NONE) return
// add a margin to the top and the bottom bar in landscape mode for notches // add a margin to the top and the bottom bar in landscape mode for notches
val isForcedPortrait = activity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT val isForcedPortrait =
val horizontalMargin = if (isFullscreen() && !isForcedPortrait) LANDSCAPE_MARGIN_HORIZONTAL else LANDSCAPE_MARGIN_HORIZONTAL_NONE activity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
val horizontalMargin =
if (isFullscreen() && !isForcedPortrait) LANDSCAPE_MARGIN_HORIZONTAL else LANDSCAPE_MARGIN_HORIZONTAL_NONE
listOf(binding.topBar, binding.bottomBar).forEach { listOf(binding.topBar, binding.bottomBar).forEach {
it.updateLayoutParams<MarginLayoutParams> { it.updateLayoutParams<MarginLayoutParams> {
@ -784,23 +805,29 @@ abstract class CustomExoPlayerView(
KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> {
player?.togglePlayPauseState() player?.togglePlayPauseState()
} }
KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> { KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> {
forward() forward()
} }
KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_MEDIA_REWIND -> { KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_MEDIA_REWIND -> {
rewind() rewind()
} }
KeyEvent.KEYCODE_N, KeyEvent.KEYCODE_NAVIGATE_NEXT -> { KeyEvent.KEYCODE_N, KeyEvent.KEYCODE_NAVIGATE_NEXT -> {
PlayingQueue.navigateNext() PlayingQueue.navigateNext()
} }
KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_NAVIGATE_PREVIOUS -> { KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_NAVIGATE_PREVIOUS -> {
PlayingQueue.navigatePrev() PlayingQueue.navigatePrev()
} }
KeyEvent.KEYCODE_F -> { KeyEvent.KEYCODE_F -> {
val fragmentManager = ContextHelper.unwrapActivity(context).supportFragmentManager val fragmentManager = ContextHelper.unwrapActivity(context).supportFragmentManager
fragmentManager.fragments.filterIsInstance<PlayerFragment>().firstOrNull() fragmentManager.fragments.filterIsInstance<PlayerFragment>().firstOrNull()
?.toggleFullscreen() ?.toggleFullscreen()
} }
else -> return false else -> return false
} }
@ -826,8 +853,6 @@ abstract class CustomExoPlayerView(
open fun minimizeOrExitPlayer() = Unit open fun minimizeOrExitPlayer() = Unit
abstract fun getChapters(): List<ChapterSegment>
open fun getWindow(): Window = activity.window open fun getWindow(): Window = activity.window
companion object { companion object {

View File

@ -31,6 +31,4 @@ class OfflinePlayerView(
this.chapters = chapters this.chapters = chapters
setCurrentChapterName() setCurrentChapterName()
} }
override fun getChapters(): List<ChapterSegment> = chapters
} }

View File

@ -16,7 +16,6 @@ import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.trackselection.TrackSelector import androidx.media3.exoplayer.trackselection.TrackSelector
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.ChapterSegment
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.extensions.toID import com.github.libretube.extensions.toID
@ -281,10 +280,6 @@ class OnlinePlayerView(
playerOptions?.exitFullscreen() playerOptions?.exitFullscreen()
} }
override fun getChapters(): List<ChapterSegment> {
return playerViewModel?.chapters.orEmpty()
}
override fun onPlaybackEvents(player: Player, events: Player.Events) { override fun onPlaybackEvents(player: Player, events: Player.Events) {
super.onPlaybackEvents(player, events) super.onPlaybackEvents(player, events)
updateDisplayedDuration() updateDisplayedDuration()