Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ data class VideoState(
*
* sortedBy is not exactly expensive, but each hasNextMirror does it again, so this alleviates unnecessary recomputation
* */
private val sortedLinks: ConcurrentHashMap<Int, List<DisplayLink>> = ConcurrentHashMap()
private val sortedLinks: ConcurrentHashMap<String, List<DisplayLink>> = ConcurrentHashMap()

/**
* The cache is guaranteed to be up to date link-wise due to the immutable links.
Expand Down Expand Up @@ -95,24 +95,44 @@ data class VideoState(
// It is by all standards, idempotent and by extension also pure as it has no "visible" side effect
/** Returns .links in the sorted order according to the qualityProfile.
* Use .links if order is not needed */
@Contract(pure = true)
fun sortLinks(qualityProfile: Int): List<DisplayLink> {
sortedLinks[qualityProfile]?.let {
return it
}

val hideNegativeSources =
QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideNegativeSources)
val hideErrorSources =
QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideErrorSources)
// Read regex pattern and priority boost from profile. Priority 0 = disabled.
val sourceRegex =
QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.SourceRegex)
val regexPriority =
QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.SourceRegexPriority)

// Parse comma-separated patterns into case-insensitive regex. Invalid = null (safe fallback).
val regex = sourceRegex.trim().takeIf { it.isNotEmpty() }?.let { pattern ->
val joined = pattern.split(",").map { it.trim() }.filter { it.isNotEmpty() }.joinToString("|")
runCatching { Regex(joined, RegexOption.IGNORE_CASE) }.getOrNull()
}

// Cache key includes regex + priority to avoid stale results.
val cacheKey = if (regex != null) "$qualityProfile|${sourceRegex.trim()}|$regexPriority" else qualityProfile.toString()
sortedLinks[cacheKey]?.let { return it }

return links.map { link ->
// negative because we want to sort highest quality first
val displayLinks = links.map { link ->
link.toDisplayLink(qualityProfile, hideNegativeSources, hideErrorSources)
}.sortedBy {
// negative because we want to sort highest quality first
-it.priority
}.also { value -> sortedLinks[qualityProfile] = value }
}

// Boost matching sources by regexPriority*1000 so they sort first.
// Within matching sources, quality priority still determines order.
// Priority 0 = disabled (regex ignored). No regex = original sort.
val sorted = if (regex != null && regexPriority > 0) {
displayLinks.sortedByDescending { link ->
val isMatch = link.shouldUseLink && regex.containsMatchIn(link.link?.first?.name ?: "")
link.priority + if (isMatch) regexPriority * 1000 else 0
}
} else {
displayLinks.sortedBy { -it.priority }
}

return sorted.also { value -> sortedLinks[cacheKey] = value }
}

