mirror of
https://github.com/recloudstream/cloudstream.git
synced 2026-08-02 04:43:17 +00:00
Compare commits
10 commits
fixsubtitl
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caf0db3dc1 |
||
|
|
1cf7425093 |
||
|
|
d7a9675bd3 |
||
|
|
e80fa1b30d |
||
|
|
7f750964f3 | ||
|
|
ff701cb220 |
||
|
|
a2adb6caaa |
||
|
|
f09435dc18 |
||
|
|
2eb05e8f35 |
||
|
|
1e33e42277 |
35 changed files with 667 additions and 140 deletions
|
|
@ -701,6 +701,23 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(
|
||||||
homeViewModel.queryTextSubmit("")
|
homeViewModel.queryTextSubmit("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load value for toggling Tv layout real time clock. Hide by default at startup
|
||||||
|
// set visibility first, to apply a scroll effect later
|
||||||
|
context?.let {
|
||||||
|
if (isLayout(TV)) {
|
||||||
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
|
||||||
|
val toggleClock =
|
||||||
|
settingsManager.getBoolean(
|
||||||
|
getString(R.string.tv_layout_clock_key),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
binding.homeClock.isVisible = toggleClock
|
||||||
|
} else {
|
||||||
|
binding.homeClock.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
if (isLayout(PHONE)) {
|
if (isLayout(PHONE)) {
|
||||||
|
|
@ -740,6 +757,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(
|
||||||
view.getLocationInWindow(rect)
|
view.getLocationInWindow(rect)
|
||||||
scrollParent.isVisible = true
|
scrollParent.isVisible = true
|
||||||
scrollParent.translationY = rect[1].toFloat() - 60.toPx
|
scrollParent.translationY = rect[1].toFloat() - 60.toPx
|
||||||
|
|
||||||
|
// Move the TV layout real time clock out of the way too
|
||||||
|
// We check if we have the correct layout and if the clock is enabled
|
||||||
|
if(isLayout(TV) && binding.homeClock.isVisible) {
|
||||||
|
val scrollParent = binding.homeClock
|
||||||
|
|
||||||
|
val rect = IntArray(2)
|
||||||
|
view.getLocationInWindow(rect)
|
||||||
|
scrollParent.isVisible = true
|
||||||
|
scrollParent.translationY = rect[1].toFloat() - 60.toPx
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,7 @@ open class FullScreenPlayer : AbstractPlayerFragment<FragmentPlayerBinding>(
|
||||||
playerVideoTitleRez,
|
playerVideoTitleRez,
|
||||||
playerVideoInfo,
|
playerVideoInfo,
|
||||||
playerGoBackHolder,
|
playerGoBackHolder,
|
||||||
|
playerVideoClock,
|
||||||
).forEach {
|
).forEach {
|
||||||
it.animateY(titleMove)
|
it.animateY(titleMove)
|
||||||
}
|
}
|
||||||
|
|
@ -772,7 +773,7 @@ open class FullScreenPlayer : AbstractPlayerFragment<FragmentPlayerBinding>(
|
||||||
val showPlayerEpisodes = !isGone && isThereEpisodes()
|
val showPlayerEpisodes = !isGone && isThereEpisodes()
|
||||||
playerEpisodesButtonRoot.isVisible = showPlayerEpisodes
|
playerEpisodesButtonRoot.isVisible = showPlayerEpisodes
|
||||||
playerEpisodesButton.isVisible = showPlayerEpisodes
|
playerEpisodesButton.isVisible = showPlayerEpisodes
|
||||||
playerVideoTitleHolder.isGone = togglePlayerTitleGone
|
playerVideoTitleHolder.isGone = togglePlayerTitleGone || playerVideoTitle.text.isBlank()
|
||||||
playerVideoTitleRez.isGone = isGone || playerVideoTitleRez.text.isBlank()
|
playerVideoTitleRez.isGone = isGone || playerVideoTitleRez.text.isBlank()
|
||||||
playerEpisodeFiller.isGone = isGone
|
playerEpisodeFiller.isGone = isGone
|
||||||
playerCenterMenu.isGone = isGone
|
playerCenterMenu.isGone = isGone
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
|
|
@ -79,6 +80,7 @@ import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioT
|
||||||
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.LinkSource
|
import com.lagradost.cloudstream3.ui.player.source_priority.LinkSource
|
||||||
|
import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
|
||||||
|
|
@ -132,6 +134,7 @@ import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
@ -1112,21 +1115,29 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
var sourceIndex = 0
|
var sourceIndex = 0
|
||||||
var startSource = 0
|
var startSource = 0
|
||||||
var sortedUrls = emptyList<Pair<ExtractorLink?, ExtractorUri?>>()
|
// Filtered and sorted links
|
||||||
|
var currentHiddenFooter: View? = null
|
||||||
|
var filteredLinks: List<DisplayLink> = emptyList()
|
||||||
|
|
||||||
fun refreshLinks(qualityProfile: Int) {
|
fun refreshLinks(qualityProfile: Int) {
|
||||||
sortedUrls = viewModel.state.sortLinks(qualityProfile)
|
val currentLinkUsed = currentSelectedLink
|
||||||
if (sortedUrls.isEmpty()) {
|
// Always display current linkFooter
|
||||||
|
val sortedLinks = viewModel.state.sortLinks(qualityProfile)
|
||||||
|
|
||||||
|
filteredLinks = sortedLinks.filter { it.shouldUseLink || it.link == currentLinkUsed }
|
||||||
|
|
||||||
|
if (sortedLinks.isEmpty()) {
|
||||||
sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone =
|
sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone =
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
startSource = sortedUrls.indexOf(currentSelectedLink)
|
startSource = filteredLinks.indexOfFirst { it.link == currentLinkUsed }
|
||||||
sourceIndex = startSource
|
sourceIndex = startSource
|
||||||
|
|
||||||
val sourcesArrayAdapter =
|
val sourcesArrayAdapter =
|
||||||
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||||
|
|
||||||
sourcesArrayAdapter.addAll(sortedUrls.map { (link, uri) ->
|
sourcesArrayAdapter.addAll(filteredLinks.map { displayLink ->
|
||||||
|
val (link, uri) = displayLink.link
|
||||||
val name = link?.name ?: uri?.name ?: "NULL"
|
val name = link?.name ?: uri?.name ?: "NULL"
|
||||||
"$name ${Qualities.getStringByInt(link?.quality)}"
|
"$name ${Qualities.getStringByInt(link?.quality)}"
|
||||||
})
|
})
|
||||||
|
|
@ -1142,7 +1153,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
providerList.setOnItemLongClickListener { _, _, position, _ ->
|
providerList.setOnItemLongClickListener { _, _, position, _ ->
|
||||||
sortedUrls.getOrNull(position)?.first?.url?.let {
|
sortedLinks.getOrNull(position)?.link?.first?.url?.let {
|
||||||
clipboardHelper(
|
clipboardHelper(
|
||||||
txt(R.string.video_source),
|
txt(R.string.video_source),
|
||||||
it
|
it
|
||||||
|
|
@ -1150,6 +1161,25 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hiddenLinks = sortedLinks.size - filteredLinks.size
|
||||||
|
providerList.removeFooterView(currentHiddenFooter)
|
||||||
|
|
||||||
|
if (hiddenLinks > 0) {
|
||||||
|
val hiddenLinksFooter: TextView = layoutInflater.inflate(
|
||||||
|
R.layout.sort_bottom_footer_add_choice, null
|
||||||
|
) as TextView
|
||||||
|
|
||||||
|
providerList.addFooterView(hiddenLinksFooter, null, false)
|
||||||
|
currentHiddenFooter = hiddenLinksFooter
|
||||||
|
|
||||||
|
val hiddenLinksText =
|
||||||
|
ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks)
|
||||||
|
.format(hiddenLinks)
|
||||||
|
hiddenLinksFooter.text = hiddenLinksText
|
||||||
|
hiddenLinksFooter.setCompoundDrawables(null, null, null, null)
|
||||||
|
hiddenLinksFooter.setTypeface(null, Typeface.ITALIC)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1363,8 +1393,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (init) {
|
if (init) {
|
||||||
sortedUrls.getOrNull(sourceIndex)?.let {
|
filteredLinks.getOrNull(sourceIndex)?.let {
|
||||||
loadLink(it, true)
|
loadLink(it.link, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourceDialog.dismissSafe(activity)
|
sourceDialog.dismissSafe(activity)
|
||||||
|
|
@ -1532,6 +1562,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun playerError(exception: Throwable) {
|
override fun playerError(exception: Throwable) {
|
||||||
|
currentSelectedLink?.let { link ->
|
||||||
|
viewModel.modifyState { this.addError(link) }
|
||||||
|
}
|
||||||
|
|
||||||
val currentUrl =
|
val currentUrl =
|
||||||
currentSelectedLink?.let { it.first?.url ?: it.second?.uri?.toString() } ?: "unknown"
|
currentSelectedLink?.let { it.first?.url ?: it.second?.uri?.toString() } ?: "unknown"
|
||||||
val headers = currentSelectedLink?.first?.headers?.toString() ?: "none"
|
val headers = currentSelectedLink?.first?.headers?.toString() ?: "none"
|
||||||
|
|
@ -1554,8 +1588,22 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
private fun noLinksFound() {
|
private fun noLinksFound() {
|
||||||
viewModel.forceClearCache = true
|
viewModel.forceClearCache = true
|
||||||
|
val hiddenLinks = viewModel.state.sortLinks(currentQualityProfile).count { !it.shouldUseLink }
|
||||||
|
|
||||||
|
context?.let { ctx ->
|
||||||
|
// Display that there are hidden links to the user.
|
||||||
|
if (hiddenLinks > 0) {
|
||||||
|
val noLinksString = ctx.getString(R.string.no_links_found_toast)
|
||||||
|
val hiddenString =
|
||||||
|
ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks)
|
||||||
|
.format(hiddenLinks)
|
||||||
|
val toastText = "$noLinksString\n($hiddenString)"
|
||||||
|
showToast(toastText, Toast.LENGTH_SHORT)
|
||||||
|
} else {
|
||||||
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1566,7 +1614,9 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val links = viewModel.state.sortLinks(currentQualityProfile)
|
val links = viewModel.state.sortLinks(currentQualityProfile)
|
||||||
if (links.isEmpty()) {
|
|
||||||
|
val firstAvailableLink = links.firstOrNull { it.shouldUseLink }?.link
|
||||||
|
if (firstAvailableLink == null) {
|
||||||
noLinksFound()
|
noLinksFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -1574,7 +1624,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
if (!isPlayerActive.compareAndSet(false, true)) {
|
if (!isPlayerActive.compareAndSet(false, true)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loadLink(links.first(), false)
|
loadLink(firstAvailableLink, false)
|
||||||
showPlayerMetadata()
|
showPlayerMetadata()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1641,25 +1691,26 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasNextMirror(): Boolean {
|
private fun getNextLink(): DisplayLink? {
|
||||||
val links = viewModel.state.sortLinks(currentQualityProfile)
|
val links = viewModel.state.sortLinks(currentQualityProfile)
|
||||||
return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size
|
val currentIndex = links.indexOfFirst { it.link == currentSelectedLink }
|
||||||
|
val nextPotentialLink =
|
||||||
|
links.withIndex().firstOrNull { it.index > currentIndex && it.value.shouldUseLink }
|
||||||
|
return nextPotentialLink?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasNextMirror(): Boolean {
|
||||||
|
return getNextLink() != null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nextMirror() {
|
override fun nextMirror() {
|
||||||
val links = viewModel.state.sortLinks(currentQualityProfile)
|
val nextLink = getNextLink()
|
||||||
if (links.isEmpty()) {
|
if (nextLink == null) {
|
||||||
noLinksFound()
|
noLinksFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val newIndex = links.indexOf(currentSelectedLink) + 1
|
loadLink(nextLink.link, true)
|
||||||
if (newIndex >= links.size) {
|
|
||||||
noLinksFound()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loadLink(links[newIndex], true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
@ -2166,6 +2217,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
isPlayerActive.set(false)
|
isPlayerActive.set(false)
|
||||||
binding?.overlayLoadingSkipButton?.isVisible = false
|
binding?.overlayLoadingSkipButton?.isVisible = false
|
||||||
binding?.playerLoadingOverlay?.isVisible = true
|
binding?.playerLoadingOverlay?.isVisible = true
|
||||||
|
viewModel.modifyState { setError(emptyList()) }
|
||||||
uiReset()
|
uiReset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2219,6 +2271,14 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
fromTagToEnglishLanguageName(it)?.lowercase() ?: return@mapNotNull null
|
fromTagToEnglishLanguageName(it)?.lowercase() ?: return@mapNotNull null
|
||||||
} ?: listOf()
|
} ?: listOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up TV clock visibility
|
||||||
|
if (isLayout(TV)) {
|
||||||
|
val showTvClock = settingsManager.getBoolean(ctx.getString(R.string.tv_layout_clock_key), false)
|
||||||
|
playerBinding?.playerVideoClock?.isVisible = showTvClock
|
||||||
|
} else {
|
||||||
|
playerBinding?.playerVideoClock?.isVisible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapBundle(savedInstanceState)
|
unwrapBundle(savedInstanceState)
|
||||||
|
|
@ -2297,19 +2357,23 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(viewModel.currentLinks) { (links, instance) ->
|
observe(viewModel.currentLinks) { (_, instance) ->
|
||||||
if (instance != viewModel.state.instance) return@observe // Outdated observe
|
if (instance != viewModel.state.instance) return@observe // Outdated observe
|
||||||
|
|
||||||
val turnVisible = links.isNotEmpty() && viewModel.generator?.canSkipLoading == true
|
val sortedLinks = viewModel.state.sortLinks(currentQualityProfile)
|
||||||
|
val usableLinks = sortedLinks.count { link -> link.shouldUseLink }
|
||||||
|
|
||||||
|
val turnVisible = usableLinks > 0 && viewModel.generator?.canSkipLoading == true
|
||||||
val wasGone = binding.overlayLoadingSkipButton.isGone
|
val wasGone = binding.overlayLoadingSkipButton.isGone
|
||||||
|
|
||||||
binding.overlayLoadingSkipButton.apply {
|
binding.overlayLoadingSkipButton.apply {
|
||||||
isVisible = turnVisible
|
isVisible = turnVisible
|
||||||
if (links.isEmpty()) {
|
|
||||||
|
if (usableLinks == 0) {
|
||||||
setText(R.string.skip_loading)
|
setText(R.string.skip_loading)
|
||||||
} else {
|
} else {
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
text = "${context.getString(R.string.skip_loading)} (${links.size})"
|
text = "${context.getString(R.string.skip_loading)} (${usableLinks})"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings
|
||||||
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
|
@ -40,12 +42,20 @@ data class GeneratorState(
|
||||||
val id: Int?,
|
val id: Int?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class DisplayLink(
|
||||||
|
val link: VideoLink,
|
||||||
|
// If the link should be displayed and used by the player
|
||||||
|
val shouldUseLink: Boolean,
|
||||||
|
val priority: Int
|
||||||
|
)
|
||||||
|
|
||||||
/** Immutable state of all current links relevant to displaying the video */
|
/** Immutable state of all current links relevant to displaying the video */
|
||||||
// @MustUseReturnValues
|
// @MustUseReturnValues
|
||||||
// @Immutable
|
// @Immutable
|
||||||
data class VideoState(
|
data class VideoState(
|
||||||
val subtitles: PersistentSet<SubtitleData> = persistentSetOf(),
|
val subtitles: PersistentSet<SubtitleData> = persistentSetOf(),
|
||||||
val links: PersistentSet<VideoLink> = persistentSetOf(),
|
val links: PersistentSet<VideoLink> = persistentSetOf(),
|
||||||
|
val erroredLinks: PersistentSet<VideoLink> = persistentSetOf(),
|
||||||
val stamps: PersistentList<VideoSkipStamp> = persistentListOf(),
|
val stamps: PersistentList<VideoSkipStamp> = persistentListOf(),
|
||||||
val loading: Resource<Unit> = Resource.Loading(),
|
val loading: Resource<Unit> = Resource.Loading(),
|
||||||
val generatorState: GeneratorState? = null,
|
val generatorState: GeneratorState? = null,
|
||||||
|
|
@ -56,19 +66,52 @@ data class VideoState(
|
||||||
*
|
*
|
||||||
* sortedBy is not exactly expensive, but each hasNextMirror does it again, so this alleviates unnecessary recomputation
|
* sortedBy is not exactly expensive, but each hasNextMirror does it again, so this alleviates unnecessary recomputation
|
||||||
* */
|
* */
|
||||||
private val sortedLinks: ConcurrentHashMap<Int, List<VideoLink>> = ConcurrentHashMap()
|
private val sortedLinks: ConcurrentHashMap<Int, List<DisplayLink>> = ConcurrentHashMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cache is guaranteed to be up to date link-wise due to the immutable links.
|
||||||
|
* However, hideNegativeSources and hideErrorSources could be updated, which requires clearing the cache.
|
||||||
|
*/
|
||||||
fun clearSortedLinksCache() = sortedLinks.clear()
|
fun clearSortedLinksCache() = sortedLinks.clear()
|
||||||
|
|
||||||
|
private fun hasLinkErrored(link: VideoLink): Boolean {
|
||||||
|
return erroredLinks.any { it == link }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun VideoLink.toDisplayLink(
|
||||||
|
qualityProfile: Int,
|
||||||
|
hideNegativeSources: Boolean,
|
||||||
|
hideErrorSources: Boolean
|
||||||
|
): DisplayLink {
|
||||||
|
val priority = getLinkPriority(qualityProfile, this.first)
|
||||||
|
val shouldHideLink =
|
||||||
|
(hideNegativeSources && priority < 0) || (hideErrorSources && hasLinkErrored(this))
|
||||||
|
val displayLink = DisplayLink(this, !shouldHideLink, priority)
|
||||||
|
|
||||||
|
return displayLink
|
||||||
|
}
|
||||||
|
|
||||||
// Modifying sortedLinks is not considered a "visible" side effect, and rerunning it does not change the result
|
// Modifying sortedLinks is not considered a "visible" side effect, and rerunning it does not change the result
|
||||||
// It is by all standards, idempotent and by extension also pure as it has no "visible" side effect
|
// 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.
|
/** Returns .links in the sorted order according to the qualityProfile.
|
||||||
* Use .links if order is not needed */
|
* Use .links if order is not needed */
|
||||||
@Contract(pure = true)
|
@Contract(pure = true)
|
||||||
fun sortLinks(qualityProfile: Int): List<VideoLink> {
|
fun sortLinks(qualityProfile: Int): List<DisplayLink> {
|
||||||
return sortedLinks[qualityProfile] ?: links.sortedBy { link ->
|
sortedLinks[qualityProfile]?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
val hideNegativeSources =
|
||||||
|
QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideNegativeSources)
|
||||||
|
val hideErrorSources =
|
||||||
|
QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideErrorSources)
|
||||||
|
|
||||||
|
return links.map { link ->
|
||||||
// negative because we want to sort highest quality first
|
// negative because we want to sort highest quality first
|
||||||
-getLinkPriority(qualityProfile, link.first)
|
link.toDisplayLink(qualityProfile, hideNegativeSources, hideErrorSources)
|
||||||
|
}.sortedBy {
|
||||||
|
// negative because we want to sort highest quality first
|
||||||
|
-it.priority
|
||||||
}.also { value -> sortedLinks[qualityProfile] = value }
|
}.also { value -> sortedLinks[qualityProfile] = value }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,6 +156,12 @@ data class VideoState(
|
||||||
@JvmName("setVideoSkipStamp")
|
@JvmName("setVideoSkipStamp")
|
||||||
@Contract(pure = true)
|
@Contract(pure = true)
|
||||||
fun set(items: Collection<VideoSkipStamp>): VideoState = copy(stamps = items.toPersistentList())
|
fun set(items: Collection<VideoSkipStamp>): VideoState = copy(stamps = items.toPersistentList())
|
||||||
|
|
||||||
|
@Contract(pure = true)
|
||||||
|
fun addError(item: VideoLink): VideoState = copy(erroredLinks = erroredLinks.add(item))
|
||||||
|
|
||||||
|
@Contract(pure = true)
|
||||||
|
fun setError(items: Collection<VideoLink>): VideoState = copy(erroredLinks = items.toPersistentSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
data class VideoLive<T>(
|
data class VideoLive<T>(
|
||||||
|
|
@ -141,9 +190,8 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
var state = VideoState(instance = 0)
|
var state = VideoState(instance = 0)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private val _currentLinks =
|
private val _currentLinks = MutableLiveData<VideoLive<Set<VideoLink>>>(null)
|
||||||
MutableLiveData<VideoLive<Set<Pair<ExtractorLink?, ExtractorUri?>>>>(null)
|
val currentLinks: LiveData<VideoLive<Set<VideoLink>>> = _currentLinks
|
||||||
val currentLinks: LiveData<VideoLive<Set<Pair<ExtractorLink?, ExtractorUri?>>>> = _currentLinks
|
|
||||||
|
|
||||||
private val _currentSubtitles = MutableLiveData<VideoLive<Set<SubtitleData>>>(null)
|
private val _currentSubtitles = MutableLiveData<VideoLive<Set<SubtitleData>>>(null)
|
||||||
val currentSubtitles: LiveData<VideoLive<Set<SubtitleData>>> = _currentSubtitles
|
val currentSubtitles: LiveData<VideoLive<Set<SubtitleData>>> = _currentSubtitles
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,16 @@ import com.lagradost.cloudstream3.utils.txt
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
import java.util.EnumMap
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import kotlin.also
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
object QualityDataHelper {
|
object QualityDataHelper {
|
||||||
private const val VIDEO_SOURCE_PRIORITY = "video_source_priority"
|
private const val VIDEO_SOURCE_PRIORITY = "video_source_priority"
|
||||||
private const val VIDEO_PROFILE_NAME = "video_profile_name"
|
private const val VIDEO_PROFILE_NAME = "video_profile_name"
|
||||||
private const val VIDEO_QUALITY_PRIORITY = "video_quality_priority"
|
private const val VIDEO_QUALITY_PRIORITY = "video_quality_priority"
|
||||||
|
const val VIDEO_PROFILE_SETTINGS = "video_profile_settings"
|
||||||
|
|
||||||
// Old key only supporting one type per profile
|
// Old key only supporting one type per profile
|
||||||
@Deprecated("Changed to support multiple types per profile")
|
@Deprecated("Changed to support multiple types per profile")
|
||||||
|
|
@ -53,13 +57,21 @@ object QualityDataHelper {
|
||||||
val types: Set<QualityProfileType>
|
val types: Set<QualityProfileType>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// Map profile and name to priority
|
||||||
|
val sourcePriorityCache: ConcurrentHashMap<Int, HashMap<String, Int>> = ConcurrentHashMap()
|
||||||
|
|
||||||
fun getSourcePriority(profile: Int, name: String?): Int {
|
fun getSourcePriority(profile: Int, name: String?): Int {
|
||||||
if (name == null) return DEFAULT_SOURCE_PRIORITY
|
if (name == null) return DEFAULT_SOURCE_PRIORITY
|
||||||
return getKey<Int>(
|
|
||||||
|
return sourcePriorityCache[profile]?.get(name) ?: (getKey<Int>(
|
||||||
"$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile",
|
"$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile",
|
||||||
name,
|
name,
|
||||||
DEFAULT_SOURCE_PRIORITY
|
DEFAULT_SOURCE_PRIORITY
|
||||||
) ?: DEFAULT_SOURCE_PRIORITY
|
) ?: DEFAULT_SOURCE_PRIORITY).also {
|
||||||
|
sourcePriorityCache.getOrPut(profile) { hashMapOf() }
|
||||||
|
sourcePriorityCache[profile]?.set(name, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllSourcePriorityNames(profile: Int): List<String> {
|
fun getAllSourcePriorityNames(profile: Int): List<String> {
|
||||||
|
|
@ -77,6 +89,8 @@ object QualityDataHelper {
|
||||||
} else {
|
} else {
|
||||||
setKey(folder, name, priority)
|
setKey(folder, name, priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourcePriorityCache[profile]?.set(name, priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setProfileName(profile: Int, name: String?) {
|
fun setProfileName(profile: Int, name: String?) {
|
||||||
|
|
@ -93,12 +107,17 @@ object QualityDataHelper {
|
||||||
?: txt(R.string.profile_number, profile)
|
?: txt(R.string.profile_number, profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map profile and quality to priority
|
||||||
|
val qualityPriorityCache: ConcurrentHashMap<Int, EnumMap<Qualities, Int>> = ConcurrentHashMap()
|
||||||
fun getQualityPriority(profile: Int, quality: Qualities): Int {
|
fun getQualityPriority(profile: Int, quality: Qualities): Int {
|
||||||
return getKey<Int>(
|
return qualityPriorityCache[profile]?.get(quality) ?: (getKey<Int>(
|
||||||
"$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile",
|
"$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile",
|
||||||
quality.value.toString(),
|
quality.value.toString(),
|
||||||
quality.defaultPriority
|
quality.defaultPriority
|
||||||
) ?: quality.defaultPriority
|
)?.also {
|
||||||
|
qualityPriorityCache.getOrPut(profile) { EnumMap(Qualities::class.java) }
|
||||||
|
qualityPriorityCache[profile]?.set(quality, it)
|
||||||
|
}) ?: quality.defaultPriority
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setQualityPriority(profile: Int, quality: Qualities, priority: Int) {
|
fun setQualityPriority(profile: Int, quality: Qualities, priority: Int) {
|
||||||
|
|
@ -107,8 +126,24 @@ object QualityDataHelper {
|
||||||
quality.value.toString(),
|
quality.value.toString(),
|
||||||
priority
|
priority
|
||||||
)
|
)
|
||||||
|
qualityPriorityCache[profile]?.set(quality, priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> setProfileSetting(profile: Int, setting: ProfileSettings<T>, value: T) {
|
||||||
|
val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile"
|
||||||
|
// Prevent unnecessary keys
|
||||||
|
if (value == setting.defaultValue) {
|
||||||
|
removeKey(folder, setting.key)
|
||||||
|
} else {
|
||||||
|
setKey(folder, setting.key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> getProfileSetting(profile: Int, setting: ProfileSettings<T>): T {
|
||||||
|
val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile"
|
||||||
|
val value = getKey<T>(folder, setting.key)
|
||||||
|
return value ?: setting.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
fun getQualityProfileTypes(profile: Int): Set<QualityProfileType> {
|
fun getQualityProfileTypes(profile: Int): Set<QualityProfileType> {
|
||||||
|
|
@ -224,3 +259,8 @@ object QualityDataHelper {
|
||||||
return Qualities.entries.minBy { abs(it.value - target) }
|
return Qualities.entries.minBy { abs(it.value - target) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
|
||||||
|
|
||||||
class SourcePriorityDialog(
|
class SourcePriorityDialog(
|
||||||
val ctx: Context,
|
val ctx: Context,
|
||||||
@StyleRes themeRes: Int,
|
@StyleRes val themeRes: Int,
|
||||||
val links: List<LinkSource>,
|
val links: List<LinkSource>,
|
||||||
private val profile: QualityDataHelper.QualityProfile,
|
private val profile: QualityDataHelper.QualityProfile,
|
||||||
/**
|
/**
|
||||||
|
|
@ -28,28 +28,27 @@ class SourcePriorityDialog(
|
||||||
PlayerSelectSourcePriorityBinding.inflate(LayoutInflater.from(ctx), null, false)
|
PlayerSelectSourcePriorityBinding.inflate(LayoutInflater.from(ctx), null, false)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
fixSystemBarsPadding(binding.root)
|
fixSystemBarsPadding(binding.root)
|
||||||
val sourcesRecyclerView = binding.sortSources
|
|
||||||
val qualitiesRecyclerView = binding.sortQualities
|
|
||||||
val profileText = binding.profileTextEditable
|
|
||||||
val saveBtt = binding.saveBtt
|
|
||||||
val exitBtt = binding.closeBtt
|
|
||||||
val helpBtt = binding.helpBtt
|
|
||||||
|
|
||||||
profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context))
|
binding.apply {
|
||||||
profileText.hint = txt(R.string.profile_number, profile.id).asString(context)
|
profileTextEditable.setText(
|
||||||
|
QualityDataHelper.getProfileName(profile.id).asString(context)
|
||||||
|
)
|
||||||
|
profileTextEditable.hint = txt(R.string.profile_number, profile.id).asString(context)
|
||||||
|
|
||||||
sourcesRecyclerView.adapter = PriorityAdapter<Nothing?>(
|
sortSources.adapter = PriorityAdapter<Nothing?>(
|
||||||
).apply {
|
).apply {
|
||||||
submitList(links.map { link ->
|
val sortedLinks = links.map { link ->
|
||||||
SourcePriority(
|
SourcePriority(
|
||||||
null,
|
null,
|
||||||
link.source,
|
link.source,
|
||||||
QualityDataHelper.getSourcePriority(profile.id, link.source)
|
QualityDataHelper.getSourcePriority(profile.id, link.source)
|
||||||
)
|
)
|
||||||
}.distinctBy { it.name }.sortedBy { -it.priority })
|
}.distinctBy { it.name }.sortedBy { -it.priority }
|
||||||
|
|
||||||
|
submitList(sortedLinks)
|
||||||
}
|
}
|
||||||
|
|
||||||
qualitiesRecyclerView.adapter = PriorityAdapter<Qualities>(
|
sortQualities.adapter = PriorityAdapter<Qualities>(
|
||||||
).apply {
|
).apply {
|
||||||
submitList(Qualities.entries.mapNotNull {
|
submitList(Qualities.entries.mapNotNull {
|
||||||
SourcePriority(
|
SourcePriority(
|
||||||
|
|
@ -62,8 +61,8 @@ class SourcePriorityDialog(
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST") // We know the types
|
@Suppress("UNCHECKED_CAST") // We know the types
|
||||||
saveBtt.setOnClickListener {
|
saveBtt.setOnClickListener {
|
||||||
val qualityAdapter = qualitiesRecyclerView.adapter as? PriorityAdapter<Qualities>
|
val qualityAdapter = sortQualities.adapter as? PriorityAdapter<Qualities>
|
||||||
val sourcesAdapter = sourcesRecyclerView.adapter as? PriorityAdapter<Nothing?>
|
val sourcesAdapter = sortSources.adapter as? PriorityAdapter<Nothing?>
|
||||||
|
|
||||||
val qualities = qualityAdapter?.immutableCurrentList ?: emptyList()
|
val qualities = qualityAdapter?.immutableCurrentList ?: emptyList()
|
||||||
val sources = sourcesAdapter?.immutableCurrentList ?: emptyList()
|
val sources = sourcesAdapter?.immutableCurrentList ?: emptyList()
|
||||||
|
|
@ -79,7 +78,7 @@ class SourcePriorityDialog(
|
||||||
qualityAdapter?.submitList(qualities.sortedBy { -it.priority })
|
qualityAdapter?.submitList(qualities.sortedBy { -it.priority })
|
||||||
sourcesAdapter?.submitList(sources.sortedBy { -it.priority })
|
sourcesAdapter?.submitList(sources.sortedBy { -it.priority })
|
||||||
|
|
||||||
val savedProfileName = profileText.text.toString()
|
val savedProfileName = profileTextEditable.text.toString()
|
||||||
if (savedProfileName.isBlank()) {
|
if (savedProfileName.isBlank()) {
|
||||||
QualityDataHelper.setProfileName(profile.id, null)
|
QualityDataHelper.setProfileName(profile.id, null)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -88,8 +87,8 @@ class SourcePriorityDialog(
|
||||||
updatedCallback.invoke()
|
updatedCallback.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
exitBtt.setOnClickListener {
|
closeBtt.setOnClickListener {
|
||||||
this.dismissSafe()
|
dismissSafe()
|
||||||
}
|
}
|
||||||
|
|
||||||
helpBtt.setOnClickListener {
|
helpBtt.setOnClickListener {
|
||||||
|
|
@ -98,6 +97,10 @@ class SourcePriorityDialog(
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settingsBtt.setOnClickListener {
|
||||||
|
SourceProfileSettingsDialog(ctx, themeRes, profile.id).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
super.show()
|
super.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.player.source_priority
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import androidx.annotation.StyleRes
|
||||||
|
import com.lagradost.cloudstream3.databinding.SourceProfileSettingsDialogBinding
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
|
||||||
|
|
||||||
|
class SourceProfileSettingsDialog(
|
||||||
|
val ctx: Context,
|
||||||
|
@StyleRes themeRes: Int,
|
||||||
|
val profile: Int
|
||||||
|
) : Dialog(ctx, themeRes) {
|
||||||
|
override fun show() {
|
||||||
|
val binding =
|
||||||
|
SourceProfileSettingsDialogBinding.inflate(LayoutInflater.from(ctx), null, false)
|
||||||
|
setContentView(binding.root)
|
||||||
|
fixSystemBarsPadding(binding.root)
|
||||||
|
|
||||||
|
binding.apply {
|
||||||
|
var hideErrorSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideErrorSources)
|
||||||
|
var hideNegativeSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideNegativeSources)
|
||||||
|
|
||||||
|
profileHideErrorSources.isChecked = hideErrorSources
|
||||||
|
profileHideErrorSources.setOnCheckedChangeListener { _, bool ->
|
||||||
|
hideErrorSources = bool
|
||||||
|
}
|
||||||
|
|
||||||
|
profileHideNegativeSources.isChecked = hideNegativeSources
|
||||||
|
profileHideNegativeSources.setOnCheckedChangeListener { _, bool ->
|
||||||
|
hideNegativeSources = bool
|
||||||
|
}
|
||||||
|
|
||||||
|
applyBtt.setOnClickListener {
|
||||||
|
QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideErrorSources, hideErrorSources)
|
||||||
|
QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideNegativeSources, hideNegativeSources)
|
||||||
|
dismissSafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelBtt.setOnClickListener {
|
||||||
|
dismissSafe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -77,6 +77,7 @@ val appLanguages = arrayListOf(
|
||||||
Pair("Azərbaycan dili", "az"),
|
Pair("Azərbaycan dili", "az"),
|
||||||
Pair("Bahasa Indonesia", "in"),
|
Pair("Bahasa Indonesia", "in"),
|
||||||
Pair("Bahasa Melayu", "ms"),
|
Pair("Bahasa Melayu", "ms"),
|
||||||
|
Pair("català", "ca"),
|
||||||
Pair("Deutsch", "de"),
|
Pair("Deutsch", "de"),
|
||||||
Pair("English", "en"),
|
Pair("English", "en"),
|
||||||
Pair("Español", "es"),
|
Pair("Español", "es"),
|
||||||
|
|
@ -97,6 +98,7 @@ val appLanguages = arrayListOf(
|
||||||
Pair("Português", "pt"),
|
Pair("Português", "pt"),
|
||||||
Pair("Português (Brasil)", "pt-BR"),
|
Pair("Português (Brasil)", "pt-BR"),
|
||||||
Pair("Română", "ro"),
|
Pair("Română", "ro"),
|
||||||
|
Pair("Shqip мова", "sq"),
|
||||||
Pair("Slovenčina", "sk"),
|
Pair("Slovenčina", "sk"),
|
||||||
Pair("Soomaaliga", "so"),
|
Pair("Soomaaliga", "so"),
|
||||||
Pair("Svenska", "sv"),
|
Pair("Svenska", "sv"),
|
||||||
|
|
@ -106,6 +108,7 @@ val appLanguages = arrayListOf(
|
||||||
Pair("Wikang Filipino", "fil"),
|
Pair("Wikang Filipino", "fil"),
|
||||||
Pair("Čeština", "cs"),
|
Pair("Čeština", "cs"),
|
||||||
Pair("Ελληνικά", "el"),
|
Pair("Ελληνικά", "el"),
|
||||||
|
Pair("беларуская мова", "be"),
|
||||||
Pair("български", "bg"),
|
Pair("български", "bg"),
|
||||||
Pair("македонски", "mk"),
|
Pair("македонски", "mk"),
|
||||||
Pair("русский", "ru"),
|
Pair("русский", "ru"),
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,8 @@ class SettingsUI : BasePreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPref(R.string.tv_layout_clock_key)?.hideOn(PHONE or EMULATOR)
|
||||||
|
|
||||||
getPref(R.string.confirm_exit_key)?.setOnPreferenceClickListener {
|
getPref(R.string.confirm_exit_key)?.setOnPreferenceClickListener {
|
||||||
val prefNames = resources.getStringArray(R.array.confirm_exit)
|
val prefNames = resources.getStringArray(R.array.confirm_exit)
|
||||||
val prefValues = resources.getIntArray(R.array.confirm_exit_values)
|
val prefValues = resources.getIntArray(R.array.confirm_exit_values)
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,9 @@ class SubtitlesFragment : BaseDialogFragment<SubtitleSettingsBinding>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
view.clipToPadding = false
|
||||||
|
view.clipChildren = false
|
||||||
|
|
||||||
// we default to 25sp, this is needed as RoundedBackgroundColorSpan breaks on override sizes
|
// we default to 25sp, this is needed as RoundedBackgroundColorSpan breaks on override sizes
|
||||||
val size = data.fixedTextSize ?: 25.0f
|
val size = data.fixedTextSize ?: 25.0f
|
||||||
view.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
view.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, size)
|
||||||
|
|
|
||||||
|
|
@ -181,11 +181,11 @@ object DataStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> Context.getKey(path: String, valueType: Class<T>): T? {
|
fun <T : Any> Context.getKey(path: String, valueType: Class<T>): T? {
|
||||||
try {
|
return try {
|
||||||
val json: String = getSharedPrefs().getString(path, null) ?: return null
|
val json: String = getSharedPrefs().getString(path, null) ?: return null
|
||||||
return parseJson(json, valueType.kotlin)
|
parseJson(json, valueType.kotlin)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,21 +193,37 @@ object DataStore {
|
||||||
setKey(getFolderName(folder, path), value)
|
setKey(getFolderName(folder, path), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
message = "Use parseJson<T>(this) directly instead.",
|
||||||
|
level = DeprecationLevel.WARNING,
|
||||||
|
replaceWith = ReplaceWith(
|
||||||
|
expression = "parseJson<T>(this)",
|
||||||
|
imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"],
|
||||||
|
),
|
||||||
|
)
|
||||||
inline fun <reified T : Any> String.toKotlinObject(): T {
|
inline fun <reified T : Any> String.toKotlinObject(): T {
|
||||||
return parseJson(this)
|
return parseJson(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
message = "Use parseJson<T>(this) directly instead.",
|
||||||
|
level = DeprecationLevel.WARNING,
|
||||||
|
replaceWith = ReplaceWith(
|
||||||
|
expression = "parseJson<T>(this)",
|
||||||
|
imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"],
|
||||||
|
),
|
||||||
|
)
|
||||||
fun <T : Any> String.toKotlinObject(valueType: Class<T>): T {
|
fun <T : Any> String.toKotlinObject(valueType: Class<T>): T {
|
||||||
return parseJson(this, valueType.kotlin)
|
return parseJson(this, valueType.kotlin)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET KEY GIVEN PATH AND DEFAULT VALUE, NULL IF ERROR
|
// GET KEY GIVEN PATH AND DEFAULT VALUE, NULL IF ERROR
|
||||||
inline fun <reified T : Any> Context.getKey(path: String, defVal: T?): T? {
|
inline fun <reified T : Any> Context.getKey(path: String, defVal: T?): T? {
|
||||||
try {
|
return try {
|
||||||
val json: String = getSharedPrefs().getString(path, null) ?: return defVal
|
val json: String = getSharedPrefs().getString(path, null) ?: return defVal
|
||||||
return json.toKotlinObject()
|
parseJson<T>(json)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,10 +149,12 @@ object TvChannelUtils {
|
||||||
.setInputId(inputId)
|
.setInputId(inputId)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val channelUri = context.contentResolver.insert(
|
val channelUri = runCatching {
|
||||||
|
context.contentResolver.insert(
|
||||||
TvContractCompat.Channels.CONTENT_URI,
|
TvContractCompat.Channels.CONTENT_URI,
|
||||||
channel.toContentValues()
|
channel.toContentValues()
|
||||||
)
|
)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
channelUri?.let {
|
channelUri?.let {
|
||||||
val channelId = ContentUris.parseId(it)
|
val channelId = ContentUris.parseId(it)
|
||||||
|
|
|
||||||
9
app/src/main/res/drawable/ic_baseline_clock_24.xml
Normal file
9
app/src/main/res/drawable/ic_baseline_clock_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:pathData="m612,668 l56,-56 -148,-148v-184h-80v216l172,172ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,480ZM480,800q133,0 226.5,-93.5T800,480q0,-133 -93.5,-226.5T480,160q-133,0 -226.5,93.5T160,480q0,133 93.5,226.5T480,800Z"
|
||||||
|
android:fillColor="#e3e3e3"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
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"
|
||||||
|
|
@ -19,8 +18,8 @@
|
||||||
android:id="@+id/subtitles_click_settings"
|
android:id="@+id/subtitles_click_settings"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||||
|
|
||||||
|
|
@ -33,14 +32,21 @@
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/settings_btt"
|
||||||
|
style="@style/WhiteButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:text="@string/title_settings" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/help_btt"
|
android:id="@+id/help_btt"
|
||||||
android:layout_width="44dp"
|
android:layout_width="44dp"
|
||||||
android:layout_height="44dp"
|
android:layout_height="44dp"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/help"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:src="@drawable/baseline_help_outline_24"
|
android:src="@drawable/baseline_help_outline_24" />
|
||||||
android:contentDescription="@string/help" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
@ -93,8 +99,8 @@
|
||||||
android:id="@+id/apply_btt_holder"
|
android:id="@+id/apply_btt_holder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
android:paddingHorizontal="?android:attr/listPreferredItemPaddingStart">
|
android:paddingHorizontal="?android:attr/listPreferredItemPaddingStart">
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
|
|
@ -108,19 +114,19 @@
|
||||||
android:textColor="?attr/textColor"
|
android:textColor="?attr/textColor"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:text="@string/profile_number"
|
tools:ignore="LabelFor"
|
||||||
tools:ignore="LabelFor" />
|
tools:text="@string/profile_number" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/save_btt"
|
android:id="@+id/save_btt"
|
||||||
|
style="@style/WhiteButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="@string/sort_save"
|
android:text="@string/sort_save" />
|
||||||
style="@style/WhiteButton" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/close_btt"
|
android:id="@+id/close_btt"
|
||||||
|
style="@style/BlackButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="@string/sort_close"
|
android:text="@string/sort_close" />
|
||||||
style="@style/BlackButton" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,12 @@
|
||||||
tools:listitem="@layout/homepage_parent"
|
tools:listitem="@layout/homepage_parent"
|
||||||
tools:visibility="gone" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<TextClock
|
||||||
|
android:id="@+id/home_clock"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/home_api_fab"
|
android:id="@+id/home_api_fab"
|
||||||
style="@style/ExtendedFloatingActionButton"
|
style="@style/ExtendedFloatingActionButton"
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
@ -163,6 +164,22 @@
|
||||||
tools:listitem="@layout/homepage_parent_tv"
|
tools:listitem="@layout/homepage_parent_tv"
|
||||||
tools:visibility="gone" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<TextClock
|
||||||
|
android:id="@+id/home_clock"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxWidth="600dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:visibility="visible"
|
||||||
|
android:format12Hour="hh:mm a"
|
||||||
|
android:format24Hour="HH:mm"
|
||||||
|
android:fontFamily="@font/google_sans"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="start"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:layout_margin="10dp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/home_api_holder"
|
android:id="@+id/home_api_holder"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,15 @@
|
||||||
tools:visibility="visible"
|
tools:visibility="visible"
|
||||||
android:layout_gravity="center"/>
|
android:layout_gravity="center"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextClock
|
||||||
|
android:id="@+id/player_video_clock"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:gravity="end"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:textStyle="bold" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Removed as it has no use anymore-->
|
<!-- Removed as it has no use anymore-->
|
||||||
|
|
@ -1076,6 +1085,8 @@
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/subtitle_holder"
|
android:id="@+id/subtitle_holder"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -352,6 +352,22 @@
|
||||||
android:layout_marginEnd="32dp"
|
android:layout_marginEnd="32dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextClock
|
||||||
|
android:id="@+id/player_video_clock"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:gravity="end"
|
||||||
|
android:maxWidth="600dp"
|
||||||
|
android:textAlignment="viewEnd"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="visible"
|
||||||
|
android:format12Hour="hh:mm a"
|
||||||
|
android:format24Hour="HH:mm"
|
||||||
|
android:fontFamily="@font/google_sans"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/player_video_title_holder"
|
android:id="@+id/player_video_title_holder"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
@ -1116,6 +1132,8 @@
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/subtitle_holder"
|
android:id="@+id/subtitle_holder"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,13 @@
|
||||||
<!-- android:layout_gravity="center"-->
|
<!-- android:layout_gravity="center"-->
|
||||||
<!-- android:src="@drawable/outline_edit_24" />-->
|
<!-- android:src="@drawable/outline_edit_24" />-->
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/settings_btt"
|
||||||
|
style="@style/WhiteButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:text="@string/title_settings" />
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
||||||
99
app/src/main/res/layout/source_profile_settings_dialog.xml
Normal file
99
app/src/main/res/layout/source_profile_settings_dialog.xml
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout 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/profile_settings_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/primaryBlackBackground"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- <ScrollView-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="match_parent">-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_rowWeight="1"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="20dp"
|
||||||
|
android:text="@string/profile_settings"
|
||||||
|
android:textColor="?attr/textColor"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/profile_hide_negative_sources"
|
||||||
|
style="@style/SettingsItem"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/google_sans"
|
||||||
|
android:nextFocusLeft="@id/apply_btt"
|
||||||
|
android:nextFocusRight="@id/cancel_btt"
|
||||||
|
android:nextFocusDown="@id/profile_hide_error_sources"
|
||||||
|
android:text="@string/profile_hide_negative_sources"
|
||||||
|
app:drawableEndCompat="@null"
|
||||||
|
app:trackTint="@color/toggle_selector" />
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/profile_hide_error_sources"
|
||||||
|
style="@style/SettingsItem"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/google_sans"
|
||||||
|
android:nextFocusLeft="@id/apply_btt"
|
||||||
|
android:nextFocusRight="@id/cancel_btt"
|
||||||
|
android:nextFocusUp="@id/profile_hide_negative_sources"
|
||||||
|
android:nextFocusDown="@id/apply_btt"
|
||||||
|
android:text="@string/profile_hide_error_sources"
|
||||||
|
app:drawableEndCompat="@null"
|
||||||
|
app:trackTint="@color/toggle_selector" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:gravity="bottom|end"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UselessParent">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/apply_btt"
|
||||||
|
style="@style/WhiteButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:nextFocusRight="@id/cancel_btt"
|
||||||
|
android:nextFocusUp="@id/subtitles_italic"
|
||||||
|
android:text="@string/sort_apply"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<requestFocus />
|
||||||
|
</com.google.android.material.button.MaterialButton>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/cancel_btt"
|
||||||
|
style="@style/BlackButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:nextFocusLeft="@id/apply_btt"
|
||||||
|
android:nextFocusUp="@id/subtitles_remove_captions"
|
||||||
|
android:text="@string/sort_cancel" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
<!-- </ScrollView>-->
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
@ -102,6 +102,8 @@
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/subtitle_holder"
|
android:id="@+id/subtitle_holder"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
@ -169,6 +171,12 @@
|
||||||
android:layout_marginEnd="32dp"
|
android:layout_marginEnd="32dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextClock
|
||||||
|
android:id="@+id/player_video_clock"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/player_video_title_holder"
|
android:id="@+id/player_video_title_holder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@
|
||||||
<string name="continue_watching">Працягнуць прагляд</string>
|
<string name="continue_watching">Працягнуць прагляд</string>
|
||||||
<string name="action_remove_watching">Выдаліць</string>
|
<string name="action_remove_watching">Выдаліць</string>
|
||||||
<string name="action_open_watching">Больш інфармацыі</string>
|
<string name="action_open_watching">Больш інфармацыі</string>
|
||||||
<string name="action_open_play">\@string/home_play</string>
|
<string name="action_open_play">@string/home_play</string>
|
||||||
<string name="vpn_might_be_needed">Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN</string>
|
<string name="vpn_might_be_needed">Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN</string>
|
||||||
<string name="vpn_torrent">Гэты пастаўшчык — Torrent, рэкамендуецца VPN</string>
|
<string name="vpn_torrent">Гэты пастаўшчык — Torrent, рэкамендуецца VPN</string>
|
||||||
<string name="provider_info_meta">Вэб-сайт не пастаўляе метададзеных, загрузіць відэа не ўдасца, калі на сайце яго няма.</string>
|
<string name="provider_info_meta">Вэб-сайт не пастаўляе метададзеных, загрузіць відэа не ўдасца, калі на сайце яго няма.</string>
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<string name="result_poster_img_des">পোস্টার</string>
|
<string name="result_poster_img_des">পোস্টার</string>
|
||||||
<string name="play_with_app_name">ক্লাউডস্ট্রিম দিয়ে চালান</string>
|
<string name="play_with_app_name">ক্লাউডস্ট্রিম দিয়ে চালান</string>
|
||||||
<string name="title_home">হোম</string>
|
<string name="title_home">হোম</string>
|
||||||
<string name="cast_format" formatted="true">অভিনয়েঃ %s</string>
|
<string name="cast_format" formatted="true">অভিনয়ে: %s</string>
|
||||||
<string name="next_episode_time_day_format" formatted="true">%1$dদিন %2$dঘন্টা %3$dমিনিট</string>
|
<string name="next_episode_time_day_format" formatted="true">%1$dদিন %2$dঘন্টা %3$dমিনিট</string>
|
||||||
<string name="next_episode_time_hour_format" formatted="true">%1$dঘন্টা %2$dমিনিট</string>
|
<string name="next_episode_time_hour_format" formatted="true">%1$dঘন্টা %2$dমিনিট</string>
|
||||||
<string name="next_episode_time_min_format" formatted="true">%d মিনিট</string>
|
<string name="next_episode_time_min_format" formatted="true">%d মিনিট</string>
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<string name="title_search">খুঁজুন</string>
|
<string name="title_search">খুঁজুন</string>
|
||||||
<string name="title_downloads">ডাউনলোডসমূহ</string>
|
<string name="title_downloads">ডাউনলোডসমূহ</string>
|
||||||
<string name="title_settings">সেটিংস</string>
|
<string name="title_settings">সেটিংস</string>
|
||||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s এপি %2$d</string>
|
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s পর্ব %2$d</string>
|
||||||
<string name="next_episode_format" formatted="true">পর্ব %d মুক্তির তারিখ</string>
|
<string name="next_episode_format" formatted="true">পর্ব %d মুক্তির তারিখ</string>
|
||||||
<string name="result_share">শেয়ার</string>
|
<string name="result_share">শেয়ার</string>
|
||||||
<string name="result_open_in_browser">ব্রাউজারে খুলুন</string>
|
<string name="result_open_in_browser">ব্রাউজারে খুলুন</string>
|
||||||
|
|
@ -131,8 +131,8 @@
|
||||||
<string name="backup_failed">স্টোরেজ এর অনুমতি অনুপস্থিত। দয়া করে আবার চেষ্টা করুন।</string>
|
<string name="backup_failed">স্টোরেজ এর অনুমতি অনুপস্থিত। দয়া করে আবার চেষ্টা করুন।</string>
|
||||||
<string name="settings_info">তথ্য</string>
|
<string name="settings_info">তথ্য</string>
|
||||||
<string name="show_trailers_settings">ট্রেইলার প্রদর্শন করুন</string>
|
<string name="show_trailers_settings">ট্রেইলার প্রদর্শন করুন</string>
|
||||||
<string name="kitsu_settings">কিটসু হতে পোস্টারসমূহ প্রদর্শন করুন</string>
|
<string name="kitsu_settings">Kitsu থেকে পোস্টারসমূহ প্রদর্শন করুন</string>
|
||||||
<string name="automatic_plugin_updates">স্বয়ংক্রিয়ভাবে প্লাগিন এর হালনাগাদ</string>
|
<string name="automatic_plugin_updates">স্বয়ংক্রিয় প্লাগইন আপডেট</string>
|
||||||
<string name="automatic_plugin_download">স্বয়ংক্রিয়ভাবে প্লাগিনসমুহের ডাউনলোড</string>
|
<string name="automatic_plugin_download">স্বয়ংক্রিয়ভাবে প্লাগিনসমুহের ডাউনলোড</string>
|
||||||
<string name="category_updates">হালনাগাদ ও ব্যাকআপ</string>
|
<string name="category_updates">হালনাগাদ ও ব্যাকআপ</string>
|
||||||
<string name="updates_settings">অ্যাপ এর হালনাগাদ দেখান</string>
|
<string name="updates_settings">অ্যাপ এর হালনাগাদ দেখান</string>
|
||||||
|
|
@ -148,9 +148,9 @@
|
||||||
<string name="double_tap_to_seek_settings_des">সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন</string>
|
<string name="double_tap_to_seek_settings_des">সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন</string>
|
||||||
<string name="delete_file">ফাইল ডিলিট</string>
|
<string name="delete_file">ফাইল ডিলিট</string>
|
||||||
<string name="subs_default_reset_toast">মান ডিফল্ট এ রিসেট করুন</string>
|
<string name="subs_default_reset_toast">মান ডিফল্ট এ রিসেট করুন</string>
|
||||||
<string name="updates_settings_des">স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন</string>
|
<string name="updates_settings_des">স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন যোগ।</string>
|
||||||
<string name="movies_singular">সিনেমা</string>
|
<string name="movies_singular">সিনেমা</string>
|
||||||
<string name="show_fillers_settings">এনিমে এর ফিলার পর্ব দেখায়</string>
|
<string name="show_fillers_settings">অ্যানিমে ফিলার পর্ব প্রদর্শন</string>
|
||||||
<string name="tv_series">টিভি সিরিজ</string>
|
<string name="tv_series">টিভি সিরিজ</string>
|
||||||
<string name="no_links_found_toast">লিংক পাওয়া যায়নি</string>
|
<string name="no_links_found_toast">লিংক পাওয়া যায়নি</string>
|
||||||
<string name="benene_des">চা খাওয়ানো হয়েছে</string>
|
<string name="benene_des">চা খাওয়ানো হয়েছে</string>
|
||||||
|
|
@ -174,7 +174,7 @@
|
||||||
<string name="delete">ডিলিট</string>
|
<string name="delete">ডিলিট</string>
|
||||||
<string name="start">শুরু</string>
|
<string name="start">শুরু</string>
|
||||||
<string name="cartoons">কার্টুন</string>
|
<string name="cartoons">কার্টুন</string>
|
||||||
<string name="apk_installer_settings_des">কিছু ফোন নতুন প্যাকেজ ইনস্টলার সাপোর্ট করে না। যদি আপডেটগুলি ইনস্টল না হয় তবে পুরোনো পদ্ধতি ব্যবহার করে দেখুন।</string>
|
<string name="apk_installer_settings_des">Some devices do not support the new package installer. Try the legacy option if updates do not install।</string>
|
||||||
<string name="no_subtitles">সাবটাইটেল নেই</string>
|
<string name="no_subtitles">সাবটাইটেল নেই</string>
|
||||||
<string name="no_chromecast_support_toast">এই প্রোভাইডার ক্রোমকাস্ট সাপোর্ট করে না</string>
|
<string name="no_chromecast_support_toast">এই প্রোভাইডার ক্রোমকাস্ট সাপোর্ট করে না</string>
|
||||||
<string name="advanced_search">উন্নত অনুসন্ধান</string>
|
<string name="advanced_search">উন্নত অনুসন্ধান</string>
|
||||||
|
|
@ -204,7 +204,7 @@
|
||||||
<string name="anim">আমাদের তৈরি Anime দেখার অ্যাপ্লিকেশন</string>
|
<string name="anim">আমাদের তৈরি Anime দেখার অ্যাপ্লিকেশন</string>
|
||||||
<string name="nsfw">18+</string>
|
<string name="nsfw">18+</string>
|
||||||
<string name="anime">এনিমে</string>
|
<string name="anime">এনিমে</string>
|
||||||
<string name="pref_filter_search_quality">অনুসন্ধান ফলাফলে নির্বাচিত ভিডিও কুয়ালিটি লুকান</string>
|
<string name="pref_filter_search_quality">অনুসন্ধানের ফলে নির্বাচিত ভিডিওর মান লুকান</string>
|
||||||
<string name="app_storage">অ্যাপ</string>
|
<string name="app_storage">অ্যাপ</string>
|
||||||
<string name="livestreams">লাইভস্ট্রিম</string>
|
<string name="livestreams">লাইভস্ট্রিম</string>
|
||||||
<string name="apk_installer_settings">APK ইনস্টলার</string>
|
<string name="apk_installer_settings">APK ইনস্টলার</string>
|
||||||
|
|
@ -354,4 +354,37 @@
|
||||||
<string name="speech_recognition_unavailable">স্পিচ রিকগনিশন উপলব্ধ নেই</string>
|
<string name="speech_recognition_unavailable">স্পিচ রিকগনিশন উপলব্ধ নেই</string>
|
||||||
<string name="begin_speaking">কথা বলা শুরু করুন…</string>
|
<string name="begin_speaking">কথা বলা শুরু করুন…</string>
|
||||||
<string name="download_queue">ডাউনলোড তালিকা</string>
|
<string name="download_queue">ডাউনলোড তালিকা</string>
|
||||||
|
<string name="play_full_series_button">সম্পূর্ণ সিরিজ প্লে করুন</string>
|
||||||
|
<string name="torrent_info">এই ভিডিওটি একটি টরেন্ট, অর্থাৎ আপনার ভিডিও অ্যাক্টিভিটি ট্র্যাক করা সম্ভব।\nএগোনোর আগে টরেন্টিং সম্পর্কে ভালোভাবে বুঝে নিন।</string>
|
||||||
|
<string name="downloads_delete_select">ডিলিট করার জন্য আইটেম সিলেক্ট করুন</string>
|
||||||
|
<string name="downloads_empty">বর্তমানে কোনো ডাউনলোড নেই।</string>
|
||||||
|
<string name="queue_empty_message">বর্তমানে কোনো ডাউনলোড কিউতে নেই।</string>
|
||||||
|
<string name="offline_file">অফলাইনে দেখার জন্য উপলব্ধ</string>
|
||||||
|
<string name="select_all">সব সিলেক্ট করুন</string>
|
||||||
|
<string name="deselect_all">সব ডিসিলেক্ট করুন</string>
|
||||||
|
<string name="open_local_video">লোকাল ভিডিও খুলুন</string>
|
||||||
|
<string name="extra_brightness_settings">অতিরিক্ত ব্রাইটনেস</string>
|
||||||
|
<string name="extra_brightness_settings_des">ডিসপ্লে ব্রাইটনেস ১০০% ছাড়িয়ে গেলে ব্রাইটনেস ফিল্টার চালু করুন</string>
|
||||||
|
<string name="search_suggestions">সার্চ সাজেশন</string>
|
||||||
|
<string name="search_suggestions_des">টাইপ করার সময় সার্চ সাজেশন দেখান</string>
|
||||||
|
<string name="clear_suggestions">সাজেশন মুছে ফেলুন</string>
|
||||||
|
<string name="show_player_metadata_overlay">প্লেয়ার মেটাডেটা ওভারলে প্রদর্শন</string>
|
||||||
|
<string name="show_cast_in_details">কাস্ট প্যানেল প্রদর্শন</string>
|
||||||
|
<string name="install_prerelease">প্রাক-মুক্তি সংস্করণ ইনস্টলেশন</string>
|
||||||
|
<string name="prerelease_already_installed">প্রাক-মুক্তি সংস্করণ ইতোমধ্যে ইনস্টল রয়েছে।</string>
|
||||||
|
<string name="prerelease_install_failed">প্রাক-মুক্তি সংস্করণ ইনস্টল করতে সমস্যা হয়েছে।</string>
|
||||||
|
<string name="delete_format" formatted="true">(%1$d | %2$s) মুছুন</string>
|
||||||
|
<string name="test_warning">সাবধান</string>
|
||||||
|
<string name="delete_message_multiple" formatted="true">আপনি কি নিম্নলিখিত আইটেমগুলো স্থায়ীভাবে মুছতে চান?\n\n%s</string>
|
||||||
|
<string name="delete_files">ফাইল মুছুন</string>
|
||||||
|
<string name="delete_message_series_episodes" formatted="true">আপনি কি নিশ্চিত যে আপনি %1$s-এ নিচের এপিসোডগুলো স্থায়ীভাবে মুছে ফেলতে চান?\n\n%2$s</string>
|
||||||
|
<string name="delete_message_series_section" formatted="true">আপনি নিচের সিরিজগুলোর সব এপিসোডও স্থায়ীভাবে মুছে ফেলবেন:\n\n%s</string>
|
||||||
|
<string name="delete_message_series_only" formatted="true">আপনি কি নিশ্চিত যে আপনি নিচের সিরিজটির সব এপিসোড স্থায়ীভাবে মুছে ফেলতে চান?\n\n%s</string>
|
||||||
|
<string name="music_singular">সঙ্গীত</string>
|
||||||
|
<string name="audio_book_singular">অডিও বই</string>
|
||||||
|
<string name="custom_media_singular">মিডিয়া</string>
|
||||||
|
<string name="audio_singular">অডিও</string>
|
||||||
|
<string name="podcast_singular">পডকাস্ট</string>
|
||||||
|
<string name="video_singular">ভিডিও</string>
|
||||||
|
<string name="encoding_error">এনকোডিং ত্রুটি</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@
|
||||||
<string name="continue_watching">Continua mirant</string>
|
<string name="continue_watching">Continua mirant</string>
|
||||||
<string name="action_remove_watching">Eliminar</string>
|
<string name="action_remove_watching">Eliminar</string>
|
||||||
<string name="action_open_watching">Més info</string>
|
<string name="action_open_watching">Més info</string>
|
||||||
<string name="action_open_play">\@string/home_play</string>
|
<string name="action_open_play">@string/home_play</string>
|
||||||
<string name="vpn_might_be_needed">Potser necessiteu una VPN per que aquest proveïdor funcioni correctament</string>
|
<string name="vpn_might_be_needed">Potser necessiteu una VPN per que aquest proveïdor funcioni correctament</string>
|
||||||
<string name="vpn_torrent">Aquest proveïdor és un Torrent, es recomana fer servir una VPN</string>
|
<string name="vpn_torrent">Aquest proveïdor és un Torrent, es recomana fer servir una VPN</string>
|
||||||
<string name="provider_info_meta">El lloc no proveeix metadades, la càrrega del video fallarà si no existeix al lloc.</string>
|
<string name="provider_info_meta">El lloc no proveeix metadades, la càrrega del video fallarà si no existeix al lloc.</string>
|
||||||
|
|
@ -682,4 +682,17 @@
|
||||||
<string name="torrent_preferred_media">Zet torrent aan in Instellingen/Providers/Media voorkeur</string>
|
<string name="torrent_preferred_media">Zet torrent aan in Instellingen/Providers/Media voorkeur</string>
|
||||||
<string name="torrent_not_accepted">Herstart de app en accepteer de Stream Torrent pop-up om verder te gaan.</string>
|
<string name="torrent_not_accepted">Herstart de app en accepteer de Stream Torrent pop-up om verder te gaan.</string>
|
||||||
<string name="update_plugins_manually">Handmatige Update Knop</string>
|
<string name="update_plugins_manually">Handmatige Update Knop</string>
|
||||||
|
<string name="biometric_setting_summary">Open de app met Vingerafdruk, Face ID, PIN, Pattern en Wachtwoord.</string>
|
||||||
|
<string name="preview_seekbar">Seekbar preview</string>
|
||||||
|
<string name="preview_seekbar_desc">Zet preview thumbnail aan op seekbar</string>
|
||||||
|
<string name="software_decoding">Software decodering</string>
|
||||||
|
<string name="software_decoding_desc">Software decodering staat de mogelijkheid toe om de speler videobestanden af te laten spelen op jouw apparaat, maar het kan een haperige of onstabiele kijkervaring zorgen op hoge resolutie.</string>
|
||||||
|
<string name="volume_exceeded_100">Volume heeft de 100% overschreden</string>
|
||||||
|
<string name="update_plugins">Update Plugins</string>
|
||||||
|
<string name="no_plugins_updated_manually">Geen plugins zijn geupdate.</string>
|
||||||
|
<string name="player_notification_channel_name">Speler meldingen</string>
|
||||||
|
<string name="player_notification_channel_description">De speler melding om de kijkervaring van de achtergrond de besturen</string>
|
||||||
|
<string name="subtitles_from_online">Online</string>
|
||||||
|
<string name="download_parallel_settings_des">Hoeveel verschillende items kunnen in parallel gedownload worden</string>
|
||||||
|
<string name="parallel_downloads">Parallel downloads</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -742,4 +742,13 @@
|
||||||
<string name="video_singular">Wideo</string>
|
<string name="video_singular">Wideo</string>
|
||||||
<string name="skip_type_preview">Zapowiedź</string>
|
<string name="skip_type_preview">Zapowiedź</string>
|
||||||
<string name="player_is_live">Na żywo</string>
|
<string name="player_is_live">Na żywo</string>
|
||||||
|
<string name="profile_settings">Ustawienia profilu</string>
|
||||||
|
<string name="profile_hide_negative_sources">Ukryj źródła z negatywnym priorytetem</string>
|
||||||
|
<string name="profile_hide_error_sources">Ukryj źródła z błędami</string>
|
||||||
|
<plurals name="links_hidden">
|
||||||
|
<item quantity="one">%d ukryty odnośnik</item>
|
||||||
|
<item quantity="few">%d ukryte odnośniki</item>
|
||||||
|
<item quantity="many">%d ukrytych odnośników</item>
|
||||||
|
<item quantity="other">%d ukrytych odnośników</item>
|
||||||
|
</plurals>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@
|
||||||
<string name="continue_watching">Vazhdo shikimin</string>
|
<string name="continue_watching">Vazhdo shikimin</string>
|
||||||
<string name="action_remove_watching">Hiq</string>
|
<string name="action_remove_watching">Hiq</string>
|
||||||
<string name="action_open_watching">Më shumë informacion</string>
|
<string name="action_open_watching">Më shumë informacion</string>
|
||||||
<string name="action_open_play">\@string/home_play</string>
|
<string name="action_open_play">@string/home_play</string>
|
||||||
<string name="vpn_might_be_needed">Një VPN mund të nevojitet që ky ofrues të funksionojë në rregull</string>
|
<string name="vpn_might_be_needed">Një VPN mund të nevojitet që ky ofrues të funksionojë në rregull</string>
|
||||||
<string name="vpn_torrent">Ky ofrues është Torrent, rekomandohet një VPN</string>
|
<string name="vpn_torrent">Ky ofrues është Torrent, rekomandohet një VPN</string>
|
||||||
<string name="provider_info_meta">Metadata nuk ofrohet nga kjo faqe, ngarkimi i videos do të dështojë nëse nuk ekziston në këtë faqe.</string>
|
<string name="provider_info_meta">Metadata nuk ofrohet nga kjo faqe, ngarkimi i videos do të dështojë nëse nuk ekziston në këtë faqe.</string>
|
||||||
|
|
@ -735,4 +735,11 @@
|
||||||
<item quantity="other">%d shkarkime në radhë</item>
|
<item quantity="other">%d shkarkime në radhë</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="player_is_live">Live</string>
|
<string name="player_is_live">Live</string>
|
||||||
|
<string name="profile_settings">Cilësimet e profil-it</string>
|
||||||
|
<string name="profile_hide_negative_sources">Fshih burimet me prioritet negativ</string>
|
||||||
|
<string name="profile_hide_error_sources">Fshih burimet me error-e</string>
|
||||||
|
<plurals name="links_hidden">
|
||||||
|
<item quantity="one">%d link i fshehur</item>
|
||||||
|
<item quantity="other">%d linqe të fshehura</item>
|
||||||
|
</plurals>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
<string name="next_episode">Tập tiếp theo</string>
|
<string name="next_episode">Tập tiếp theo</string>
|
||||||
<string name="result_tags">Thể loại</string>
|
<string name="result_tags">Thể loại</string>
|
||||||
<string name="result_share">Chia sẻ</string>
|
<string name="result_share">Chia sẻ</string>
|
||||||
<string name="result_open_in_browser">Mở bằng trình duyệt</string>
|
<string name="result_open_in_browser">Phát bằng Trình duyệt</string>
|
||||||
<string name="skip_loading">Bỏ tải</string>
|
<string name="skip_loading">Bỏ tải</string>
|
||||||
<string name="loading">Đang tải…</string>
|
<string name="loading">Đang tải…</string>
|
||||||
<string name="type_watching">Đang xem</string>
|
<string name="type_watching">Đang xem</string>
|
||||||
|
|
@ -255,13 +255,13 @@
|
||||||
<string name="watch_quality_pref">Chất lượng xem ưu tiên (WiFi)</string>
|
<string name="watch_quality_pref">Chất lượng xem ưu tiên (WiFi)</string>
|
||||||
<string name="limit_title">Số ký tự tối đa tiêu đề trình phát</string>
|
<string name="limit_title">Số ký tự tối đa tiêu đề trình phát</string>
|
||||||
<string name="limit_title_rez">Hiển thị thông tin trình phát</string>
|
<string name="limit_title_rez">Hiển thị thông tin trình phát</string>
|
||||||
<string name="video_buffer_size_settings">Kích thước bộ nhớ đệm video</string>
|
<string name="video_buffer_size_settings">Kích thước bộ đệm video</string>
|
||||||
<string name="video_buffer_length_settings">Thời lượng bộ nhớ đệm</string>
|
<string name="video_buffer_length_settings">Thời lượng bộ đệm video</string>
|
||||||
<string name="video_buffer_disk_settings">Bộ nhớ đệm video trên thiết bị</string>
|
<string name="video_buffer_disk_settings">Bộ nhớ đệm video trên thiết bị</string>
|
||||||
<string name="video_buffer_clear_settings">Xoá bộ nhớ đệm hình ảnh và video</string>
|
<string name="video_buffer_clear_settings">Xoá bộ nhớ đệm hình ảnh và video</string>
|
||||||
<string name="video_ram_description">Sẽ gây lỗi nếu đặt quá cao trên thiết bị có bộ nhớ thấp như Android TV.</string>
|
<string name="video_ram_description">Sẽ gây lỗi nếu đặt quá cao trên thiết bị có bộ nhớ thấp như Android TV.</string>
|
||||||
<string name="video_disk_description">Sẽ gây lỗi nếu đặt quá cao trên máy có dung lượng lưu trữ thấp như Android TV.</string>
|
<string name="video_disk_description">Sẽ gây lỗi nếu đặt quá cao trên thiết bị có dung lượng lưu trữ thấp như Android TV.</string>
|
||||||
<string name="dns_pref">DNS over HTTPS</string>
|
<string name="dns_pref">DNS qua HTTPS</string>
|
||||||
<string name="dns_pref_summary">Rất hữu ích để bỏ chặn ISP</string>
|
<string name="dns_pref_summary">Rất hữu ích để bỏ chặn ISP</string>
|
||||||
<string name="add_site_pref">Bản sao trang web</string>
|
<string name="add_site_pref">Bản sao trang web</string>
|
||||||
<string name="remove_site_pref">Xoá trang web</string>
|
<string name="remove_site_pref">Xoá trang web</string>
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@
|
||||||
<string name="opensubtitles_key">opensubtitles_key</string>
|
<string name="opensubtitles_key">opensubtitles_key</string>
|
||||||
<string name="subdl_key">subdl_key</string>
|
<string name="subdl_key">subdl_key</string>
|
||||||
<string name="animeskip_key">animeskip_key</string>
|
<string name="animeskip_key">animeskip_key</string>
|
||||||
|
<string name="tv_layout_clock_key">tv_layout_clock_key</string>
|
||||||
|
|
||||||
<string name="pref_category_security_key">pref_category_security_key</string>
|
<string name="pref_category_security_key">pref_category_security_key</string>
|
||||||
<string name="pref_category_gestures_key">pref_category_gestures_key</string>
|
<string name="pref_category_gestures_key">pref_category_gestures_key</string>
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,8 @@
|
||||||
<string name="pref_category_looks">Looks</string>
|
<string name="pref_category_looks">Looks</string>
|
||||||
<string name="pref_category_ui_features">Features</string>
|
<string name="pref_category_ui_features">Features</string>
|
||||||
<string name="category_general">General</string>
|
<string name="category_general">General</string>
|
||||||
|
<string name="tv_layout_clock_settings">Show real time clock</string>
|
||||||
|
<string name="tv_layout_clock_settings_des">Show a real time clock at the top of the screen. Applies to the homepage and player</string>
|
||||||
<string name="random_button_settings">Random Button</string>
|
<string name="random_button_settings">Random Button</string>
|
||||||
<string name="random_button_settings_desc">Show random button on Homepage and Library</string>
|
<string name="random_button_settings_desc">Show random button on Homepage and Library</string>
|
||||||
<string name="provider_lang_settings">Extension languages</string>
|
<string name="provider_lang_settings">Extension languages</string>
|
||||||
|
|
@ -622,6 +624,14 @@
|
||||||
<string name="action_subscribe">Subscribe</string>
|
<string name="action_subscribe">Subscribe</string>
|
||||||
<string name="action_unsubscribe">Unsubscribe</string>
|
<string name="action_unsubscribe">Unsubscribe</string>
|
||||||
<string name="profile_number">Profile %d</string>
|
<string name="profile_number">Profile %d</string>
|
||||||
|
<string name="profile_settings">Profile settings</string>
|
||||||
|
<string name="profile_hide_negative_sources">Hide sources with a negative priority</string>
|
||||||
|
<string name="profile_hide_error_sources">Hide sources with errors</string>
|
||||||
|
<plurals name="links_hidden">
|
||||||
|
<item quantity="one">%d hidden link</item>
|
||||||
|
<item quantity="other">%d hidden links</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
<string name="wifi">Wi-Fi</string>
|
<string name="wifi">Wi-Fi</string>
|
||||||
<string name="mobile_data">Mobile data</string>
|
<string name="mobile_data">Mobile data</string>
|
||||||
<string name="set_default">Set default</string>
|
<string name="set_default">Set default</string>
|
||||||
|
|
@ -783,5 +793,4 @@
|
||||||
<item quantity="other">%d downloads queued</item>
|
<item quantity="other">%d downloads queued</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="player_is_live">Live</string>
|
<string name="player_is_live">Live</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,12 @@
|
||||||
android:icon="@drawable/metadata_overlay_icon"
|
android:icon="@drawable/metadata_overlay_icon"
|
||||||
android:key="@string/show_player_metadata_key"
|
android:key="@string/show_player_metadata_key"
|
||||||
android:title="@string/show_player_metadata_overlay" />
|
android:title="@string/show_player_metadata_overlay" />
|
||||||
|
<SwitchPreference
|
||||||
|
android:icon="@drawable/ic_baseline_clock_24"
|
||||||
|
android:summary="@string/tv_layout_clock_settings_des"
|
||||||
|
android:title="@string/tv_layout_clock_settings"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="@string/tv_layout_clock_key" />
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
android:icon="@drawable/ic_baseline_play_arrow_24"
|
android:icon="@drawable/ic_baseline_play_arrow_24"
|
||||||
android:summary="@string/random_button_settings_desc"
|
android:summary="@string/random_button_settings_desc"
|
||||||
|
|
|
||||||
1
fastlane/metadata/android/bn/changelogs/2.txt
Normal file
1
fastlane/metadata/android/bn/changelogs/2.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
- চেঞ্জলগ যোগ করা হয়েছে!
|
||||||
7
fastlane/metadata/android/bn/full_description.txt
Normal file
7
fastlane/metadata/android/bn/full_description.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
CloudStream-3 দিয়ে সিনেমা, টিভি-সিরিজ ও অ্যানিমে স্ট্রিম ও ডাউনলোড করা যায়।
|
||||||
|
|
||||||
|
অ্যাপে কোনো বিজ্ঞাপন ও অ্যানালিটিক্স নেই এবং
|
||||||
|
এটি একাধিক ট্রেলার ও মুভি সাইট সাপোর্ট করে, আরও আছে যেমন—
|
||||||
|
বুকমার্ক
|
||||||
|
সাবটাইটেল ডাউনলোড
|
||||||
|
ক্রোমকাস্ট সাপোর্ট
|
||||||
1
fastlane/metadata/android/bn/short_description.txt
Normal file
1
fastlane/metadata/android/bn/short_description.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
সিনেমা, টিভি সিরিজ ও অ্যানিমে স্ট্রিম ও ডাউনলোড করুন।
|
||||||
1
fastlane/metadata/android/bn/title.txt
Normal file
1
fastlane/metadata/android/bn/title.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
CloudStream
|
||||||
Loading…
Add table
Add a link
Reference in a new issue