Refactor the SponsorBlock segment handling

This commit is contained in:
Bnyro 2023-02-12 13:17:44 +01:00
parent 7648f1822f
commit 9746d5a5fd
4 changed files with 57 additions and 67 deletions

View File

@ -65,7 +65,6 @@ object PreferenceKeys {
const val SYSTEM_CAPTION_STYLE = "system_caption_style" const val SYSTEM_CAPTION_STYLE = "system_caption_style"
const val CAPTION_SETTINGS = "caption_settings" const val CAPTION_SETTINGS = "caption_settings"
const val SEEK_INCREMENT = "seek_increment" const val SEEK_INCREMENT = "seek_increment"
const val PLAYER_VIDEO_FORMAT = "player_video_format"
const val DEFAULT_RESOLUTION = "default_res" const val DEFAULT_RESOLUTION = "default_res"
const val DEFAULT_RESOLUTION_MOBILE = "default_res_mobile" const val DEFAULT_RESOLUTION_MOBILE = "default_res_mobile"
const val BUFFERING_GOAL = "buffering_goal" const val BUFFERING_GOAL = "buffering_goal"

View File

@ -9,10 +9,12 @@ import android.content.pm.ActivityInfo
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import android.view.accessibility.CaptioningManager import android.view.accessibility.CaptioningManager
import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.PipedStream import com.github.libretube.api.obj.PipedStream
import com.github.libretube.api.obj.Segment
import com.github.libretube.constants.PreferenceKeys import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.enums.AudioQuality import com.github.libretube.enums.AudioQuality
import com.github.libretube.enums.PlayerEvent import com.github.libretube.enums.PlayerEvent
@ -214,12 +216,6 @@ object PlayerHelper {
true true
) )
val videoFormatPreference: String
get() = PreferenceHelper.getString(
PreferenceKeys.PLAYER_VIDEO_FORMAT,
"webm"
)
private val bufferingGoal: Int private val bufferingGoal: Int
get() = PreferenceHelper.getString( get() = PreferenceHelper.getString(
PreferenceKeys.BUFFERING_GOAL, PreferenceKeys.BUFFERING_GOAL,
@ -232,7 +228,7 @@ object PlayerHelper {
true true
) )
val sponsorBlockNotifications: Boolean private val sponsorBlockNotifications: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
"sb_notifications_key", "sb_notifications_key",
true true
@ -481,4 +477,32 @@ object PlayerHelper {
) )
return this return this
} }
/**
* Check for SponsorBlock segments matching the current player position
* @param context A main dispatcher context
* @param segments List of the SponsorBlock segments
* @param skipManually Whether the event gets handled by the function caller
* @return If segment found and [skipManually] is true, the end position of the segment in ms, otherwise null
*/
fun ExoPlayer.checkForSegments(context: Context, segments: List<Segment>, skipManually: Boolean = false): Long? {
segments.forEach { segment ->
val segmentStart = (segment.segment[0] * 1000f).toLong()
val segmentEnd = (segment.segment[1] * 1000f).toLong()
if (currentPosition in segmentStart until segmentEnd) {
if (!skipManually) {
if (sponsorBlockNotifications) {
runCatching {
Toast.makeText(context, R.string.segment_skipped, Toast.LENGTH_SHORT)
.show()
}
}
seekTo(segmentEnd)
} else {
return segmentEnd
}
}
}
return null
}
} }

View File