@Contract(pure = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,6 @@ object QualityDataHelper {
sealed class ProfileSettings<T>(val key: String, val defaultValue: T) {
object HideErrorSources : ProfileSettings<Boolean>("hide_error_sources", false)
object HideNegativeSources : ProfileSettings<Boolean>("hide_negative_sources", false)
object SourceRegex : ProfileSettings<String>("source_regex", "")
object SourceRegexPriority : ProfileSettings<Int>("source_regex_priority", 0)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import com.lagradost.cloudstream3.utils.txt
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView

class SourcePriorityDialog(
val ctx: Context,
Expand All @@ -29,6 +32,57 @@ class SourcePriorityDialog(
setContentView(binding.root)
fixSystemBarsPadding(binding.root)

val regexInput = EditText(ctx).apply {
hint = ctx.getString(R.string.source_regex_hint)
setText(QualityDataHelper.getProfileSetting(profile.id, ProfileSettings.SourceRegex))
isSingleLine = true
setTextColor(ctx.getColor(android.R.color.white))
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}
val regexPriority = QualityDataHelper.getProfileSetting(profile.id, ProfileSettings.SourceRegexPriority)
val priorityText = TextView(ctx).apply {
text = regexPriority.toString()
gravity = android.view.Gravity.CENTER
minWidth = (40 * ctx.resources.displayMetrics.density).toInt()
setTextColor(ctx.getColor(android.R.color.white))
}
val minusBtn = android.widget.ImageView(ctx).apply {
setImageResource(com.lagradost.cloudstream3.R.drawable.baseline_remove_24)
background = ctx.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackgroundBorderless)).getDrawable(0)
setPadding(8, 8, 8, 8)
isFocusable = true
setOnClickListener {
val v = (priorityText.text.toString().toIntOrNull() ?: 0) - 1
priorityText.text = v.coerceAtLeast(0).toString()
}
}
val plusBtn = android.widget.ImageView(ctx).apply {
setImageResource(com.lagradost.cloudstream3.R.drawable.ic_baseline_add_24)
background = ctx.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackgroundBorderless)).getDrawable(0)
setPadding(8, 8, 8, 8)
isFocusable = true
setOnClickListener {
val v = (priorityText.text.toString().toIntOrNull() ?: 0) + 1
priorityText.text = v.toString()
}
}
val regexRow = LinearLayout(ctx).apply {
orientation = LinearLayout.HORIZONTAL
gravity = android.view.Gravity.CENTER_VERTICAL
setPadding(
(12 * ctx.resources.displayMetrics.density).toInt(),
(8 * ctx.resources.displayMetrics.density).toInt(),
(12 * ctx.resources.displayMetrics.density).toInt(),
(4 * ctx.resources.displayMetrics.density).toInt()
)
addView(regexInput)
addView(minusBtn)
addView(priorityText)
addView(plusBtn)
}
// Insert regex row into sort_sources_holder, after the "Sources" title
binding.sortSourcesHolder.addView(regexRow, 1)

binding.apply {
profileTextEditable.setText(
QualityDataHelper.getProfileName(profile.id).asString(context)
Expand Down Expand Up @@ -75,6 +129,17 @@ class SourcePriorityDialog(
QualityDataHelper.setSourcePriority(profile.id, it.name, it.priority)
}

val pattern = regexInput.text.toString().trim()
if (pattern.isNotEmpty()) {
val joined = pattern.split(",").map { it.trim() }.filter { it.isNotEmpty() }.joinToString("|")
if (runCatching { Regex(joined, RegexOption.IGNORE_CASE) }.isFailure) {
android.widget.Toast.makeText(ctx, ctx.getString(R.string.source_regex_invalid), android.widget.Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
}
QualityDataHelper.setProfileSetting(profile.id, ProfileSettings.SourceRegex, pattern)
QualityDataHelper.setProfileSetting(profile.id, ProfileSettings.SourceRegexPriority, priorityText.text.toString().toIntOrNull() ?: 0)

qualityAdapter?.submitList(qualities.sortedBy { -it.priority })
sourcesAdapter?.submitList(sources.sortedBy { -it.priority })

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />

<com.google.android.material.button.MaterialButton
android:id="@+id/settings_btt"
style="@style/WhiteButton"
Expand Down
1 change: 0 additions & 1 deletion app/src/main/res/layout/player_select_source_priority.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@
<!-- android:padding="10dp"-->
<!-- android:layout_gravity="center"-->
<!-- android:src="@drawable/outline_edit_24" />-->

<com.google.android.material.button.MaterialButton
android:id="@+id/settings_btt"
style="@style/WhiteButton"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
<string name="title_downloads">Downloads</string>
<string name="download_queue">Download queue</string>
<string name="title_settings">Settings</string>
<string name="source_regex_hint">Regex Comma-separated</string>
<string name="source_regex_invalid">Invalid regex</string>
<string name="search_hint">Search…</string>
<string name="search_hint_site" formatted="true">Search %s…</string>
<string name="speech_recognition_unavailable">Speech recognition is not available</string>
Expand Down