Skip to content

Commit

Permalink
Merge pull request #15 from aniyomiorg/dev
Browse files Browse the repository at this point in the history
feat(player): Implement a new seekbar (aniyomiorg#1061)
  • Loading branch information
LuftVerbot authored Jul 11, 2023
2 parents 6b084c6 + 27a4014 commit b361eb3
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 140 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ dependencies {
// FFmpeg-kit
implementation(libs.ffmpeg.kit)
implementation(libs.arthenica.smartexceptions)
// seeker seek bar
implementation(libs.seeker)
}

androidComponents {
Expand Down
141 changes: 125 additions & 16 deletions app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.databinding.PlayerActivityBinding
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.player.settings.PlayerChaptersSheet
import eu.kanade.tachiyomi.ui.player.settings.PlayerOptionsSheet
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.ui.player.settings.PlayerScreenshotSheet
Expand All @@ -67,7 +68,7 @@ import eu.kanade.tachiyomi.util.system.powerManager
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast
import `is`.xyz.mpv.MPVLib
import `is`.xyz.mpv.MPVView
import `is`.xyz.mpv.MPVView.Chapter
import `is`.xyz.mpv.Utils
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
Expand Down Expand Up @@ -246,6 +247,14 @@ class PlayerActivity : BaseActivity() {

private var hadPreviousAudio = false

private var videoChapters: List<Chapter> = emptyList()
set(value) {
field = value
runOnUiThread {
playerControls.seekbar.updateSeekbar(chapters = value)
}
}

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
playerControls.resetControlsFade()
return super.dispatchTouchEvent(ev)
Expand Down Expand Up @@ -320,6 +329,7 @@ class PlayerActivity : BaseActivity() {

val logLevel = if (viewModel.networkPreferences.verboseLogging().get()) "info" else "warn"
player.initialize(applicationContext.filesDir.path, logLevel)
MPVLib.observeProperty("chapter-list", MPVLib.mpvFormat.MPV_FORMAT_NONE)
mpvUpdateHwDec(HwDecState.get(playerPreferences.standardHwDec().get()))
MPVLib.setOptionString("keep-open", "always")
MPVLib.setOptionString("ytdl", "no")
Expand Down Expand Up @@ -535,6 +545,8 @@ class PlayerActivity : BaseActivity() {
}
player.paused = true
showLoadingIndicator(true)
videoChapters = emptyList()
aniskipStamps = emptyList()

lifecycleScope.launch {
viewModel.mutableState.update {
Expand Down Expand Up @@ -635,7 +647,13 @@ class PlayerActivity : BaseActivity() {
binding.secondsView.stop()
}

fun doubleTapSeek(time: Int, event: MotionEvent? = null, isDoubleTap: Boolean = true) {
fun doubleTapSeek(
time: Int,
event: MotionEvent? = null,
isDoubleTap: Boolean = true,
isChapter: Boolean = false,
text: String? = null,
) {
if (SeekState.mode != SeekState.DOUBLE_TAP) {
doubleTapBg = if (time < 0) binding.rewBg else binding.ffwdBg
}
Expand Down Expand Up @@ -664,7 +682,11 @@ class PlayerActivity : BaseActivity() {
}
doubleTapBg.visibility = View.VISIBLE

binding.secondsView.seconds -= time
if (isChapter) {
binding.secondsView.binding.doubleTapSeconds.text = text
} else {
binding.secondsView.seconds -= time
}
} else {
binding.secondsView.updateLayoutParams<ConstraintLayout.LayoutParams> {
rightToRight = ConstraintLayout.LayoutParams.PARENT_ID
Expand All @@ -679,9 +701,15 @@ class PlayerActivity : BaseActivity() {
}
doubleTapBg.visibility = View.VISIBLE

binding.secondsView.seconds += time
if (isChapter) {
binding.secondsView.binding.doubleTapSeconds.text = text
} else {
binding.secondsView.seconds += time
}
}
if (!isChapter) {
playerControls.hideUiForSeek()
}
playerControls.hideUiForSeek()
binding.secondsView.start()
ViewAnimationUtils.createCircularReveal(view, x, y, 0f, kotlin.math.max(view.height, view.width).toFloat()).setDuration(500).start()

Expand Down Expand Up @@ -818,19 +846,25 @@ class PlayerActivity : BaseActivity() {
setVideoList(quality, currentVideoList)
}

private fun setChapter(index: Int) {
player.timePos = player.loadChapters()[index].time.roundToInt()
player.paused = false
private fun setChapter(chapter: Chapter) {
val seekDifference = chapter.time.roundToInt() - (player.timePos ?: 0)
doubleTapSeek(
time = seekDifference,
isDoubleTap = false,
isChapter = true,
text = chapter.title.takeIf { !it.isNullOrBlank() }
?: Utils.prettyTime(chapter.time.roundToInt()),
)
}

@Suppress("UNUSED_PARAMETER")
fun pickChapter(view: View) {
playerControls.hideControls(true)
PlayerChaptersSheet(
this,
R.string.chapter_dialog_header,
::setChapter,
player.loadChapters(),
activity = this@PlayerActivity,
textRes = R.string.chapter_dialog_header,
seekToChapterMethod = ::setChapter,
chapters = videoChapters,
).show()
}

Expand Down Expand Up @@ -1302,9 +1336,6 @@ class PlayerActivity : BaseActivity() {
}

viewModel.viewModelScope.launchUI {
if (player.loadChapters() != emptyList<MPVView.Chapter>()) {
binding.playerControls.binding.chaptersBtn.visibility = View.VISIBLE
}
if (playerPreferences.adjustOrientationVideoDimensions().get()) {
if ((player.videoW ?: 1) / (player.videoH ?: 1) >= 1) {
this@PlayerActivity.requestedOrientation = playerPreferences.defaultPlayerOrientationLandscape().get()
Expand All @@ -1323,10 +1354,82 @@ class PlayerActivity : BaseActivity() {
waitingAniSkip = playerPreferences.waitingTimeAniSkip().get()
runBlocking {
aniSkipInterval = viewModel.aniSkipResponse(player.duration)
playerControls.binding.playbackSeekbar.setStamps(aniSkipInterval)
aniSkipInterval?.let {
aniskipStamps = it
updateChapters(it, player.duration)
}
}
}

private var aniskipStamps: List<Stamp> = emptyList()

private fun updateChapters(stamps: List<Stamp>? = null, duration: Int? = null) {
val aniskipStamps = stamps ?: aniskipStamps
val sortedAniskipStamps = aniskipStamps.sortedBy { it.interval.startTime }
val aniskipChapters = sortedAniskipStamps.mapIndexed { i, it ->
val startTime = if (i == 0 && it.interval.startTime < 1.0) {
0.0
} else {
it.interval.startTime
}
val startChapter = Chapter(
index = -2, // Index -2 is used to indicate that this is an AniSkip chapter
title = it.skipType.getString(),
time = startTime,
)
val nextStart = sortedAniskipStamps.getOrNull(i + 1)?.interval?.startTime
val isNotLastChapter = abs(it.interval.endTime - (duration?.toDouble() ?: -2.0)) > 1.0
val isNotAdjacent = nextStart == null || (abs(it.interval.endTime - nextStart) > 1.0)
if (isNotLastChapter && isNotAdjacent) {
val endChapter = Chapter(
index = -1,
title = null,
time = it.interval.endTime,
)
return@mapIndexed listOf(startChapter, endChapter)
} else {
listOf(startChapter)
}
}.flatten()
val playerChapters = player.loadChapters().filter { playerChapter ->
aniskipChapters.none { aniskipChapter ->
abs(aniskipChapter.time - playerChapter.time) < 1.0 && aniskipChapter.index == -2
}
}.sortedBy { it.time }.mapIndexed { i, it ->
if (i == 0 && it.time < 1.0) {
Chapter(
it.index,
it.title,
0.0,
)
} else {
it
}
}
val filteredAniskipChapters = aniskipChapters.filter { aniskipChapter ->
playerChapters.none { playerChapter ->
abs(aniskipChapter.time - playerChapter.time) < 1.0 && aniskipChapter.index != -2
}
}
val startChapter = if ((playerChapters + filteredAniskipChapters).isNotEmpty() &&
playerChapters.none { it.time == 0.0 } &&
filteredAniskipChapters.none { it.time == 0.0 }
) {
listOf(
Chapter(
index = -1,
title = null,
time = 0.0,
),
)
} else {
emptyList()
}
val combinedChapters = (startChapter + playerChapters + filteredAniskipChapters).sortedBy { it.time }
runOnUiThread { binding.playerControls.binding.chaptersBtn.isVisible = combinedChapters.isNotEmpty() }
videoChapters = combinedChapters
}

private val aniSkipEnable = playerPreferences.aniSkipEnabled().get()
private val autoSkipAniSkip = playerPreferences.autoSkipAniSkip().get()
private val netflixStyle = playerPreferences.enableNetflixStyleAniSkip().get()
Expand Down Expand Up @@ -1398,6 +1501,12 @@ class PlayerActivity : BaseActivity() {
}
}

internal fun eventPropertyUi(property: String) {
when (property) {
"chapter-list" -> updateChapters()
}
}

private val nextEpisodeRunnable = Runnable { switchEpisode(previous = false, autoPlay = true) }

private fun endFile(eofReached: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class PlayerObserver(val activity: PlayerActivity) :
MPVLib.EventObserver,
MPVLib.LogObserver {

override fun eventProperty(property: String) {}
override fun eventProperty(property: String) {
activity.runOnUiThread { activity.eventPropertyUi(property) }
}

override fun eventProperty(property: String, value: Boolean) {
activity.runOnUiThread { activity.eventPropertyUi(property, value) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,43 @@
package eu.kanade.tachiyomi.ui.player
package eu.kanade.tachiyomi.ui.player.settings

import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.PlayerChaptersItemBinding
import eu.kanade.tachiyomi.databinding.PlayerChaptersSheetBinding
import eu.kanade.tachiyomi.ui.player.PlayerActivity
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.widget.sheet.PlayerBottomSheetDialog
import `is`.xyz.mpv.MPVView
import `is`.xyz.mpv.MPVView.Chapter
import `is`.xyz.mpv.Utils
import kotlin.math.roundToInt

/** Sheet to show when Chapter selection buttons in player are clicked. */
class PlayerChaptersSheet(
private val activity: PlayerActivity,
@StringRes
private val textRes: Int,
private val seekToChapterMethod: (Int) -> Unit,
private val chapters: MutableList<MPVView.Chapter>,
private val seekToChapterMethod: (Chapter) -> Unit,
private val chapters: List<Chapter>,
) : PlayerBottomSheetDialog(activity) {

private lateinit var binding: PlayerChaptersSheetBinding
private var wasPaused: Boolean? = null

@SuppressLint("SetTextI18n")
override fun createView(inflater: LayoutInflater): View {
wasPaused = activity.player.paused
activity.player.paused = true
binding = PlayerChaptersSheetBinding.inflate(activity.layoutInflater, null, false)

binding.chapterSelectionHeader.setText(textRes)
chapters.forEachIndexed { i, chapter ->
val chapterView = PlayerChaptersItemBinding.inflate(activity.layoutInflater).root
chapterView.text = "${chapter.title}(${Utils.prettyTime(chapter.time.roundToInt())})"
chapterView.text = if (chapter.title.isNullOrBlank()) {
Utils.prettyTime(chapter.time.roundToInt())
} else {
"${chapter.title} (${Utils.prettyTime(chapter.time.roundToInt())})"
}
// Highlighted the current chapter
if (i == chapters.lastIndex) {
if (activity.player.timePos!!.toInt() >= chapter.time.toInt()) {
Expand All @@ -45,7 +51,7 @@ class PlayerChaptersSheet(
chapterView.setTextColor(context.getResourceColor(R.attr.colorOnPrimary))
}
chapterView.setOnClickListener {
seekToChapterMethod(i)
seekToChapterMethod(chapters[i])
this.dismiss()
}
binding.linearLayout.addView(chapterView)
Expand Down
Loading

0 comments on commit b361eb3

Please sign in to comment.