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 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
@ -24,6 +26,7 @@ import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ui.CaptionStyleCompat import com.google.android.exoplayer2.ui.CaptionStyleCompat
import com.google.android.exoplayer2.video.VideoSize import com.google.android.exoplayer2.video.VideoSize
import kotlin.math.absoluteValue
import kotlin.math.roundToInt import kotlin.math.roundToInt
object PlayerHelper { object PlayerHelper {
@ -214,12 +217,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 +229,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 +478,40 @@ 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? {
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.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
@ -111,6 +112,7 @@ class BackgroundMode : LifecycleService() {
val notification: Notification = Notification.Builder(this, BACKGROUND_CHANNEL_ID) val notification: Notification = Notification.Builder(this, BACKGROUND_CHANNEL_ID)
.setContentTitle(getString(R.string.app_name)) .setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.playingOnBackground)) .setContentText(getString(R.string.playingOnBackground))
.setSmallIcon(R.drawable.ic_launcher_lockscreen)
.build() .build()
startForeground(PLAYER_NOTIFICATION_ID, notification) 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 // 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 +317,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 +330,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
@ -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()
) )