Added quality profiles

This commit is contained in:
Lag 2023-03-07 01:33:47 +01:00
parent 76545f55c3
commit a6fd25d2e1
19 changed files with 1080 additions and 79 deletions

View file

@ -39,6 +39,7 @@ import com.lagradost.cloudstream3.CommonActivity.playerEventListener
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
@ -108,8 +109,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
// get() = episodes.isNotEmpty() // get() = episodes.isNotEmpty()
// options for player // options for player
protected var currentPrefQuality =
Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell /**
* Default profile 1
* Decides how links should be sorted based on a priority system.
* This will be set in runtime based on settings.
**/
protected var currentQualityProfile = 1
// protected var currentPrefQuality =
// Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell
protected var fastForwardTime = 10000L protected var fastForwardTime = 10000L
protected var androidTVInterfaceOffSeekTime = 10000L; protected var androidTVInterfaceOffSeekTime = 10000L;
protected var androidTVInterfaceOnSeekTime = 30000L; protected var androidTVInterfaceOnSeekTime = 30000L;
@ -1221,10 +1229,16 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
.toLong() * 1000L .toLong() * 1000L
androidTVInterfaceOffSeekTime = androidTVInterfaceOffSeekTime =
settingsManager.getInt(ctx.getString(R.string.android_tv_interface_off_seek_key), 10) settingsManager.getInt(
ctx.getString(R.string.android_tv_interface_off_seek_key),
10
)
.toLong() * 1000L .toLong() * 1000L
androidTVInterfaceOnSeekTime = androidTVInterfaceOnSeekTime =
settingsManager.getInt(ctx.getString(R.string.android_tv_interface_on_seek_key), 10) settingsManager.getInt(
ctx.getString(R.string.android_tv_interface_on_seek_key),
10
)
.toLong() * 1000L .toLong() * 1000L
navigationBarHeight = ctx.getNavigationBarHeight() navigationBarHeight = ctx.getNavigationBarHeight()
@ -1257,10 +1271,20 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
ctx.getString(R.string.double_tap_pause_enabled_key), ctx.getString(R.string.double_tap_pause_enabled_key),
false false
) )
currentPrefQuality = settingsManager.getInt(
ctx.getString(if (ctx.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key), val profiles = QualityDataHelper.getProfiles()
currentPrefQuality val type = if (ctx.isUsingMobileData())
) QualityDataHelper.QualityProfileType.Data
else QualityDataHelper.QualityProfileType.WiFi
currentQualityProfile =
profiles.firstOrNull { it.type == type }?.id ?: profiles.firstOrNull()?.id
?: currentQualityProfile
// currentPrefQuality = settingsManager.getInt(
// ctx.getString(if (ctx.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key),
// currentPrefQuality
// )
// useSystemBrightness = // useSystemBrightness =
// settingsManager.getBoolean(ctx.getString(R.string.use_system_brightness_key), false) // settingsManager.getBoolean(ctx.getString(R.string.use_system_brightness_key), false)
} }

View file

