From b5566af401113d3882b38d2a7e041e4264e32eba Mon Sep 17 00:00:00 2001
From: LagradOst <46196380+Blatzar@users.noreply.github.com>
Date: Wed, 14 Jun 2023 17:30:39 +0000
Subject: [PATCH] Added quality profiles (#414)
* Added quality profiles
* Better quality selection
* Added profile bg and fixed some sources (#483)
Co-authored-by: Blatzar <>
---------
Co-authored-by: Lag <>
Co-authored-by: Osten <11805592+LagradOst@users.noreply.github.com>
---
.../cloudstream3/extractors/DoodExtractor.kt | 2 +-
.../cloudstream3/extractors/GuardareStream.kt | 2 +-
.../cloudstream3/extractors/Tantifilm.kt | 2 +-
.../ui/player/FullScreenPlayer.kt | 40 +++-
.../cloudstream3/ui/player/GeneratorPlayer.kt | 121 ++++++++----
.../player/source_priority/PriorityAdapter.kt | 60 ++++++
.../player/source_priority/ProfilesAdapter.kt | 116 ++++++++++++
.../source_priority/QualityDataHelper.kt | 159 ++++++++++++++++
.../source_priority/QualityProfileDialog.kt | 106 +++++++++++
.../source_priority/SourcePriorityDialog.kt | 105 ++++++++++
.../cloudstream3/ui/result/UiText.kt | 4 +-
.../cloudstream3/utils/DataStoreHelper.kt | 2 +-
.../cloudstream3/utils/ExtractorApi.kt | 28 ++-
.../utils/SingleSelectionHelper.kt | 11 --
.../lagradost/cloudstream3/utils/UIHelper.kt | 34 +++-
.../res/drawable/baseline_help_outline_24.xml | 5 +
.../main/res/drawable/baseline_remove_24.xml | 2 +-
app/src/main/res/drawable/profile_bg_blue.jpg | Bin 0 -> 21107 bytes
.../res/drawable/profile_bg_dark_blue.jpg | Bin 0 -> 42704 bytes
.../main/res/drawable/profile_bg_orange.jpg | Bin 0 -> 70427 bytes
app/src/main/res/drawable/profile_bg_pink.jpg | Bin 0 -> 117989 bytes
.../main/res/drawable/profile_bg_purple.jpg | Bin 0 -> 8564 bytes
app/src/main/res/drawable/profile_bg_red.jpg | Bin 0 -> 47464 bytes
app/src/main/res/drawable/profile_bg_teal.jpg | Bin 0 -> 128982 bytes
.../res/layout/player_prioritize_item.xml | 48 +++++
.../layout/player_quality_profile_dialog.xml | 105 ++++++++++
.../layout/player_quality_profile_item.xml | 66 +++++++
.../layout/player_select_source_and_subs.xml | 44 ++++-
.../layout/player_select_source_priority.xml | 179 ++++++++++++++++++
app/src/main/res/values/strings.xml | 21 +-
app/src/main/res/xml/settings_player.xml | 16 +-
31 files changed, 1188 insertions(+), 90 deletions(-)
create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/PriorityAdapter.kt
create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt
create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt
create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt
create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt
create mode 100644 app/src/main/res/drawable/baseline_help_outline_24.xml
create mode 100644 app/src/main/res/drawable/profile_bg_blue.jpg
create mode 100644 app/src/main/res/drawable/profile_bg_dark_blue.jpg
create mode 100644 app/src/main/res/drawable/profile_bg_orange.jpg
create mode 100644 app/src/main/res/drawable/profile_bg_pink.jpg
create mode 100644 app/src/main/res/drawable/profile_bg_purple.jpg
create mode 100644 app/src/main/res/drawable/profile_bg_red.jpg
create mode 100644 app/src/main/res/drawable/profile_bg_teal.jpg
create mode 100644 app/src/main/res/layout/player_prioritize_item.xml
create mode 100644 app/src/main/res/layout/player_quality_profile_dialog.xml
create mode 100644 app/src/main/res/layout/player_quality_profile_item.xml
create mode 100644 app/src/main/res/layout/player_select_source_priority.xml
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
index 0d94eb08..24495a40 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
@@ -58,7 +58,7 @@ open class DoodLaExtractor : ExtractorApi() {
val quality = Regex("\\d{3,4}p").find(response0.substringAfter("
").substringBefore(""))?.groupValues?.get(0)
return listOf(
ExtractorLink(
- trueUrl,
+ this.name,
this.name,
trueUrl,
mainUrl,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt
index 2adc00d5..3d046267 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt
@@ -58,7 +58,7 @@ open class GuardareStream : ExtractorApi() {
jsonVideoData.data.forEach {
callback.invoke(
ExtractorLink(
- it.file + ".${it.type}",
+ this.name,
this.name,
it.file + ".${it.type}",
mainUrl,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Tantifilm.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tantifilm.kt
index d721dea8..13aa48c6 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Tantifilm.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tantifilm.kt
@@ -30,7 +30,7 @@ open class Tantifilm : ExtractorApi() {
val jsonvideodata = parseJson(response)
return jsonvideodata.data.map {
ExtractorLink(
- it.file+".${it.type}",
+ this.name,
this.name,
it.file+".${it.type}",
mainUrl,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt
index 86e21fd6..9ff1c52d 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt
@@ -39,6 +39,7 @@ import com.lagradost.cloudstream3.CommonActivity.playerEventListener
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
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.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
@@ -108,8 +109,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
// get() = episodes.isNotEmpty()
// 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 androidTVInterfaceOffSeekTime = 10000L;
protected var androidTVInterfaceOnSeekTime = 30000L;
@@ -1221,10 +1229,16 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
.toLong() * 1000L
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
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
navigationBarHeight = ctx.getNavigationBarHeight()
@@ -1257,10 +1271,20 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
ctx.getString(R.string.double_tap_pause_enabled_key),
false
)
- currentPrefQuality = settingsManager.getInt(
- ctx.getString(if (ctx.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key),
- currentPrefQuality
- )
+
+ val profiles = QualityDataHelper.getProfiles()
+ 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 =
// settingsManager.getBoolean(ctx.getString(R.string.use_system_brightness_key), false)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
index 46f2bca9..e20a07fa 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
@@ -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.CustomDecoder.Companion.updateForcedEncoding
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.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
@@ -57,6 +61,7 @@ import kotlinx.coroutines.Job
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
+import kotlin.math.abs
class GeneratorPlayer : FullScreenPlayer() {
companion object {
@@ -188,17 +193,31 @@ class GeneratorPlayer : FullScreenPlayer() {
player.addTimeStamps(listOf()) // clear stamps
}
- private fun sortLinks(useQualitySettings: Boolean = true): List> {
- return currentLinks.sortedBy {
- val (linkData, _) = it
- var quality = linkData?.quality ?: Qualities.Unknown.value
+ private fun closestQuality(target: Int?): Qualities {
+ if (target == null) return Qualities.Unknown
+ return Qualities.values().minBy { abs(it.value - target) }
+ }
- // we set all qualities above current max as reverse
- if (useQualitySettings && quality > currentPrefQuality) {
- quality = currentPrefQuality - quality - 1
- }
- // negative because we want to sort highest quality first
- -(quality)
+ private fun getLinkPriority(
+ qualityProfile: Int,
+ link: Pair
+ ): 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
+ return qualityPriority + sourcePriority
+ }
+
+ private fun sortLinks(qualityProfile: Int): List> {
+ return currentLinks.sortedBy {
+ -getLinkPriority(qualityProfile, it)
}
}
@@ -584,33 +603,39 @@ class GeneratorPlayer : FullScreenPlayer() {
var sourceIndex = 0
var startSource = 0
+ var sortedUrls = emptyList>()
- val sortedUrls = sortLinks(useQualitySettings = false)
- if (sortedUrls.isEmpty()) {
- sourceDialog.findViewById(R.id.sort_sources_holder)?.isGone = true
- } else {
- startSource = sortedUrls.indexOf(currentSelectedLink)
- sourceIndex = startSource
+ fun refreshLinks(qualityProfile: Int) {
+ sortedUrls = sortLinks(qualityProfile)
+ if (sortedUrls.isEmpty()) {
+ sourceDialog.findViewById(R.id.sort_sources_holder)?.isGone =
+ true
+ } else {
+ startSource = sortedUrls.indexOf(currentSelectedLink)
+ sourceIndex = startSource
- val sourcesArrayAdapter =
- ArrayAdapter(ctx, R.layout.sort_bottom_single_choice)
+ val sourcesArrayAdapter =
+ ArrayAdapter(ctx, R.layout.sort_bottom_single_choice)
- sourcesArrayAdapter.addAll(sortedUrls.map { (link, uri) ->
- val name = link?.name ?: uri?.name ?: "NULL"
- "$name ${Qualities.getStringByInt(link?.quality)}"
- })
+ sourcesArrayAdapter.addAll(sortedUrls.map { (link, uri) ->
+ val name = link?.name ?: uri?.name ?: "NULL"
+ "$name ${Qualities.getStringByInt(link?.quality)}"
+ })
- providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
- providerList.adapter = sourcesArrayAdapter
- providerList.setSelection(sourceIndex)
- providerList.setItemChecked(sourceIndex, true)
+ providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
+ providerList.adapter = sourcesArrayAdapter
+ providerList.setSelection(sourceIndex)
+ providerList.setItemChecked(sourceIndex, true)
- providerList.setOnItemClickListener { _, _, which, _ ->
- sourceIndex = which
- providerList.setItemChecked(which, true)
+ providerList.setOnItemClickListener { _, _, which, _ ->
+ sourceIndex = which
+ providerList.setItemChecked(which, true)
+ }
}
}
+ refreshLinks(currentQualityProfile)
+
sourceDialog.setOnDismissListener {
if (shouldDismiss) dismiss()
selectSourceDialog = null
@@ -650,6 +675,29 @@ class GeneratorPlayer : FullScreenPlayer() {
sourceDialog.dismissSafe(activity)
}
+ fun setProfileName(profile: Int) {
+ sourceDialog.source_settings_btt.setText(
+ QualityDataHelper.getProfileName(
+ profile
+ )
+ )
+ }
+ setProfileName(currentQualityProfile)
+
+ sourceDialog.profiles_click_settings.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 {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
@@ -847,7 +895,7 @@ class GeneratorPlayer : FullScreenPlayer() {
private fun startPlayer() {
if (isActive) return // we don't want double load when you skip loading
- val links = sortLinks()
+ val links = sortLinks(currentQualityProfile)
if (links.isEmpty()) {
noLinksFound()
return
@@ -868,12 +916,12 @@ class GeneratorPlayer : FullScreenPlayer() {
}
override fun hasNextMirror(): Boolean {
- val links = sortLinks()
+ val links = sortLinks(currentQualityProfile)
return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size
}
override fun nextMirror() {
- val links = sortLinks()
+ val links = sortLinks(currentQualityProfile)
if (links.isEmpty()) {
noLinksFound()
return
@@ -1314,6 +1362,15 @@ class GeneratorPlayer : FullScreenPlayer() {
val turnVisible = it.isNotEmpty()
val wasGone = overlay_loading_skip_button?.isGone == true
overlay_loading_skip_button?.isVisible = turnVisible
+
+ normalSafeApiCall {
+ currentLinks.lastOrNull()?.let { last ->
+ if (getLinkPriority(currentQualityProfile, last) >= QualityDataHelper.AUTO_SKIP_PRIORITY) {
+ startPlayer()
+ }
+ }
+ }
+
if (turnVisible && wasGone) {
overlay_loading_skip_button?.requestFocus()
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/PriorityAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/PriorityAdapter.kt
new file mode 100644
index 00000000..8e0ce67c
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/PriorityAdapter.kt
@@ -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(
+ val data: T,
+ val name: String,
+ var priority: Int
+)
+
+class PriorityAdapter(override val items: MutableList>) :
+ AppUtils.DiffAdapter>(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 bind(item: SourcePriority) {
+ 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()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt
new file mode 100644
index 00000000..ff84c1f5
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt
@@ -0,0 +1,116 @@
+package com.lagradost.cloudstream3.ui.player.source_priority
+
+import android.content.res.ColorStateList
+import android.graphics.Typeface
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
+import com.lagradost.cloudstream3.R
+import com.lagradost.cloudstream3.ui.result.UiImage
+import com.lagradost.cloudstream3.utils.AppUtils
+import com.lagradost.cloudstream3.utils.UIHelper.setImage
+import kotlinx.android.synthetic.main.player_quality_profile_item.view.card_view
+import kotlinx.android.synthetic.main.player_quality_profile_item.view.outline
+import kotlinx.android.synthetic.main.player_quality_profile_item.view.profile_image_background
+import kotlinx.android.synthetic.main.player_quality_profile_item.view.profile_text
+import kotlinx.android.synthetic.main.player_quality_profile_item.view.text_is_mobile_data
+import kotlinx.android.synthetic.main.player_quality_profile_item.view.text_is_wifi
+
+class ProfilesAdapter(
+ override val items: MutableList,
+ val usedProfile: Int,
+ val clickCallback: (oldIndex: Int?, newIndex: Int) -> Unit,
+) :
+ AppUtils.DiffAdapter(
+ 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? = null
+
+ fun getCurrentProfile(): QualityDataHelper.QualityProfile? {
+ return currentItem?.second
+ }
+
+ inner class ProfilesViewHolder(
+ itemView: View,
+ ) : RecyclerView.ViewHolder(itemView) {
+ private val art = listOf(
+ R.drawable.profile_bg_teal,
+ R.drawable.profile_bg_blue,
+ R.drawable.profile_bg_dark_blue,
+ R.drawable.profile_bg_purple,
+ R.drawable.profile_bg_pink,
+ R.drawable.profile_bg_red,
+ R.drawable.profile_bg_orange,
+ )
+
+ fun bind(item: QualityDataHelper.QualityProfile, index: Int) {
+ val priorityText: TextView = itemView.profile_text
+ val profileBg: ImageView = itemView.profile_image_background
+ val wifiText: TextView = itemView.text_is_wifi
+ val dataText: TextView = itemView.text_is_mobile_data
+ val outline: View = itemView.outline
+ val cardView: View = itemView.card_view
+
+ 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
+ // Prevent UI bug when re-selecting the item quickly
+ if (prevIndex == index) {
+ return
+ }
+ currentItem = index to item
+ clickCallback.invoke(prevIndex, index)
+ }
+
+ outline.isVisible = currentItem?.second?.id == item.id
+
+ profileBg.setImage(UiImage.Drawable(art[index % art.size]), null, false) { palette ->
+ val color = palette.getDarkVibrantColor(
+ ContextCompat.getColor(
+ itemView.context,
+ R.color.dubColorBg
+ )
+ )
+ wifiText.backgroundTintList = ColorStateList.valueOf(color)
+ dataText.backgroundTintList = ColorStateList.valueOf(color)
+ }
+
+ val textStyle =
+ if (item.id == usedProfile) {
+ Typeface.BOLD
+ } else {
+ Typeface.NORMAL
+ }
+
+ priorityText.setTypeface(null, textStyle)
+
+ cardView.setOnClickListener {
+ setCurrentItem()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt
new file mode 100644
index 00000000..96249db4
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt
@@ -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 = 7
+
+ /**
+ * 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("$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 {
+ 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,
+ 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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt
new file mode 100644
index 00000000..28a6365f
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt
@@ -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,
+ 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()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt
new file mode 100644
index 00000000..efc1f1b8
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt
@@ -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,
+ 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
+ val sourcesAdapter = sourcesRecyclerView.adapter as? PriorityAdapter
+
+ 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()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt
index 81ef8d57..f2eca5b8 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt
@@ -72,7 +72,7 @@ sealed class UiImage {
fun ImageView?.setImage(value: UiImage?, fadeIn: Boolean = true) {
when (value) {
- is UiImage.Image -> setImageImage(value,fadeIn)
+ is UiImage.Image -> setImageImage(value, fadeIn)
is UiImage.Drawable -> setImageDrawable(value)
null -> {
this?.isVisible = false
@@ -88,7 +88,7 @@ fun ImageView?.setImageImage(value: UiImage.Image, fadeIn: Boolean = true) {
fun ImageView?.setImageDrawable(value: UiImage.Drawable) {
if (this == null) return
this.isVisible = true
- setImageResource(value.resId)
+ this.setImage(UiImage.Drawable(value.resId))
}
@JvmName("imgNull")
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt
index 516cd990..3bdb64e1 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt
@@ -117,7 +117,7 @@ object DataStoreHelper {
/**
* 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? {
val folder = "$currentAccount/$RESULT_WATCH_STATE"
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt
index 5062ebd9..f6373dce 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt
@@ -114,16 +114,16 @@ data class ExtractorSubtitleLink(
*/
val schemaStripRegex = Regex("""^(https:|)//(www\.|)""")
-enum class Qualities(var value: Int) {
- Unknown(400),
- P144(144), // 144p
- P240(240), // 240p
- P360(360), // 360p
- P480(480), // 480p
- P720(720), // 720p
- P1080(1080), // 1080p
- P1440(1440), // 1440p
- P2160(2160); // 4k or 2160p
+enum class Qualities(var value: Int, val defaultPriority: Int) {
+ Unknown(400, 4),
+ P144(144, 0), // 144p
+ P240(240, 2), // 240p
+ P360(360, 3), // 360p
+ P480(480, 4), // 480p
+ P720(720, 5), // 720p
+ P1080(1080, 6), // 1080p
+ P1440(1440, 7), // 1440p
+ P2160(2160, 8); // 4k or 2160p
companion object {
fun getStringByInt(qual: Int?): String {
@@ -135,6 +135,14 @@ enum class Qualities(var value: Int) {
else -> "${qual}p"
}
}
+ fun getStringByIntFull(quality: Int): String {
+ return when (quality) {
+ 0 -> "Auto"
+ Unknown.value -> "Unknown"
+ P2160.value -> "4K"
+ else -> "${quality}p"
+ }
+ }
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
index 2dc6846c..1f6d726d 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
@@ -250,17 +250,6 @@ object SingleSelectionHelper {
)
}
- fun showBottomDialog(
- items: List,
- selectedIndex: Int,
- name: String,
- showApply: Boolean,
- dismissCallback: () -> Unit,
- callback: (Int) -> Unit,
- ) {
-
- }
-
/** Only for a low amount of items */
fun Activity?.showBottomDialog(
items: List,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt
index c300d615..7d798204 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt
@@ -44,12 +44,13 @@ import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestListener
+import com.bumptech.glide.request.RequestOptions.bitmapTransform
import com.bumptech.glide.request.target.Target
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
+import com.lagradost.cloudstream3.ui.result.UiImage
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
-import com.lagradost.cloudstream3.utils.GlideOptions.bitmapTransform
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlin.math.roundToInt
@@ -188,11 +189,30 @@ object UIHelper {
fadeIn: Boolean = true,
colorCallback: ((Palette) -> Unit)? = null
): Boolean {
- if (this == null || url.isNullOrBlank()) return false
+ if (url.isNullOrBlank()) return false
+ this.setImage(UiImage.Image(url, headers, errorImageDrawable), errorImageDrawable, fadeIn, colorCallback)
+ return true
+ }
+
+ fun ImageView?.setImage(
+ uiImage: UiImage?,
+ @DrawableRes
+ errorImageDrawable: Int? = null,
+ fadeIn: Boolean = true,
+ colorCallback: ((Palette) -> Unit)? = null
+ ): Boolean {
+ if (this == null || uiImage == null) return false
+
+ val (glideImage, identifier) =
+ (uiImage as? UiImage.Drawable)?.resId?.let {
+ it to it.toString()
+ } ?: (uiImage as? UiImage.Image)?.let { image ->
+ GlideUrl(image.url) { image.headers ?: emptyMap() } to image.url
+ } ?: return false
return try {
val builder = GlideApp.with(this)
- .load(GlideUrl(url) { headers ?: emptyMap() })
+ .load(glideImage)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.ALL).let { req ->
if (fadeIn)
@@ -211,7 +231,13 @@ object UIHelper {
isFirstResource: Boolean
): Boolean {
resource?.toBitmapOrNull()
- ?.let { bitmap -> createPaletteAsync(url, bitmap, colorCallback) }
+ ?.let { bitmap ->
+ createPaletteAsync(
+ identifier,
+ bitmap,
+ colorCallback
+ )
+ }
return false
}
diff --git a/app/src/main/res/drawable/baseline_help_outline_24.xml b/app/src/main/res/drawable/baseline_help_outline_24.xml
new file mode 100644
index 00000000..3a72cda0
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_help_outline_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_remove_24.xml b/app/src/main/res/drawable/baseline_remove_24.xml
index 791a2f81..f4455598 100644
--- a/app/src/main/res/drawable/baseline_remove_24.xml
+++ b/app/src/main/res/drawable/baseline_remove_24.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="?attr/white">
diff --git a/app/src/main/res/drawable/profile_bg_blue.jpg b/app/src/main/res/drawable/profile_bg_blue.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e573439b04e57d86042066539a6b521621662eb8
GIT binary patch
literal 21107
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