@ -17,7 +17,7 @@ import androidx.lifecycle.lifecycleScope
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.SegmentData import com.github.libretube.api.obj.Segment
import com.github.libretube.api.obj.Streams import com.github.libretube.api.obj.Streams
import com.github.libretube.constants.BACKGROUND_CHANNEL_ID import com.github.libretube.constants.BACKGROUND_CHANNEL_ID
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
@ -30,6 +30,7 @@ import com.github.libretube.extensions.query
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toStreamItem import com.github.libretube.extensions.toStreamItem
import com.github.libretube.helpers.PlayerHelper import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.PlayerHelper.checkForSegments
import com.github.libretube.helpers.PlayerHelper.loadPlaybackParams import com.github.libretube.helpers.PlayerHelper.loadPlaybackParams
import com.github.libretube.util.NowPlayingNotification import com.github.libretube.util.NowPlayingNotification
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
@ -71,7 +72,7 @@ class BackgroundMode : LifecycleService() {
/** /**
* SponsorBlock Segment data * SponsorBlock Segment data
*/ */
private var segmentData: SegmentData? = null private var segments: List<Segment> = listOf()
/** /**
* [Notification] for the player * [Notification] for the player
@ -223,7 +224,7 @@ class BackgroundMode : LifecycleService() {
} }
} }
fetchSponsorBlockSegments() if (PlayerHelper.sponsorBlockEnabled) fetchSponsorBlockSegments()
} }
/** /**
@ -284,7 +285,7 @@ class BackgroundMode : LifecycleService() {
// play new video on background // play new video on background
this.videoId = nextVideo this.videoId = nextVideo
this.streams = null this.streams = null
this.segmentData = null this.segments = emptyList()
loadAudio(videoId, keepQueue = true) loadAudio(videoId, keepQueue = true)
} }
@ -315,10 +316,10 @@ class BackgroundMode : LifecycleService() {
runCatching { runCatching {
val categories = PlayerHelper.getSponsorBlockCategories() val categories = PlayerHelper.getSponsorBlockCategories()
if (categories.isEmpty()) return@runCatching if (categories.isEmpty()) return@runCatching
segmentData = RetrofitInstance.api.getSegments( segments = RetrofitInstance.api.getSegments(
videoId, videoId,
JsonHelper.json.encodeToString(categories) JsonHelper.json.encodeToString(categories)
) ).segments
checkForSegments() checkForSegments()
} }
} }
@ -328,24 +329,9 @@ class BackgroundMode : LifecycleService() {
* check for SponsorBlock segments * check for SponsorBlock segments
*/ */
private fun checkForSegments() { private fun checkForSegments() {
Handler(Looper.getMainLooper()).postDelayed(this::checkForSegments, 100) handler.postDelayed(this::checkForSegments, 100)
if (segmentData == null || segmentData!!.segments.isEmpty()) return player?.checkForSegments(this, segments)
segmentData!!.segments.forEach { segment ->
val segmentStart = (segment.segment[0] * 1000f).toLong()
val segmentEnd = (segment.segment[1] * 1000f).toLong()
val currentPosition = player?.currentPosition
if (currentPosition in segmentStart until segmentEnd) {
if (PlayerHelper.sponsorBlockNotifications) {
runCatching {
Toast.makeText(this, R.string.segment_skipped, Toast.LENGTH_SHORT)
.show()
}
}
player?.seekTo(segmentEnd)
}
}
} }
private fun updateQueue() { private fun updateQueue() {

View File

@ -46,7 +46,6 @@ import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.ChapterSegment import com.github.libretube.api.obj.ChapterSegment
import com.github.libretube.api.obj.PipedStream import com.github.libretube.api.obj.PipedStream
import com.github.libretube.api.obj.Segment import com.github.libretube.api.obj.Segment
import com.github.libretube.api.obj.SegmentData
import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.StreamItem
import com.github.libretube.api.obj.Streams import com.github.libretube.api.obj.Streams
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
@ -73,6 +72,7 @@ import com.github.libretube.helpers.DashHelper
import com.github.libretube.helpers.ImageHelper import com.github.libretube.helpers.ImageHelper
import com.github.libretube.helpers.NavigationHelper import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.helpers.PlayerHelper import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.PlayerHelper.checkForSegments
import com.github.libretube.helpers.PlayerHelper.loadPlaybackParams import com.github.libretube.helpers.PlayerHelper.loadPlaybackParams
import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.obj.ShareData import com.github.libretube.obj.ShareData
@ -114,10 +114,6 @@ import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.android.exoplayer2.upstream.DefaultDataSource import com.google.android.exoplayer2.upstream.DefaultDataSource
import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.IOException
import java.util.*
import java.util.concurrent.Executors
import kotlin.math.abs
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -125,6 +121,10 @@ import kotlinx.coroutines.withContext
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException
import java.util.*
import java.util.concurrent.Executors
import kotlin.math.abs
class PlayerFragment : BaseFragment(), OnlinePlayerOptions { class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
@ -182,7 +182,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
/** /**
* SponsorBlock * SponsorBlock
*/ */
private lateinit var segmentData: SegmentData private var segments = listOf<Segment>()
private var sponsorBlockEnabled = PlayerHelper.sponsorBlockEnabled private var sponsorBlockEnabled = PlayerHelper.sponsorBlockEnabled
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
@ -642,31 +642,14 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
if (!sponsorBlockEnabled) return if (!sponsorBlockEnabled) return
if (!::segmentData.isInitialized || segmentData.segments.isEmpty()) return if (segments.isEmpty()) return
val currentPosition = exoPlayer.currentPosition exoPlayer.checkForSegments(requireContext(), segments, PlayerHelper.skipSegmentsManually)?.let { segmentEnd ->
segmentData.segments.forEach { segment: Segment -> binding.sbSkipBtn.visibility = View.VISIBLE
val segmentStart = (segment.segment[0] * 1000f).toLong() binding.sbSkipBtn.setOnClickListener {
val segmentEnd = (segment.segment[1] * 1000f).toLong()
// show the button to manually skip the segment
if (currentPosition in segmentStart until segmentEnd) {
if (PlayerHelper.skipSegmentsManually) {
binding.sbSkipBtn.visibility = View.VISIBLE
binding.sbSkipBtn.setOnClickListener {
exoPlayer.seekTo(segmentEnd)
}
return
}
if (PlayerHelper.sponsorBlockNotifications) {
Toast.makeText(context, R.string.segment_skipped, Toast.LENGTH_SHORT).show()
}
// skip the segment automatically
exoPlayer.seekTo(segmentEnd) exoPlayer.seekTo(segmentEnd)
return
} }
return
} }
if (PlayerHelper.skipSegmentsManually) binding.sbSkipBtn.visibility = View.GONE if (PlayerHelper.skipSegmentsManually) binding.sbSkipBtn.visibility = View.GONE
@ -764,13 +747,13 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
runCatching { runCatching {
val categories = PlayerHelper.getSponsorBlockCategories() val categories = PlayerHelper.getSponsorBlockCategories()
if (categories.isEmpty()) return@runCatching if (categories.isEmpty()) return@runCatching
segmentData = segments =
RetrofitInstance.api.getSegments( RetrofitInstance.api.getSegments(
videoId!!, videoId!!,
JsonHelper.json.encodeToString(categories) JsonHelper.json.encodeToString(categories)
) ).segments
if (segmentData.segments.isEmpty()) return@runCatching if (segments.isEmpty()) return@runCatching
playerBinding.exoProgress.setSegments(segmentData.segments) playerBinding.exoProgress.setSegments(segments)
runOnUiThread { runOnUiThread {
playerBinding.sbToggle.visibility = View.VISIBLE playerBinding.sbToggle.visibility = View.VISIBLE
updateDisplayedDuration() updateDisplayedDuration()
@ -1103,12 +1086,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerBinding.duration.text = DateUtils.formatElapsedTime( playerBinding.duration.text = DateUtils.formatElapsedTime(
exoPlayer.duration.div(1000) exoPlayer.duration.div(1000)
) )
if (!this::segmentData.isInitialized || this.segmentData.segments.isEmpty()) { if (segments.isEmpty()) return
return
}
val durationWithSb = DateUtils.formatElapsedTime( val durationWithSb = DateUtils.formatElapsedTime(
exoPlayer.duration.div(1000) - segmentData.segments.sumOf { exoPlayer.duration.div(1000) - segments.sumOf {
it.segment[1] - it.segment[0] it.segment[1] - it.segment[0]
}.toInt() }.toInt()
) )