Merge pull request #3036 from Bnyro/master

Fix SponsorBlock segment loop at end of video
This commit is contained in:
Bnyro 2023-02-12 13:28:08 +01:00 committed by GitHub
commit 954681be9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 63 deletions

View File

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

View File

@ -9,10 +9,12 @@ import android.content.pm.ActivityInfo
import android.graphics.drawable.Icon
import android.os.Build
import android.view.accessibility.CaptioningManager
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import com.github.libretube.R
import com.github.libretube.api.obj.PipedStream
import com.github.libretube.api.obj.Segment
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.enums.AudioQuality
import com.github.libretube.enums.PlayerEvent
@ -24,6 +26,7 @@ import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ui.CaptionStyleCompat
import com.google.android.exoplayer2.video.VideoSize
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
object PlayerHelper {
@ -214,12 +217,6 @@ object PlayerHelper {
true
)
val videoFormatPreference: String
get() = PreferenceHelper.getString(
PreferenceKeys.PLAYER_VIDEO_FORMAT,
"webm"
)
private val bufferingGoal: Int
get() = PreferenceHelper.getString(
PreferenceKeys.BUFFERING_GOAL,
@ -232,7 +229,7 @@ object PlayerHelper {
true
)
val sponsorBlockNotifications: Boolean
private val sponsorBlockNotifications: Boolean
get() = PreferenceHelper.getBoolean(
"sb_notifications_key",
true
@ -481,4 +478,40 @@ object PlayerHelper {
)
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? {
for (segment in segments) {
val segmentStart = (segment.segment[0] * 1000f).toLong()
val segmentEnd = (segment.segment[1] * 1000f).toLong()
// avoid seeking to the same segment multiple times, e.g. when the SB segment is at the end of the video
if ((duration - currentPosition).absoluteValue < 500) continue
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.api.JsonHelper
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.constants.BACKGROUND_CHANNEL_ID
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.toStreamItem
import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.PlayerHelper.checkForSegments
import com.github.libretube.helpers.PlayerHelper.loadPlaybackParams
import com.github.libretube.util.NowPlayingNotification
import com.github.libretube.util.PlayingQueue
@ -71,7 +72,7 @@ class BackgroundMode : LifecycleService() {
/**
* SponsorBlock Segment data
*/
private var segmentData: SegmentData? = null
private var segments: List<Segment> = listOf()
/**
* [Notification] for the player
@ -111,6 +112,7 @@ class BackgroundMode : LifecycleService() {
val notification: Notification = Notification.Builder(this, BACKGROUND_CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.playingOnBackground))
.setSmallIcon(R.drawable.ic_launcher_lockscreen)
.build()
startForeground(PLAYER_NOTIFICATION_ID, notification)
@ -223,7 +225,7 @@ class BackgroundMode : LifecycleService() {
}
}
fetchSponsorBlockSegments()
if (PlayerHelper.sponsorBlockEnabled) fetchSponsorBlockSegments()
}
/**
@ -284,7 +286,7 @@ class BackgroundMode : LifecycleService() {
// play new video on background
this.videoId = nextVideo
this.streams = null
this.segmentData = null
this.segments = emptyList()
loadAudio(videoId, keepQueue = true)
}
@ -315,10 +317,10 @@ class BackgroundMode : LifecycleService() {
runCatching {
val categories = PlayerHelper.getSponsorBlockCategories()
if (categories.isEmpty()) return@runCatching
segmentData = RetrofitInstance.api.getSegments(
segments = RetrofitInstance.api.getSegments(
videoId,
JsonHelper.json.encodeToString(categories)
)
).segments
checkForSegments()
}
}
@ -328,24 +330,9 @@ class BackgroundMode : LifecycleService() {
* check for SponsorBlock segments
*/
private fun checkForSegments() {
Handler(Looper.getMainLooper()).postDelayed(this::checkForSegments, 100)
handler.postDelayed(this::checkForSegments, 100)
if (segmentData == null || segmentData!!.segments.isEmpty()) return
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)
}
}
player?.checkForSegments(this, segments)
}
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.PipedStream
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.Streams
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.NavigationHelper
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.PreferenceHelper
import com.github.libretube.obj.ShareData
@ -182,7 +182,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
/**
* SponsorBlock
*/
private lateinit var segmentData: SegmentData
private var segments = listOf<Segment>()
private var sponsorBlockEnabled = PlayerHelper.sponsorBlockEnabled
val handler = Handler(Looper.getMainLooper())
@ -642,31 +642,14 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
if (!sponsorBlockEnabled) return
if (!::segmentData.isInitialized || segmentData.segments.isEmpty()) return
if (segments.isEmpty()) return
val currentPosition = exoPlayer.currentPosition
segmentData.segments.forEach { segment: Segment ->
val segmentStart = (segment.segment[0] * 1000f).toLong()
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.checkForSegments(requireContext(), segments, PlayerHelper.skipSegmentsManually)?.let { segmentEnd ->
binding.sbSkipBtn.visibility = View.VISIBLE
binding.sbSkipBtn.setOnClickListener {
exoPlayer.seekTo(segmentEnd)
return
}
return
}
if (PlayerHelper.skipSegmentsManually) binding.sbSkipBtn.visibility = View.GONE
@ -764,13 +747,13 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
runCatching {
val categories = PlayerHelper.getSponsorBlockCategories()
if (categories.isEmpty()) return@runCatching
segmentData =
segments =
RetrofitInstance.api.getSegments(
videoId!!,
JsonHelper.json.encodeToString(categories)
)
if (segmentData.segments.isEmpty()) return@runCatching
playerBinding.exoProgress.setSegments(segmentData.segments)
).segments
if (segments.isEmpty()) return@runCatching
playerBinding.exoProgress.setSegments(segments)
runOnUiThread {
playerBinding.sbToggle.visibility = View.VISIBLE
updateDisplayedDuration()
@ -1103,12 +1086,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerBinding.duration.text = DateUtils.formatElapsedTime(
exoPlayer.duration.div(1000)
)
if (!this::segmentData.isInitialized || this.segmentData.segments.isEmpty()) {
return
}
if (segments.isEmpty()) return
val durationWithSb = DateUtils.formatElapsedTime(
exoPlayer.duration.div(1000) - segmentData.segments.sumOf {
exoPlayer.duration.div(1000) - segments.sumOf {
it.segment[1] - it.segment[0]
}.toInt()
)