Skip to content

Commit

Permalink
Options to choose which categories are skipped automatically or manua…
Browse files Browse the repository at this point in the history
…lly (#4021)

Co-authored-by: general-a <404aaronm@gmail.com>
Co-authored-by: Bnyro <bnyro@tutanota.com>
  • Loading branch information
3 people authored Jun 19, 2023
1 parent 0582c94 commit 0e6ef50
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ object PreferenceKeys {
const val SKIP_BUTTONS = "skip_buttons"
const val PICTURE_IN_PICTURE = "picture_in_picture"
const val PLAYER_RESIZE_MODE = "player_resize_mode"
const val SB_SKIP_MANUALLY = "sb_skip_manually_key"
const val SB_SHOW_MARKERS = "sb_show_markers"
const val ALTERNATIVE_PLAYER_LAYOUT = "alternative_player_layout"
const val USE_HLS_OVER_DASH = "use_hls"
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/github/libretube/enums/SbSkipOptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.github.libretube.enums

enum class SbSkipOptions {
OFF,
MANUAL,
AUTOMATIC
}
88 changes: 21 additions & 67 deletions app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,27 @@ import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.LoadControl
import androidx.media3.ui.CaptionStyleCompat
import com.github.libretube.R
import com.github.libretube.api.obj.PipedStream
import com.github.libretube.api.obj.Segment
import com.github.libretube.api.obj.Streams
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.enums.AudioQuality
import com.github.libretube.enums.PlayerEvent
import com.github.libretube.enums.SbSkipOptions
import kotlin.math.absoluteValue
import kotlin.math.roundToInt

object PlayerHelper {
private const val ACTION_MEDIA_CONTROL = "media_control"
const val CONTROL_TYPE = "control_type"
private val SPONSOR_CATEGORIES: Array<String> =
arrayOf(
"intro",
"selfpromo",
"interaction",
"sponsor",
"outro",
"filler",
"music_offtopic",
"preview")

/**
* Create a base64 encoded DASH stream manifest
Expand Down Expand Up @@ -69,63 +78,14 @@ object PlayerHelper {
/**
* 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")
fun getSponsorBlockCategories(): MutableMap<String, SbSkipOptions> {
val categories: MutableMap<String, SbSkipOptions> = mutableMapOf()

for (cat in SPONSOR_CATEGORIES){
val state = PreferenceHelper.getString(cat + "_category_key", "off").uppercase()
if (SbSkipOptions.valueOf(state) != SbSkipOptions.OFF){
categories[cat] = SbSkipOptions.valueOf(state)
}
}
return categories
}
Expand Down Expand Up @@ -240,12 +200,6 @@ object PlayerHelper {
true,
)

val skipSegmentsManually: Boolean
get() = PreferenceHelper.getBoolean(
PreferenceKeys.SB_SKIP_MANUALLY,
false,
)

val autoPlayEnabled: Boolean
get() = PreferenceHelper.getBoolean(
PreferenceKeys.AUTO_PLAY,
Expand Down Expand Up @@ -476,7 +430,7 @@ object PlayerHelper {
fun ExoPlayer.checkForSegments(
context: Context,
segments: List<Segment>,
skipManually: Boolean = false,
sponsorBlockConfig: MutableMap<String, SbSkipOptions>,
): Long? {
for (segment in segments) {
val segmentStart = (segment.segment[0] * 1000f).toLong()
Expand All @@ -486,7 +440,7 @@ object PlayerHelper {
if ((duration - currentPosition).absoluteValue < 500) continue

if (currentPosition in segmentStart until segmentEnd) {
if (!skipManually) {
if (sponsorBlockConfig.get(segment.category) == SbSkipOptions.AUTOMATIC) {
if (sponsorBlockNotifications) {
runCatching {
Toast.makeText(context, R.string.segment_skipped, Toast.LENGTH_SHORT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.github.libretube.db.obj.WatchPosition
import com.github.libretube.extensions.parcelableExtra
import com.github.libretube.extensions.setMetadata
import com.github.libretube.extensions.toID
import com.github.libretube.enums.SbSkipOptions
import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.helpers.PlayerHelper.checkForSegments
import com.github.libretube.helpers.PlayerHelper.loadPlaybackParams
Expand Down Expand Up @@ -76,6 +77,7 @@ class OnlinePlayerService : LifecycleService() {
* SponsorBlock Segment data
*/
private var segments: List<Segment> = listOf()
private var sponsorBlockConfig: MutableMap<String, SbSkipOptions> = PlayerHelper.getSponsorBlockCategories()

/**
* [Notification] for the player
Expand Down Expand Up @@ -313,11 +315,10 @@ class OnlinePlayerService : LifecycleService() {
private fun fetchSponsorBlockSegments() {
lifecycleScope.launch(Dispatchers.IO) {
runCatching {
val categories = PlayerHelper.getSponsorBlockCategories()
if (categories.isEmpty()) return@runCatching
if (sponsorBlockConfig.isEmpty()) return@runCatching
segments = RetrofitInstance.api.getSegments(
videoId,
JsonHelper.json.encodeToString(categories),
JsonHelper.json.encodeToString(sponsorBlockConfig.keys),
).segments
checkForSegments()
}
Expand All @@ -330,7 +331,7 @@ class OnlinePlayerService : LifecycleService() {
private fun checkForSegments() {
handler.postDelayed(this::checkForSegments, 100)

player?.checkForSegments(this, segments)
player?.checkForSegments(this, segments, sponsorBlockConfig)
}

private fun updateQueue() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
*/
private var segments = listOf<Segment>()
private var sponsorBlockEnabled = PlayerHelper.sponsorBlockEnabled
private var sponsorBlockConfig = PlayerHelper.getSponsorBlockCategories()

private val handler = Handler(Looper.getMainLooper())
private val mainActivity get() = activity as MainActivity
Expand Down Expand Up @@ -663,16 +664,16 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {

if (segments.isEmpty()) return

exoPlayer.checkForSegments(requireContext(), segments, PlayerHelper.skipSegmentsManually)
exoPlayer.checkForSegments(requireContext(), segments, sponsorBlockConfig)
?.let { segmentEnd ->
binding.sbSkipBtn.visibility = View.VISIBLE
binding.sbSkipBtn.setOnClickListener {
exoPlayer.seekTo(segmentEnd)
binding.sbSkipBtn.visibility = View.GONE
}
return
}

if (PlayerHelper.skipSegmentsManually) binding.sbSkipBtn.visibility = View.GONE
}

private fun playVideo() {
Expand Down Expand Up @@ -773,12 +774,11 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
private fun fetchSponsorBlockSegments() {
lifecycleScope.launch(Dispatchers.IO) {
runCatching {
val categories = PlayerHelper.getSponsorBlockCategories()
if (categories.isEmpty()) return@runCatching
if (sponsorBlockConfig.isEmpty()) return@runCatching
segments =
RetrofitInstance.api.getSegments(
videoId,
JsonHelper.json.encodeToString(categories),
JsonHelper.json.encodeToString(sponsorBlockConfig.keys),
).segments
if (segments.isEmpty()) return@runCatching

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.github.libretube.ui.views
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.View
import android.widget.AdapterView
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.TextView
import com.github.libretube.R

class SbSpinnerPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs) {
private lateinit var adapter: ArrayAdapter<CharSequence>
private var selectedItem: CharSequence? = null

init {
layoutResource = R.layout.spinner_preference
}

override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val spinner: Spinner = holder.itemView.findViewById(R.id.spinner)

holder.itemView.findViewById<TextView>(android.R.id.title)?.text = super.getTitle()
holder.itemView.findViewById<TextView>(android.R.id.summary)?.text = super.getSummary()

// Set the spinner adapter
adapter = ArrayAdapter.createFromResource(
context,
R.array.sb_skip_options,
android.R.layout.simple_spinner_item
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter

// Set the initial selected item
selectedItem?.let { selectedItem ->
val position = getEntryValues().indexOf(selectedItem)
spinner.setSelection(position)
}

// Set a listener to handle item selection
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>,
view: View?,
position: Int,
id: Long
) {
persistString(getEntryValues()[position])
}

override fun onNothingSelected(parent: AdapterView<*>) = Unit
}
}

override fun onGetDefaultValue(ta: TypedArray, index: Int): Any {
// Get the default value from the XML attribute, if specified
return ta.getString(index).orEmpty()
}

override fun onSetInitialValue(defaultValue: Any?) {
// Set the initial selected item from the persisted value, if available
selectedItem = getPersistedString(defaultValue?.toString())
}

private fun getEntryValues() = context.resources.getStringArray(R.array.sb_skip_options_values)
}
62 changes: 62 additions & 0 deletions app/src/main/res/layout/spinner_preference.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp">

<LinearLayout
android:id="@+id/left_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="55dp"
android:orientation="vertical">

<TextView
android:id="@android:id/title"
style="?android:attr/preferenceStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:gravity="center"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="14sp"
android:textStyle="bold"
tools:text="Title" />

<TextView
android:id="@android:id/summary"
style="?android:attr/preferenceStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:lineSpacingExtra="4dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="14sp"
tools:text="Summary" />

</LinearLayout>

<Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@android:id/title"
android:layout_alignBottom="@android:id/title"
android:layout_alignParentEnd="true"
android:layout_marginTop="-2dp"
android:textAlignment="textEnd"
android:layout_marginEnd="-10dp"
android:paddingStart="0dp"
android:paddingEnd="35dp" />

</RelativeLayout>
12 changes: 12 additions & 0 deletions app/src/main/res/values/array.xml
Original file line number Diff line number Diff line change
Expand Up @@ -422,4 +422,16 @@
<item>playlists</item>
</string-array>

<string-array name="sb_skip_options">
<item>@string/off</item>
<item>@string/manual</item>
<item>@string/automatic</item>
</string-array>

<string-array name="sb_skip_options_values">
<item>off</item>
<item>manual</item>
<item>automatic</item>
</string-array>

</resources>
5 changes: 4 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
<string name="category_sponsor_description">Paid promotion, paid referrals and direct advertisements. Not for self-promotion or free genuine shoutouts to causes, creators, websites, and products.</string>
<string name="category_selfpromo">Unpaid/Self Promotion</string>
<string name="category_selfpromo_description">Similar to \"sponsor\" except for unpaid or self promotion. This includes sections about merchandise, donations, or info about who they collaborated with.</string>
<string name="category_interaction">Interaction reminder (like and subscribe)</string>
<string name="category_interaction">Interaction reminder</string>
<string name="category_interaction_description">When there is a short reminder to like, subscribe or follow in the middle of content. If long or about something specific, it should instead be self promotion.</string>
<string name="category_intro">Intermission/Intro Animation</string>
<string name="category_intro_description">An interval without actual content. Could be a pause, static frame, repeating animation. Should not be used for transitions containing info.</string>
Expand Down Expand Up @@ -419,6 +419,9 @@
<string name="change_playlist_description">Change playlist description</string>
<string name="playlist_description">Playlist description</string>
<string name="emptyPlaylistDescription">The playlist description can\'t be empty</string>
<string name="off">Off</string>
<string name="manual">Manual</string>
<string name="automatic">Automatic</string>

<!-- Backup & Restore Settings -->
<string name="import_subscriptions_from">Import subscriptions from</string>
Expand Down
Loading

0 comments on commit 0e6ef50

Please sign in to comment.