mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 06:10:31 +05:30
Merge pull request #4090 from Bnyro/master
Chapters dialog: highlight current chapter and auto scroll to it
This commit is contained in:
commit
a2cc01081b
@ -6,6 +6,8 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.view.accessibility.CaptioningManager
|
import android.view.accessibility.CaptioningManager
|
||||||
@ -16,6 +18,7 @@ import androidx.core.app.RemoteActionCompat
|
|||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import androidx.core.view.children
|
||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.PlaybackParameters
|
import androidx.media3.common.PlaybackParameters
|
||||||
@ -46,7 +49,8 @@ object PlayerHelper {
|
|||||||
"outro",
|
"outro",
|
||||||
"filler",
|
"filler",
|
||||||
"music_offtopic",
|
"music_offtopic",
|
||||||
"preview")
|
"preview"
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a base64 encoded DASH stream manifest
|
* Create a base64 encoded DASH stream manifest
|
||||||
@ -84,9 +88,9 @@ object PlayerHelper {
|
|||||||
fun getSponsorBlockCategories(): MutableMap<String, SbSkipOptions> {
|
fun getSponsorBlockCategories(): MutableMap<String, SbSkipOptions> {
|
||||||
val categories: MutableMap<String, SbSkipOptions> = mutableMapOf()
|
val categories: MutableMap<String, SbSkipOptions> = mutableMapOf()
|
||||||
|
|
||||||
for (category in SPONSOR_CATEGORIES){
|
for (category in SPONSOR_CATEGORIES) {
|
||||||
val state = PreferenceHelper.getString(category + "_category", "off").uppercase()
|
val state = PreferenceHelper.getString(category + "_category", "off").uppercase()
|
||||||
if (SbSkipOptions.valueOf(state) != SbSkipOptions.OFF){
|
if (SbSkipOptions.valueOf(state) != SbSkipOptions.OFF) {
|
||||||
categories[category] = SbSkipOptions.valueOf(state)
|
categories[category] = SbSkipOptions.valueOf(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,6 +113,7 @@ object PlayerHelper {
|
|||||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"auto" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR
|
"auto" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR
|
||||||
"landscape" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
"landscape" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||||
"portrait" -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
"portrait" -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
||||||
@ -231,7 +236,9 @@ object PlayerHelper {
|
|||||||
|
|
||||||
private val backgroundSpeed: Float
|
private val backgroundSpeed: Float
|
||||||
get() = when (PreferenceHelper.getBoolean(PreferenceKeys.CUSTOM_PLAYBACK_SPEED, false)) {
|
get() = when (PreferenceHelper.getBoolean(PreferenceKeys.CUSTOM_PLAYBACK_SPEED, false)) {
|
||||||
true -> PreferenceHelper.getString(PreferenceKeys.BACKGROUND_PLAYBACK_SPEED, "1").toFloat()
|
true -> PreferenceHelper.getString(PreferenceKeys.BACKGROUND_PLAYBACK_SPEED, "1")
|
||||||
|
.toFloat()
|
||||||
|
|
||||||
else -> playbackSpeed
|
else -> playbackSpeed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,8 +434,7 @@ object PlayerHelper {
|
|||||||
* Check for SponsorBlock segments matching the current player position
|
* Check for SponsorBlock segments matching the current player position
|
||||||
* @param context A main dispatcher context
|
* @param context A main dispatcher context
|
||||||
* @param segments List of the SponsorBlock segments
|
* @param segments List of the SponsorBlock segments
|
||||||
* @param skipManually Whether the event gets handled by the function caller
|
* @return If segment found and should skip manually, the end position of the segment in ms, otherwise null
|
||||||
* @return If segment found and [skipManually] is true, the end position of the segment in ms, otherwise null
|
|
||||||
*/
|
*/
|
||||||
fun ExoPlayer.checkForSegments(
|
fun ExoPlayer.checkForSegments(
|
||||||
context: Context,
|
context: Context,
|
||||||
@ -465,11 +471,19 @@ object PlayerHelper {
|
|||||||
for (segment in segments) {
|
for (segment in segments) {
|
||||||
val segmentStart = (segment.segment[0] * 1000f).toLong()
|
val segmentStart = (segment.segment[0] * 1000f).toLong()
|
||||||
val segmentEnd = (segment.segment[1] * 1000f).toLong()
|
val segmentEnd = (segment.segment[1] * 1000f).toLong()
|
||||||
if (currentPosition in segmentStart..segmentEnd) { return true }
|
if (currentPosition in segmentStart..segmentEnd) return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the currently played chapter
|
||||||
|
*/
|
||||||
|
fun getCurrentChapterIndex(exoPlayer: ExoPlayer, chapters: List<ChapterSegment>): Int? {
|
||||||
|
val currentPosition = exoPlayer.currentPosition / 1000
|
||||||
|
return chapters.indexOfLast { currentPosition >= it.start }.takeIf { it >= 0 }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a dialog with the chapters provided, even if the list is empty
|
* Show a dialog with the chapters provided, even if the list is empty
|
||||||
*/
|
*/
|
||||||
@ -477,11 +491,31 @@ object PlayerHelper {
|
|||||||
val titles = chapters.map { chapter ->
|
val titles = chapters.map { chapter ->
|
||||||
"(${DateUtils.formatElapsedTime(chapter.start)}) ${chapter.title}"
|
"(${DateUtils.formatElapsedTime(chapter.start)}) ${chapter.title}"
|
||||||
}
|
}
|
||||||
MaterialAlertDialogBuilder(context)
|
val dialog = MaterialAlertDialogBuilder(context)
|
||||||
.setTitle(R.string.chapters)
|
.setTitle(R.string.chapters)
|
||||||
.setItems(titles.toTypedArray()) { _, index ->
|
.setItems(titles.toTypedArray()) { _, index ->
|
||||||
player.seekTo(chapters[index].start * 1000)
|
player.seekTo(chapters[index].start * 1000)
|
||||||
}
|
}
|
||||||
.show()
|
.create()
|
||||||
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
val updatePosition = Runnable {
|
||||||
|
// scroll to the current playing index in the chapter
|
||||||
|
val currentPosition =
|
||||||
|
getCurrentChapterIndex(player, chapters) ?: return@Runnable
|
||||||
|
dialog.listView.smoothScrollToPosition(currentPosition)
|
||||||
|
val current = dialog.listView.children.toList()[currentPosition]
|
||||||
|
val highlightColor =
|
||||||
|
ThemeHelper.getThemeColor(context, android.R.attr.colorControlHighlight)
|
||||||
|
current.setBackgroundColor(highlightColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.setOnShowListener {
|
||||||
|
updatePosition.run()
|
||||||
|
// update the position after a short delay
|
||||||
|
if (dialog.isShowing) handler.postDelayed(updatePosition, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -582,12 +582,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
binding.playerViewsInfo.text = viewInfo
|
binding.playerViewsInfo.text = viewInfo
|
||||||
|
|
||||||
if (this::chapters.isInitialized && chapters.isNotEmpty()) {
|
if (this::chapters.isInitialized && chapters.isNotEmpty()) {
|
||||||
val chapterIndex = getCurrentChapterIndex() ?: return
|
setCurrentChapterName(true, false)
|
||||||
// scroll to the current chapter in the chapterRecView in the description
|
|
||||||
binding.chaptersRecView.scrollToPosition(chapterIndex)
|
|
||||||
// set selected item, that should be highlighted
|
|
||||||
val chaptersAdapter = binding.chaptersRecView.adapter as ChaptersAdapter
|
|
||||||
chaptersAdapter.updateSelectedPosition(chapterIndex)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1198,17 +1193,17 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set the name of the video chapter in the exoPlayerView
|
// set the name of the video chapter in the exoPlayerView
|
||||||
private fun setCurrentChapterName(position: Long? = null) {
|
private fun setCurrentChapterName(forceUpdate: Boolean = false, enqueueNew: Boolean = true) {
|
||||||
// return if chapters are empty to avoid crashes
|
// return if chapters are empty to avoid crashes
|
||||||
if (chapters.isEmpty() || _binding == null) return
|
if (chapters.isEmpty() || _binding == null) return
|
||||||
|
|
||||||
// call the function again in 100ms
|
// call the function again in 100ms
|
||||||
binding.player.postDelayed(this::setCurrentChapterName, 100)
|
if (enqueueNew) binding.player.postDelayed(this::setCurrentChapterName, 100)
|
||||||
|
|
||||||
// if the user is scrubbing the time bar, don't update
|
// if the user is scrubbing the time bar, don't update
|
||||||
if (scrubbingTimeBar && position == null) return
|
if (scrubbingTimeBar && !forceUpdate) return
|
||||||
|
|
||||||
val chapterIndex = getCurrentChapterIndex(position) ?: return
|
val chapterIndex = PlayerHelper.getCurrentChapterIndex(exoPlayer, chapters) ?: return
|
||||||
val chapterName = chapters[chapterIndex].title.trim()
|
val chapterName = chapters[chapterIndex].title.trim()
|
||||||
|
|
||||||
// change the chapter name textView text to the chapterName
|
// change the chapter name textView text to the chapterName
|
||||||
@ -1220,14 +1215,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the name of the currently played chapter
|
|
||||||
*/
|
|
||||||
private fun getCurrentChapterIndex(position: Long? = null): Int? {
|
|
||||||
val currentPosition = (position ?: exoPlayer.currentPosition) / 1000
|
|
||||||
return chapters.indexOfLast { currentPosition >= it.start }.takeIf { it >= 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setMediaSource(uri: Uri, mimeType: String) {
|
private fun setMediaSource(uri: Uri, mimeType: String) {
|
||||||
val mediaItem = MediaItem.Builder()
|
val mediaItem = MediaItem.Builder()
|
||||||
.setUri(uri)
|
.setUri(uri)
|
||||||
@ -1533,12 +1520,12 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
playerBinding,
|
playerBinding,
|
||||||
streams.duration * 1000,
|
streams.duration * 1000,
|
||||||
onScrub = {
|
onScrub = {
|
||||||
setCurrentChapterName(it)
|
setCurrentChapterName(forceUpdate = true, enqueueNew = false)
|
||||||
scrubbingTimeBar = true
|
scrubbingTimeBar = true
|
||||||
},
|
},
|
||||||
onScrubEnd = {
|
onScrubEnd = {
|
||||||
scrubbingTimeBar = false
|
scrubbingTimeBar = false
|
||||||
setCurrentChapterName(it)
|
setCurrentChapterName(forceUpdate = true, enqueueNew = false)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user