Merge pull request #4090 from Bnyro/master

Chapters dialog: highlight current chapter and auto scroll to it
This commit is contained in:
Bnyro 2023-06-24 18:57:17 +02:00 committed by GitHub
commit a2cc01081b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 29 deletions

View File

@ -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
@ -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()
} }
} }

View File

@ -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)
}, },
), ),
) )