@ -31,6 +31,10 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subtitl
import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioTrackLanguage import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioTrackLanguage
import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForcedEncoding import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForcedEncoding
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
import com.lagradost.cloudstream3.ui.player.source_priority.SourcePriority
import com.lagradost.cloudstream3.ui.player.source_priority.SourcePriorityDialog
import com.lagradost.cloudstream3.ui.result.* import com.lagradost.cloudstream3.ui.result.*
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1 import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
@ -57,6 +61,7 @@ import kotlinx.coroutines.Job
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap
import kotlin.math.abs
class GeneratorPlayer : FullScreenPlayer() { class GeneratorPlayer : FullScreenPlayer() {
companion object { companion object {
@ -188,17 +193,31 @@ class GeneratorPlayer : FullScreenPlayer() {
player.addTimeStamps(listOf()) // clear stamps player.addTimeStamps(listOf()) // clear stamps
} }
private fun sortLinks(useQualitySettings: Boolean = true): List<Pair<ExtractorLink?, ExtractorUri?>> { private fun closestQuality(target: Int?): Qualities {
return currentLinks.sortedBy { if (target == null) return Qualities.Unknown
val (linkData, _) = it return Qualities.values().minBy { abs(it.value - target) }
var quality = linkData?.quality ?: Qualities.Unknown.value
// we set all qualities above current max as reverse
if (useQualitySettings && quality > currentPrefQuality) {
quality = currentPrefQuality - quality - 1
} }
private fun getLinkPriority(
qualityProfile: Int,
link: Pair<ExtractorLink?, ExtractorUri?>
): Int {
val (linkData, _) = link
val qualityPriority = QualityDataHelper.getQualityPriority(
qualityProfile,
closestQuality(linkData?.quality)
)
val sourcePriority =
QualityDataHelper.getSourcePriority(qualityProfile, linkData?.name)
// negative because we want to sort highest quality first // negative because we want to sort highest quality first
-(quality) return qualityPriority + sourcePriority
}
private fun sortLinks(qualityProfile: Int): List<Pair<ExtractorLink?, ExtractorUri?>> {
return currentLinks.sortedBy {
-getLinkPriority(qualityProfile, it)
} }
} }
@ -584,10 +603,13 @@ class GeneratorPlayer : FullScreenPlayer() {
var sourceIndex = 0 var sourceIndex = 0
var startSource = 0 var startSource = 0
var sortedUrls = emptyList<Pair<ExtractorLink?, ExtractorUri?>>()
val sortedUrls = sortLinks(useQualitySettings = false) fun refreshLinks(qualityProfile: Int) {
sortedUrls = sortLinks(qualityProfile)
if (sortedUrls.isEmpty()) { if (sortedUrls.isEmpty()) {
sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone = true sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone =
true
} else { } else {
startSource = sortedUrls.indexOf(currentSelectedLink) startSource = sortedUrls.indexOf(currentSelectedLink)
sourceIndex = startSource sourceIndex = startSource
@ -610,6 +632,9 @@ class GeneratorPlayer : FullScreenPlayer() {
providerList.setItemChecked(which, true) providerList.setItemChecked(which, true)
} }
} }
}
refreshLinks(currentQualityProfile)
sourceDialog.setOnDismissListener { sourceDialog.setOnDismissListener {
if (shouldDismiss) dismiss() if (shouldDismiss) dismiss()
@ -650,6 +675,29 @@ class GeneratorPlayer : FullScreenPlayer() {
sourceDialog.dismissSafe(activity) sourceDialog.dismissSafe(activity)
} }
fun setProfileName(profile: Int) {
sourceDialog.source_settings_btt.setText(
QualityDataHelper.getProfileName(
profile
)
)
}
setProfileName(currentQualityProfile)
sourceDialog.source_settings_btt.setOnClickListener {
val activity = activity ?: return@setOnClickListener
QualityProfileDialog(
activity,
R.style.AlertDialogCustomBlack,
currentLinks.mapNotNull { it.first },
currentQualityProfile
) { profile ->
currentQualityProfile = profile.id
setProfileName(profile.id)
refreshLinks(profile.id)
}.show()
}
sourceDialog.subtitles_encoding_format?.apply { sourceDialog.subtitles_encoding_format?.apply {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
@ -847,7 +895,7 @@ class GeneratorPlayer : FullScreenPlayer() {
private fun startPlayer() { private fun startPlayer() {
if (isActive) return // we don't want double load when you skip loading if (isActive) return // we don't want double load when you skip loading
val links = sortLinks() val links = sortLinks(currentQualityProfile)
if (links.isEmpty()) { if (links.isEmpty()) {
noLinksFound() noLinksFound()
return return
@ -868,12 +916,12 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
override fun hasNextMirror(): Boolean { override fun hasNextMirror(): Boolean {
val links = sortLinks() val links = sortLinks(currentQualityProfile)
return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size
} }
override fun nextMirror() { override fun nextMirror() {
val links = sortLinks() val links = sortLinks(currentQualityProfile)
if (links.isEmpty()) { if (links.isEmpty()) {
noLinksFound() noLinksFound()
return return
@ -1314,6 +1362,15 @@ class GeneratorPlayer : FullScreenPlayer() {
val turnVisible = it.isNotEmpty() val turnVisible = it.isNotEmpty()
val wasGone = overlay_loading_skip_button?.isGone == true val wasGone = overlay_loading_skip_button?.isGone == true
overlay_loading_skip_button?.isVisible = turnVisible overlay_loading_skip_button?.isVisible = turnVisible
normalSafeApiCall {
currentLinks.lastOrNull()?.let { last ->
if (getLinkPriority(currentQualityProfile, last) >= QualityDataHelper.AUTO_SKIP_PRIORITY) {
startPlayer()
}
}
}
if (turnVisible && wasGone) { if (turnVisible && wasGone) {
overlay_loading_skip_button?.requestFocus() overlay_loading_skip_button?.requestFocus()
} }

View file

@ -0,0 +1,60 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.AppUtils
import kotlinx.android.synthetic.main.player_prioritize_item.view.*
data class SourcePriority<T>(
val data: T,
val name: String,
var priority: Int
)
class PriorityAdapter<T>(override val items: MutableList<SourcePriority<T>>) :
AppUtils.DiffAdapter<SourcePriority<T>>(items) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return PriorityViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.player_prioritize_item, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is PriorityViewHolder -> holder.bind(items[position])
}
}
class PriorityViewHolder(
itemView: View,
) : RecyclerView.ViewHolder(itemView) {
fun <T> bind(item: SourcePriority<T>) {
val plusButton: ImageView = itemView.add_button
val subtractButton: ImageView = itemView.subtract_button
val priorityText: TextView = itemView.priority_text
val priorityNumber: TextView = itemView.priority_number
priorityText.text = item.name
fun updatePriority() {
priorityNumber.text = item.priority.toString()
}
updatePriority()
plusButton.setOnClickListener {
// If someone clicks til the integer limit then they deserve to crash.
item.priority++
updatePriority()
}
subtractButton.setOnClickListener {
item.priority--
updatePriority()
}
}
}
}

View file

@ -0,0 +1,81 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.content.res.ColorStateList
import android.graphics.Paint
import android.graphics.Typeface
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.AppUtils
import kotlinx.android.synthetic.main.player_quality_profile_item.view.*
class ProfilesAdapter(
override val items: MutableList<QualityDataHelper.QualityProfile>,
val usedProfile: Int,
val clickCallback: (oldIndex: Int?, newIndex: Int) -> Unit,
) :
AppUtils.DiffAdapter<QualityDataHelper.QualityProfile>(
items,
comparison = { first: QualityDataHelper.QualityProfile, second: QualityDataHelper.QualityProfile ->
first.id == second.id
}) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ProfilesViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.player_quality_profile_item, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ProfilesViewHolder -> holder.bind(items[position], position)
}
}
private var currentItem: Pair<Int, QualityDataHelper.QualityProfile>? = null
fun getCurrentProfile(): QualityDataHelper.QualityProfile? {
return currentItem?.second
}
inner class ProfilesViewHolder(
itemView: View,
) : RecyclerView.ViewHolder(itemView) {
fun bind(item: QualityDataHelper.QualityProfile, index: Int) {
val priorityText: TextView = itemView.profile_text
val wifiText: TextView = itemView.text_is_wifi
val dataText: TextView = itemView.text_is_mobile_data
val outline: View = itemView.outline
priorityText.text = item.name.asString(itemView.context)
dataText.isVisible = item.type == QualityDataHelper.QualityProfileType.Data
wifiText.isVisible = item.type == QualityDataHelper.QualityProfileType.WiFi
fun setCurrentItem() {
val prevIndex = currentItem?.first
currentItem = index to item
clickCallback.invoke(prevIndex, index)
}
outline.isVisible = currentItem?.second?.id == item.id
val textStyle =
if (item.id == usedProfile) {
Typeface.BOLD
} else {
Typeface.NORMAL
}
priorityText.setTypeface(null, textStyle)
itemView.setOnClickListener {
setCurrentItem()
}
}
}
}

View file

@ -0,0 +1,159 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.content.Context
import androidx.annotation.StringRes
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.debugAssert
import com.lagradost.cloudstream3.ui.result.UiText
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
import com.lagradost.cloudstream3.utils.Qualities
object QualityDataHelper {
private const val VIDEO_SOURCE_PRIORITY = "video_source_priority"
private const val VIDEO_PROFILE_NAME = "video_profile_name"
private const val VIDEO_QUALITY_PRIORITY = "video_quality_priority"
private const val VIDEO_PROFILE_TYPE = "video_profile_type"
private const val DEFAULT_SOURCE_PRIORITY = 1
/**
* Automatically skip loading links once this priority is reached
**/
const val AUTO_SKIP_PRIORITY = 10
/**
* Must be higher than amount of QualityProfileTypes
**/
private const val PROFILE_COUNT = 10
/**
* Unique guarantees that there will always be one of this type in the profile list.
**/
enum class QualityProfileType(@StringRes val stringRes: Int, val unique: Boolean) {
None(R.string.none, false),
WiFi(R.string.wifi, true),
Data(R.string.mobile_data, true)
}
data class QualityProfile(
val name: UiText,
val id: Int,
val type: QualityProfileType
)
fun getSourcePriority(profile: Int, name: String?): Int {
if (name == null) return DEFAULT_SOURCE_PRIORITY
return getKey(
"$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile",
name,
DEFAULT_SOURCE_PRIORITY
) ?: DEFAULT_SOURCE_PRIORITY
}
fun setSourcePriority(profile: Int, name: String, priority: Int) {
setKey("$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile", name, priority)
}
fun setProfileName(profile: Int, name: String?) {
val path = "$currentAccount/$VIDEO_PROFILE_NAME/$profile"
if (name == null) {
removeKey(path)
} else {
setKey(path, name.trim())
}
}
fun getProfileName(profile: Int): UiText {
return getKey<String>("$currentAccount/$VIDEO_PROFILE_NAME/$profile")?.let { txt(it) }
?: txt(R.string.profile_number, profile)
}
fun getQualityPriority(profile: Int, quality: Qualities): Int {
return getKey(
"$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile",
quality.value.toString(),
quality.defaultPriority
) ?: quality.defaultPriority
}
fun setQualityPriority(profile: Int, quality: Qualities, priority: Int) {
setKey(
"$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile",
quality.value.toString(),
priority
)
}
fun getQualityProfileType(profile: Int): QualityProfileType {
return getKey("$currentAccount/$VIDEO_PROFILE_TYPE/$profile") ?: QualityProfileType.None
}
fun setQualityProfileType(profile: Int, type: QualityProfileType?) {
val path = "$currentAccount/$VIDEO_PROFILE_TYPE/$profile"
if (type == QualityProfileType.None) {
removeKey(path)
} else {
setKey(path, type)
}
}
/**
* Gets all quality profiles, always includes one profile with WiFi and Data
* Must under all circumstances at least return one profile
**/
fun getProfiles(): List<QualityProfile> {
val availableTypes = QualityProfileType.values().toMutableList()
val profiles = (1..PROFILE_COUNT).map { profileNumber ->
// Get the real type
val type = getQualityProfileType(profileNumber)
// This makes it impossible to get more than one of each type
// Duplicates will be turned to None
val uniqueType = if (type.unique && !availableTypes.remove(type)) {
QualityProfileType.None
} else {
type
}
QualityProfile(
getProfileName(profileNumber),
profileNumber,
uniqueType
)
}.toMutableList()
/**
* If no profile of this type exists: insert it on the earliest profile with None type
**/
fun insertType(
list: MutableList<QualityProfile>,
type: QualityProfileType
) {
if (list.any { it.type == type }) return
val index =
list.indexOfFirst { it.type == QualityProfileType.None }
list.getOrNull(index)?.copy(type = type)
?.let { fixed ->
list.set(index, fixed)
}
}
QualityProfileType.values().forEach {
if (it.unique) insertType(profiles, it)
}
debugAssert({
!QualityProfileType.values().all { type ->
!type.unique || profiles.any { it.type == type }
}
}, { "All unique quality types do not exist" })
debugAssert({
profiles.isEmpty()
}, { "No profiles!" })
return profiles
}
}

View file

@ -0,0 +1,106 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.app.Dialog
import android.view.View
import android.widget.TextView
import androidx.annotation.StyleRes
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfileName
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfiles
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import kotlinx.android.synthetic.main.player_quality_profile_dialog.*
class QualityProfileDialog(
val activity: FragmentActivity,
@StyleRes val themeRes: Int,
private val links: List<ExtractorLink>,
private val usedProfile: Int,
private val profileSelectionCallback: (QualityDataHelper.QualityProfile) -> Unit
) : Dialog(activity, themeRes) {
override fun show() {
setContentView(R.layout.player_quality_profile_dialog)
val profilesRecyclerView: RecyclerView = profiles_recyclerview
val useBtt: View = use_btt
val editBtt: View = edit_btt
val cancelBtt: View = cancel_btt
val defaultBtt: View = set_default_btt
val currentProfileText: TextView = currently_selected_profile_text
val selectedItemActionsHolder: View = selected_item_holder
fun getCurrentProfile(): QualityDataHelper.QualityProfile? {
return (profilesRecyclerView.adapter as? ProfilesAdapter)?.getCurrentProfile()
}
fun refreshProfiles() {
currentProfileText.text = getProfileName(usedProfile).asString(context)
(profilesRecyclerView.adapter as? ProfilesAdapter)?.updateList(getProfiles())
}
profilesRecyclerView.adapter = ProfilesAdapter(
mutableListOf(),
usedProfile,
) { oldIndex: Int?, newIndex: Int ->
profilesRecyclerView.adapter?.notifyItemChanged(newIndex)
selectedItemActionsHolder.alpha = 1f
if (oldIndex != null) {
profilesRecyclerView.adapter?.notifyItemChanged(oldIndex)
}
}
refreshProfiles()
editBtt.setOnClickListener {
getCurrentProfile()?.let { profile ->
SourcePriorityDialog(context, themeRes, links, profile) {
refreshProfiles()
}.show()
}
}
defaultBtt.setOnClickListener {
val currentProfile = getCurrentProfile() ?: return@setOnClickListener
val choices = QualityDataHelper.QualityProfileType.values()
.filter { it != QualityDataHelper.QualityProfileType.None }
val choiceNames = choices.map { txt(it.stringRes).asString(context) }
activity.showBottomDialog(
choiceNames,
choices.indexOf(currentProfile.type),
txt(R.string.set_default).asString(context),
false,
{},
{ index ->
val pickedChoice = choices.getOrNull(index) ?: return@showBottomDialog
// Remove previous picks
if (pickedChoice.unique) {
getProfiles().filter { it.type == pickedChoice }.forEach {
QualityDataHelper.setQualityProfileType(it.id, null)
}
}
QualityDataHelper.setQualityProfileType(currentProfile.id, pickedChoice)
refreshProfiles()
})
}
cancelBtt.setOnClickListener {
this.dismissSafe()
}
useBtt.setOnClickListener {
getCurrentProfile()?.let {
profileSelectionCallback.invoke(it)
this.dismissSafe()
}
}
super.show()
}
}

View file

@ -0,0 +1,105 @@
package com.lagradost.cloudstream3.ui.player.source_priority
import android.app.Dialog
import android.content.Context
import android.view.View
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.StyleRes
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.work.impl.constraints.controllers.ConstraintController
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import kotlinx.android.synthetic.main.player_select_source_priority.*
class SourcePriorityDialog(
ctx: Context,
@StyleRes themeRes: Int,
val links: List<ExtractorLink>,
private val profile: QualityDataHelper.QualityProfile,
/**
* Notify that the profile overview should be updated, for example if the name has been updated
* Should not be called excessively.
**/
private val updatedCallback: () -> Unit
) : Dialog(ctx, themeRes) {
override fun show() {
setContentView(R.layout.player_select_source_priority)
val sourcesRecyclerView: RecyclerView = sort_sources
val qualitiesRecyclerView: RecyclerView = sort_qualities
val profileText: EditText = profile_text_editable
val saveBtt: View = save_btt
val exitBtt: View = close_btt
val helpBtt: View = help_btt
profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context))
profileText.hint = txt(R.string.profile_number, profile.id).asString(context)
sourcesRecyclerView.adapter = PriorityAdapter(
links.map { link ->
SourcePriority(
null,
link.source,
QualityDataHelper.getSourcePriority(profile.id, link.source)
)
}.distinctBy { it.name }.sortedBy { -it.priority }.toMutableList()
)
qualitiesRecyclerView.adapter = PriorityAdapter(
Qualities.values().mapNotNull {
SourcePriority(
it,
Qualities.getStringByIntFull(it.value).ifBlank { return@mapNotNull null },
QualityDataHelper.getQualityPriority(profile.id, it)
)
}.sortedBy { -it.priority }.toMutableList()
)
@Suppress("UNCHECKED_CAST") // We know the types
saveBtt.setOnClickListener {
val qualityAdapter = qualitiesRecyclerView.adapter as? PriorityAdapter<Qualities>
val sourcesAdapter = sourcesRecyclerView.adapter as? PriorityAdapter<Nothing>
val qualities = qualityAdapter?.items ?: emptyList()
val sources = sourcesAdapter?.items ?: emptyList()
qualities.forEach {
val data = it.data as? Qualities ?: return@forEach
QualityDataHelper.setQualityPriority(profile.id, data, it.priority)
}
sources.forEach {
QualityDataHelper.setSourcePriority(profile.id, it.name, it.priority)
}
qualityAdapter?.updateList(qualities.sortedBy { -it.priority })
sourcesAdapter?.updateList(sources.sortedBy { -it.priority })
val savedProfileName = profileText.text.toString()
if (savedProfileName.isBlank()) {
QualityDataHelper.setProfileName(profile.id, null)
} else {
QualityDataHelper.setProfileName(profile.id, savedProfileName)
}
updatedCallback.invoke()
}
exitBtt.setOnClickListener {
this.dismissSafe()
}
helpBtt.setOnClickListener {
AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
setMessage(R.string.quality_profile_help)
}.show()
}
super.show()
}
}

View file

@ -117,7 +117,7 @@ object DataStoreHelper {
/** /**
* A datastore wide account for future implementations of a multiple account system * A datastore wide account for future implementations of a multiple account system
**/ **/
private var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
fun getAllWatchStateIds(): List<Int>? { fun getAllWatchStateIds(): List<Int>? {
val folder = "$currentAccount/$RESULT_WATCH_STATE" val folder = "$currentAccount/$RESULT_WATCH_STATE"

View file

@ -97,16 +97,16 @@ data class ExtractorSubtitleLink(
*/ */
val schemaStripRegex = Regex("""^(https:|)//(www\.|)""") val schemaStripRegex = Regex("""^(https:|)//(www\.|)""")
enum class Qualities(var value: Int) { enum class Qualities(var value: Int, val defaultPriority: Int) {
Unknown(400), Unknown(400, 4),
P144(144), // 144p P144(144, 0), // 144p
P240(240), // 240p P240(240, 2), // 240p
P360(360), // 360p P360(360, 3), // 360p
P480(480), // 480p P480(480, 4), // 480p
P720(720), // 720p P720(720, 5), // 720p
P1080(1080), // 1080p P1080(1080, 6), // 1080p
P1440(1440), // 1440p P1440(1440, 7), // 1440p
P2160(2160); // 4k or 2160p P2160(2160, 8); // 4k or 2160p
companion object { companion object {
fun getStringByInt(qual: Int?): String { fun getStringByInt(qual: Int?): String {
@ -118,6 +118,14 @@ enum class Qualities(var value: Int) {
else -> "${qual}p" else -> "${qual}p"
} }
} }
fun getStringByIntFull(quality: Int): String {
return when (quality) {
0 -> "Auto"
Unknown.value -> "Unknown"
P2160.value -> "4K"
else -> "${quality}p"
}
}
} }
} }

View file

@ -250,17 +250,6 @@ object SingleSelectionHelper {
) )
} }
fun showBottomDialog(
items: List<String>,
selectedIndex: Int,
name: String,
showApply: Boolean,
dismissCallback: () -> Unit,
callback: (Int) -> Unit,
) {
}
/** Only for a low amount of items */ /** Only for a low amount of items */
fun Activity?.showBottomDialog( fun Activity?.showBottomDialog(
items: List<String>, items: List<String>,

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/white" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/>
</vector>

View file

@ -3,7 +3,7 @@
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24"
android:tint="?attr/colorControlNormal"> android:tint="?attr/white">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M19,13H5v-2h14v2z"/> android:pathData="M19,13H5v-2h14v2z"/>

View file

@ -0,0 +1,47 @@
<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:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/priority_text"
style="@style/NoCheckLabel"
android:layout_width="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/subtract_button"
android:paddingStart="25dp"
android:paddingEnd="25dp"
tools:text="hello" />
<ImageView
android:id="@+id/subtract_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/priority_number"
android:padding="10dp"
android:src="@drawable/baseline_remove_24" />
<TextView
android:id="@+id/priority_number"
style="@style/NoCheckLabel"
android:layout_width="wrap_content"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/add_button"
android:gravity="center"
android:minWidth="50dp"
android:textColor="?attr/textColor"
tools:text="1" />
<ImageView
android:id="@+id/add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:padding="10dp"
android:src="@drawable/ic_baseline_add_24" />
</RelativeLayout>

View file

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/profiles"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/currently_selected_profile_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:textColor="?attr/textColor"
android:textSize="15sp"
tools:text="Profile 1" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/profiles_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="4"
tools:listitem="@layout/player_quality_profile_item" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:animateLayoutChanges="true"
android:gravity="end|bottom"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/selected_item_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0.5"
android:layout_weight="1">
<com.google.android.material.button.MaterialButton
android:id="@+id/edit_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:text="@string/edit" />
<com.google.android.material.button.MaterialButton
android:id="@+id/set_default_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:text="@string/set_default" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end">
<com.google.android.material.button.MaterialButton
android:id="@+id/use_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:text="@string/use" />
</LinearLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/cancel_btt"
style="@style/BlackButton"
android:layout_width="wrap_content"
android:text="@string/sort_cancel" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_view"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginStart="10dp"
android:animateLayoutChanges="true"
android:backgroundTint="?attr/primaryGrayBackground"
app:cardCornerRadius="@dimen/rounded_image_radius">
<View
android:id="@+id/outline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/outline"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:layout_gravity="center"
android:id="@+id/profile_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:padding="10dp"
tools:text="Mobile data" />
<TextView
android:id="@+id/text_is_wifi"
style="@style/DubButton"
android:layout_gravity="end"
android:text="@string/wifi" />
<TextView
android:id="@+id/text_is_mobile_data"
style="@style/DubButton"
android:layout_gravity="end"
android:text="@string/mobile_data" />
</androidx.cardview.widget.CardView>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -20,20 +21,45 @@
android:layout_weight="50" android:layout_weight="50"
android:orientation="vertical"> android:orientation="vertical">
<TextView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:background="@drawable/outline_drawable_less"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingTop="10dp" android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/pick_source" android:text="@string/pick_source"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView
tools:text="Profile 1"
android:minWidth="140dp"
android:id="@+id/source_settings_btt"
android:layout_width="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:paddingHorizontal="10dp"
android:drawablePadding="10dp"
android:textColor="?attr/textColor"
android:textSize="15sp"
app:drawableEndCompat="@drawable/ic_outline_settings_24" />
</LinearLayout>
<ListView <ListView
android:id="@+id/sort_providers" android:id="@+id/sort_providers"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp"
android:baselineAligned="false"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/sort_sources_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/pick_source"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sort_sources"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:background="?attr/primaryBlackBackground"
android:listSelector="@drawable/outline_drawable_less"
android:nextFocusLeft="@id/sort_subtitles"
android:nextFocusRight="@id/apply_btt"
android:requiresFadingEdge="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:layout_height="100dp"
tools:listitem="@layout/player_prioritize_item" />
</LinearLayout>
<LinearLayout
android:id="@+id/sort_subtitles_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:orientation="vertical">
<!-- android:id="@+id/subs_settings" android:foreground="?android:attr/selectableItemBackgroundBorderless"
-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<LinearLayout
android:id="@+id/subtitles_click_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:background="@drawable/outline_drawable_less"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:layout_weight="1"
android:text="@string/qualities"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/help_btt"
android:padding="12dp"
android:layout_width="50dp"
android:src="@drawable/baseline_help_outline_24"
android:layout_height="50dp">
</ImageView>
</LinearLayout>
<ImageView
android:layout_width="25dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginTop="0dp"
android:layout_marginEnd="10dp"
android:contentDescription="@string/home_change_provider_img_des"
android:src="@drawable/ic_outline_settings_24"
android:visibility="gone" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sort_qualities"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:background="?attr/primaryBlackBackground"
android:listSelector="@drawable/outline_drawable_less"
android:nextFocusLeft="@id/sort_providers"
android:nextFocusRight="@id/cancel_btt"
android:requiresFadingEdge="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:layout_height="200dp"
tools:listfooter="@layout/sort_bottom_footer_add_choice"
tools:listitem="@layout/player_prioritize_item" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/apply_btt_holder"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:layout_marginTop="-60dp"
android:orientation="horizontal">
<EditText
android:id="@+id/profile_text_editable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:inputType="text"
android:maxLength="32"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold"
tools:text="Profile 1" />
<!-- <ImageView-->
<!-- android:layout_width="50dp"-->
<!-- android:layout_height="50dp"-->
<!-- android:padding="10dp"-->
<!-- android:layout_gravity="center"-->
<!-- android:src="@drawable/outline_edit_24" />-->
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
</Space>
<com.google.android.material.button.MaterialButton
android:id="@+id/save_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:text="@string/sort_save" />
<com.google.android.material.button.MaterialButton
android:id="@+id/close_btt"
style="@style/BlackButton"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:text="@string/sort_close" />
</LinearLayout>
</LinearLayout>

View file

@ -657,4 +657,19 @@
<string name="subscription_new">Subscribed to %s</string> <string name="subscription_new">Subscribed to %s</string>
<string name="subscription_deleted">Unsubscribed from %s</string> <string name="subscription_deleted">Unsubscribed from %s</string>
<string name="subscription_episode_released">Episode %d released!</string> <string name="subscription_episode_released">Episode %d released!</string>
<string name="profile_number">Profile %d</string>
<string name="wifi">Wi-Fi</string>
<string name="mobile_data">Mobile data</string>
<string name="set_default">Set default</string>
<string name="use">Use</string>
<string name="edit">Edit</string>
<string name="profiles">Profiles</string>
<string name="help">Help</string>
<string name="quality_profile_help">
Here you can change how the sources are ordered. If a video has a higher priority it will appear higher in the source selection.
The sum of the source priority and the quality priority is the video priority.
\n\nNOTE: If the sum is 10 or more the player will automatically skip loading when that link is loaded!
</string>
<string name="qualities">Qualities</string>
</resources> </resources>

View file

@ -11,14 +11,14 @@
<PreferenceCategory <PreferenceCategory
android:title="@string/pref_category_defaults"> android:title="@string/pref_category_defaults">
<Preference <!-- <Preference-->
android:icon="@drawable/ic_baseline_hd_24" <!-- android:icon="@drawable/ic_baseline_hd_24"-->
android:key="@string/quality_pref_key" <!-- android:key="@string/quality_pref_key"-->
android:title="@string/watch_quality_pref" /> <!-- android:title="@string/watch_quality_pref" />-->
<Preference <!-- <Preference-->
android:icon="@drawable/ic_baseline_hd_24" <!-- android:icon="@drawable/ic_baseline_hd_24"-->
android:key="@string/quality_pref_mobile_data_key" <!-- android:key="@string/quality_pref_mobile_data_key"-->
android:title="@string/watch_quality_pref_data" /> <!-- android:title="@string/watch_quality_pref_data" />-->
<Preference <Preference
android:icon="@drawable/netflix_play" android:icon="@drawable/netflix_play"