Merge pull request #947 from Bnyro/sb

SponsorBlock for Background Play
This commit is contained in:
Bnyro 2022-08-02 09:39:46 +00:00 committed by GitHub
commit 634f98b06d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 100 deletions

View File

@ -18,7 +18,6 @@ import android.os.Looper
import android.os.PowerManager import android.os.PowerManager
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
import android.text.Html import android.text.Html
import android.text.TextUtils
import android.text.format.DateUtils import android.text.format.DateUtils
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -35,6 +34,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.BACKGROUND_CHANNEL_ID import com.github.libretube.BACKGROUND_CHANNEL_ID
import com.github.libretube.Globals import com.github.libretube.Globals
import com.github.libretube.PLAYER_NOTIFICATION_ID import com.github.libretube.PLAYER_NOTIFICATION_ID
@ -53,7 +53,6 @@ import com.github.libretube.obj.ChapterSegment
import com.github.libretube.obj.Playlist import com.github.libretube.obj.Playlist
import com.github.libretube.obj.Segment import com.github.libretube.obj.Segment
import com.github.libretube.obj.Segments import com.github.libretube.obj.Segments
import com.github.libretube.obj.SponsorBlockPrefs
import com.github.libretube.obj.StreamItem import com.github.libretube.obj.StreamItem
import com.github.libretube.obj.Streams import com.github.libretube.obj.Streams
import com.github.libretube.obj.Subscribe import com.github.libretube.obj.Subscribe
@ -157,7 +156,6 @@ class PlayerFragment : Fragment() {
private var token = "" private var token = ""
private var relatedStreamsEnabled = true private var relatedStreamsEnabled = true
private var autoplayEnabled = false private var autoplayEnabled = false
private val sponsorBlockPrefs = SponsorBlockPrefs()
private var autoRotationEnabled = true private var autoRotationEnabled = true
private var playbackSpeed = "1F" private var playbackSpeed = "1F"
private var pausePlayerOnScreenOffEnabled = false private var pausePlayerOnScreenOffEnabled = false
@ -171,6 +169,8 @@ class PlayerFragment : Fragment() {
private var bufferingGoal = 50000 private var bufferingGoal = 50000
private var seekBarPreview = false private var seekBarPreview = false
private var defaultSubtitle = "" private var defaultSubtitle = ""
private var sponsorBlockEnabled = true
private var sponsorBlockNotifications = true
/** /**
* for autoplay * for autoplay
@ -234,7 +234,6 @@ class PlayerFragment : Fragment() {
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
} }
setSponsorBlockPrefs()
createExoPlayer() createExoPlayer()
initializeTransitionLayout() initializeTransitionLayout()
initializeOnClickActions() initializeOnClickActions()
@ -315,6 +314,16 @@ class PlayerFragment : Fragment() {
false false
) )
sponsorBlockEnabled = PreferenceHelper.getBoolean(
"sb_enabled_key",
true
)
sponsorBlockNotifications = PreferenceHelper.getBoolean(
"sb_notifications_key",
true
)
defaultSubtitle = PreferenceHelper.getString( defaultSubtitle = PreferenceHelper.getString(
PreferenceKeys.DEFAULT_SUBTITLE, PreferenceKeys.DEFAULT_SUBTITLE,
"" ""
@ -325,29 +334,6 @@ class PlayerFragment : Fragment() {
} }
} }
private fun setSponsorBlockPrefs() {
sponsorBlockPrefs.sponsorBlockEnabled =
PreferenceHelper.getBoolean("sb_enabled_key", true)
sponsorBlockPrefs.sponsorNotificationsEnabled =
PreferenceHelper.getBoolean("sb_notifications_key", true)
sponsorBlockPrefs.introEnabled =
PreferenceHelper.getBoolean("intro_category_key", false)
sponsorBlockPrefs.selfPromoEnabled =
PreferenceHelper.getBoolean("selfpromo_category_key", false)
sponsorBlockPrefs.interactionEnabled =
PreferenceHelper.getBoolean("interaction_category_key", false)
sponsorBlockPrefs.sponsorsEnabled =
PreferenceHelper.getBoolean("sponsors_category_key", true)
sponsorBlockPrefs.outroEnabled =
PreferenceHelper.getBoolean("outro_category_key", false)
sponsorBlockPrefs.fillerEnabled =
PreferenceHelper.getBoolean("filler_category_key", false)
sponsorBlockPrefs.musicOffTopicEnabled =
PreferenceHelper.getBoolean("music_offtopic_category_key", false)
sponsorBlockPrefs.previewEnabled =
PreferenceHelper.getBoolean("preview_category_key", false)
}
private fun initializeTransitionLayout() { private fun initializeTransitionLayout() {
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
mainActivity.binding.container.visibility = View.VISIBLE mainActivity.binding.container.visibility = View.VISIBLE
@ -729,20 +715,20 @@ class PlayerFragment : Fragment() {
} }
private fun checkForSegments() { private fun checkForSegments() {
if (!exoPlayer.isPlaying || !sponsorBlockPrefs.sponsorBlockEnabled) return if (!exoPlayer.isPlaying || !sponsorBlockEnabled) return
exoPlayerView.postDelayed(this::checkForSegments, 100) Handler(Looper.getMainLooper()).postDelayed(this::checkForSegments, 100)
if (!::segmentData.isInitialized || segmentData.segments.isEmpty()) { if (!::segmentData.isInitialized || segmentData.segments.isEmpty()) {
return return
} }
segmentData.segments.forEach { segment: Segment -> segmentData.segments.forEach { segment: Segment ->
val segmentStart = (segment.segment!![0] * 1000.0f).toLong() val segmentStart = (segment.segment!![0] * 1000f).toLong()
val segmentEnd = (segment.segment[1] * 1000.0f).toLong() val segmentEnd = (segment.segment[1] * 1000f).toLong()
val currentPosition = exoPlayer.currentPosition val currentPosition = exoPlayer.currentPosition
if (currentPosition in segmentStart until segmentEnd) { if (currentPosition in segmentStart until segmentEnd) {
if (sponsorBlockPrefs.sponsorNotificationsEnabled) { if (sponsorBlockNotifications) {
Toast.makeText(context, R.string.segment_skipped, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.segment_skipped, Toast.LENGTH_SHORT).show()
} }
exoPlayer.seekTo(segmentEnd) exoPlayer.seekTo(segmentEnd)
@ -784,7 +770,7 @@ class PlayerFragment : Fragment() {
exoPlayer.play() exoPlayer.play()
exoPlayerView.useController = true exoPlayerView.useController = true
initializePlayerNotification(requireContext()) initializePlayerNotification(requireContext())
fetchSponsorBlockSegments() if (sponsorBlockEnabled) fetchSponsorBlockSegments()
// show comments if related streams disabled // show comments if related streams disabled
if (!relatedStreamsEnabled) toggleComments() if (!relatedStreamsEnabled) toggleComments()
// prepare for autoplay // prepare for autoplay
@ -798,6 +784,24 @@ class PlayerFragment : Fragment() {
run() run()
} }
/**
* fetch the segments for SponsorBlock
*/
private fun fetchSponsorBlockSegments() {
CoroutineScope(Dispatchers.IO).launch {
kotlin.runCatching {
val categories = PlayerHelper.getSponsorBlockCategories()
if (categories.size > 0) {
segmentData =
RetrofitInstance.api.getSegments(
videoId!!,
ObjectMapper().writeValueAsString(categories)
)
}
}
}
}
private fun refreshLiveStatus() { private fun refreshLiveStatus() {
// switch back to normal speed when on the end of live stream // switch back to normal speed when on the end of live stream
if (exoPlayer.duration - exoPlayer.currentPosition < 7000) { if (exoPlayer.duration - exoPlayer.currentPosition < 7000) {
@ -899,56 +903,6 @@ class PlayerFragment : Fragment() {
} }
} }
private fun fetchSponsorBlockSegments() {
fun run() {
lifecycleScope.launch(Dispatchers.IO) {
if (sponsorBlockPrefs.sponsorBlockEnabled) {
val categories: ArrayList<String> = arrayListOf()
if (sponsorBlockPrefs.introEnabled) {
categories.add("intro")
}
if (sponsorBlockPrefs.selfPromoEnabled) {
categories.add("selfpromo")
}
if (sponsorBlockPrefs.interactionEnabled) {
categories.add("interaction")
}
if (sponsorBlockPrefs.sponsorsEnabled) {
categories.add("sponsor")
}
if (sponsorBlockPrefs.outroEnabled) {
categories.add("outro")
}
if (sponsorBlockPrefs.fillerEnabled) {
categories.add("filler")
}
if (sponsorBlockPrefs.musicOffTopicEnabled) {
categories.add("music_offtopic")
}
if (sponsorBlockPrefs.previewEnabled) {
categories.add("preview")
}
if (categories.size > 0) {
segmentData = try {
RetrofitInstance.api.getSegments(
videoId!!,
"[\"" + TextUtils.join("\",\"", categories) + "\"]"
)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
return@launch
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response")
return@launch
}
}
}
}
}
run()
}
private fun prepareExoPlayerView() { private fun prepareExoPlayerView() {
exoPlayerView.apply { exoPlayerView.apply {
setShowSubtitleButton(true) setShowSubtitleButton(true)
@ -1014,8 +968,8 @@ class PlayerFragment : Fragment() {
// Listener for play and pause icon change // Listener for play and pause icon change
exoPlayer.addListener(object : Player.Listener { exoPlayer.addListener(object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
if (isPlaying && sponsorBlockPrefs.sponsorBlockEnabled) { if (isPlaying && sponsorBlockEnabled) {
exoPlayerView.postDelayed( Handler(Looper.getMainLooper()).postDelayed(
this@PlayerFragment::checkForSegments, this@PlayerFragment::checkForSegments,
100 100
) )

View File

@ -1,14 +0,0 @@
package com.github.libretube.obj
class SponsorBlockPrefs(
var sponsorBlockEnabled: Boolean = false,
var sponsorNotificationsEnabled: Boolean = false,
var sponsorsEnabled: Boolean = false,
var selfPromoEnabled: Boolean = false,
var interactionEnabled: Boolean = false,
var introEnabled: Boolean = false,
var outroEnabled: Boolean = false,
var fillerEnabled: Boolean = false,
var musicOffTopicEnabled: Boolean = false,
var previewEnabled: Boolean = false
)

View File

@ -7,15 +7,21 @@ import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.Looper
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.BACKGROUND_CHANNEL_ID import com.github.libretube.BACKGROUND_CHANNEL_ID
import com.github.libretube.PLAYER_NOTIFICATION_ID import com.github.libretube.PLAYER_NOTIFICATION_ID
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.obj.Segment
import com.github.libretube.obj.Segments
import com.github.libretube.obj.Streams import com.github.libretube.obj.Streams
import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.preferences.PreferenceKeys import com.github.libretube.preferences.PreferenceKeys
import com.github.libretube.util.DescriptionAdapter import com.github.libretube.util.DescriptionAdapter
import com.github.libretube.util.PlayerHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.toID import com.github.libretube.util.toID
import com.google.android.exoplayer2.C import com.google.android.exoplayer2.C
@ -25,6 +31,8 @@ import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ui.PlayerNotificationManager import com.google.android.exoplayer2.ui.PlayerNotificationManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -32,6 +40,11 @@ import kotlinx.coroutines.runBlocking
* Loads the selected videos audio in background mode with a notification area. * Loads the selected videos audio in background mode with a notification area.
*/ */
class BackgroundMode : Service() { class BackgroundMode : Service() {
/**
* VideoId of the video
*/
private lateinit var videoId: String
/** /**
* The response that gets when called the Api. * The response that gets when called the Api.
*/ */
@ -63,8 +76,16 @@ class BackgroundMode : Service() {
*/ */
private lateinit var audioAttributes: AudioAttributes private lateinit var audioAttributes: AudioAttributes
/**
* SponsorBlock Segment data
*/
private var segmentData: Segments? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
/**
* setting the required notification for running as a foreground service
*/
if (Build.VERSION.SDK_INT >= 26) { if (Build.VERSION.SDK_INT >= 26) {
val channelId = BACKGROUND_CHANNEL_ID val channelId = BACKGROUND_CHANNEL_ID
val channel = NotificationChannel( val channel = NotificationChannel(
@ -89,7 +110,7 @@ class BackgroundMode : Service() {
destroyPlayer() destroyPlayer()
// get the intent arguments // get the intent arguments
val videoId = intent?.getStringExtra("videoId")!! videoId = intent?.getStringExtra("videoId")!!
val position = intent.getLongExtra("position", 0L) val position = intent.getLongExtra("position", 0L)
// play the audio in the background // play the audio in the background
@ -121,6 +142,8 @@ class BackgroundMode : Service() {
// seek to the previous position if available // seek to the previous position if available
if (seekToPosition != 0L) player?.seekTo(seekToPosition) if (seekToPosition != 0L) player?.seekTo(seekToPosition)
fetchSponsorBlockSegments()
} }
} }
@ -175,6 +198,8 @@ class BackgroundMode : Service() {
destroyPlayer() destroyPlayer()
// play new video on background // play new video on background
this.videoId = videoId
this.segmentData = null
playAudio(videoId) playAudio(videoId)
} }
} }
@ -222,6 +247,43 @@ class BackgroundMode : Service() {
mediaSessionConnector.setPlayer(player) mediaSessionConnector.setPlayer(player)
} }
/**
* fetch the segments for SponsorBlock
*/
private fun fetchSponsorBlockSegments() {
CoroutineScope(Dispatchers.IO).launch {
kotlin.runCatching {
val categories = PlayerHelper.getSponsorBlockCategories()
if (categories.size > 0) {
segmentData =
RetrofitInstance.api.getSegments(
videoId,
ObjectMapper().writeValueAsString(categories)
)
checkForSegments()
}
}
}
}
/**
* check for SponsorBlock segments
*/
private fun checkForSegments() {
Handler(Looper.getMainLooper()).postDelayed(this::checkForSegments, 100)
if (segmentData == null || segmentData!!.segments.isEmpty()) return
segmentData!!.segments.forEach { segment: 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) {
player?.seekTo(segmentEnd)
}
}
}
private fun destroyPlayer() { private fun destroyPlayer() {
// clear old player and its notification // clear old player and its notification
playerNotification = null playerNotification = null

View File

@ -68,4 +68,68 @@ object PlayerHelper {
CaptionStyleCompat.createFromCaptionStyle(captioningManager.userStyle) CaptionStyleCompat.createFromCaptionStyle(captioningManager.userStyle)
} }
} }
/**
* get the categories for sponsorBlock
*/
fun getSponsorBlockCategories(): ArrayList<String> {
val categories: ArrayList<String> = arrayListOf()
if (PreferenceHelper.getBoolean(
"intro_category_key",
false
)
) {
categories.add("intro")
}
if (PreferenceHelper.getBoolean(
"selfpromo_category_key",
false
)
) {
categories.add("selfpromo")
}
if (PreferenceHelper.getBoolean(
"interaction_category_key",
false
)
) {
categories.add("interaction")
}
if (PreferenceHelper.getBoolean(
"sponsors_category_key",
true
)
) {
categories.add("sponsor")
}
if (PreferenceHelper.getBoolean(
"outro_category_key",
false
)
) {
categories.add("outro")
}
if (PreferenceHelper.getBoolean(
"filler_category_key",
false
)
) {
categories.add("filler")
}
if (PreferenceHelper.getBoolean(
"music_offtopic_category_key",
false
)
) {
categories.add("music_offtopic")
}
if (PreferenceHelper.getBoolean(
"preview_category_key",
false
)
) {
categories.add("preview")
}
return categories
}
} }