mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 00:10:32 +05:30
Refactor the SponsorBlock segment handling
This commit is contained in:
parent
7648f1822f
commit
9746d5a5fd
@ -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"
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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()
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user